Эффективно вставлять или заменять несколько элементов в середине или в начале Vec?

Есть ли простой способ вставить или заменить несколько элементов из &[T] и/или Vec<T> в середине или в начале Vec в линейном времени?

Я мог найти std::vec::Vec::insert, но это только для вставки одного элемента в O(n) времени, поэтому я, очевидно, не могу позвонить что в цикле.

Я мог бы сделать split_off в этом индексе, extend новые элементы в левую половину раскола, а затем extend вторую половину в первую, но есть ли лучший способ?

Ответы

Ответ 1

Начиная с Rust 1.21.0, Vec::splice доступен и позволяет вставлять в любой момент, включая полное добавление:

let mut vec = vec![1, 5];
let slice = &[2, 3, 4];

vec.splice(1..1, slice.iter().cloned());

println!("{:?}", vec); // [1, 2, 3, 4, 5]

Состояние документов:

Примечание 4: Это оптимально, если:

  • Хвост (элементы в векторе после диапазона) пуст
  • или replace_with дает меньше элементов, чем диапазоны длины
  • или нижняя граница ее size_hint() точна.

В этом случае нижняя граница итератора среза должна быть точной, поэтому он должен выполнять один ход памяти.


splice является более мощным в том смысле, что он позволяет удалить диапазон значений (первый аргумент), вставить новые значения (второй аргумент) и, при необходимости, получить старые значения (результат вызова).

Замена набора элементов

let mut vec = vec![0, 1, 5];
let slice = &[2, 3, 4];

vec.splice(..2, slice.iter().cloned());

println!("{:?}", vec); // [2, 3, 4, 5]

Получение предыдущих значений

let mut vec = vec![0, 1, 2, 3, 4];
let slice = &[9, 8, 7];

let old: Vec<_> = vec.splice(3.., slice.iter().cloned()).collect();

println!("{:?}", vec); // [0, 1, 2, 9, 8, 7]
println!("{:?}", old); // [3, 4]

Ответ 2

Хорошо, в интерфейсе Vec нет соответствующего метода (как я вижу). Но мы всегда можем реализовать то же самое.

memmove

Когда T Скопировать, возможно, наиболее очевидным способом является перемещение памяти, например:

fn push_all_at<T>(v: &mut Vec<T>, offset: usize, s: &[T]) where T: Copy {
    match (v.len(), s.len()) {
        (_, 0) => (),
        (current_len, _) => {
            v.reserve_exact(s.len());
            unsafe {
                v.set_len(current_len + s.len());
                let to_move = current_len - offset;
                let src = v.as_mut_ptr().offset(offset as isize);
                if to_move > 0 {
                    let dst = src.offset(s.len() as isize);
                    std::ptr::copy_memory(dst, src, to_move);
                }
                std::ptr::copy_nonoverlapping_memory(src, s.as_ptr(), s.len());
            }
        },
    }
}

перетасовка

Если T не копируется, но реализует Clone, мы можем добавить данный фрагмент в конец Vec и перенести его в нужную позицию с помощью свопов в линейное время:

fn push_all_at<T>(v: &mut Vec<T>, mut offset: usize, s: &[T]) where T: Clone + Default {
    match (v.len(), s.len()) {
        (_, 0) => (),
        (0, _) => { v.push_all(s); },
        (_, _) => {
            assert!(offset <= v.len());
            let pad = s.len() - ((v.len() - offset) % s.len());
            v.extend(repeat(Default::default()).take(pad));
            v.push_all(s);
            let total = v.len();
            while total - offset >= s.len() {
                for i in 0 .. s.len() { v.swap(offset + i, total - s.len() + i); }
                offset += s.len();
            }
            v.truncate(total - pad);
        },
    }
}

итераторы concat

Возможно, лучшим вариантом будет вовсе не изменять Vec. Например, если вы хотите получить доступ к результату через итератор, мы можем просто построить цепочку итераторов из наших блоков:

let v: &[usize] = &[0, 1, 2];
let s: &[usize] = &[3, 4, 5, 6];
let offset = 2;
let chain = v.iter().take(offset).chain(s.iter()).chain(v.iter().skip(offset));

let result: Vec<_> = chain.collect();
println!("Result: {:?}", result);