Как добавить ломтик к Vec

Я ожидал Vec::insert_slice(index, slice) метод - решение для строк (String::insert_str()) существует.

Я знаю о Vec::insert(), но это вставляет только один элемент за раз, а не срез. Альтернативно, когда предварительно срезанный срез представляет собой Vec вместо этого можно добавить к нему, но это не обобщает. Идиоматическое решение, вероятно, использует Vec::splice(), но использование итераторов, как в примере, заставляет меня почесать голову.

Во-вторых, вся концепция предвидения, по-видимому, была изгнана из документов. Там нет ни одного упоминания. Буду признателен за комментарии о том, почему. Обратите внимание, что относительно малоизвестные методы, такие как Vec::swap_remove() существуют

Мой типичный вариант использования состоит из индексированных байтовых строк.

2 ответа

Решение

String::insert_str использует тот факт, что строка по существу Vec<u8>, Он перераспределяет базовый буфер, перемещает все начальные байты в конец, затем добавляет новые байты в начало.

Это не всегда безопасно и не может быть добавлено непосредственно в Vec потому что во время копирования Vec больше не находится в допустимом состоянии - в данных есть "дыры".

Это не имеет значения для String потому что данные u8 а также u8 не реализует Drop, Там нет такой гарантии для произвольного T в Vec, но если вы очень внимательно следите за своим состоянием и убираетесь должным образом, вы можете сделать то же самое - вот что splice делает!

вся концепция предвидения, казалось бы, была изгнана

Я бы предположил, что это потому, что готовится к Vec плохая идея с точки зрения производительности. Если вам нужно сделать это, наивный случай прост:

fn prepend<T>(v: Vec<T>, s: &[T]) -> Vec<T>
where
    T: Clone,
{
    let mut tmp: Vec<_> = s.to_owned();
    tmp.extend(v);
    tmp
}

Это занимает немного больше памяти, так как нам нужно достаточно места для двух копий v,

splice Метод принимает итератор новых значений и диапазон значений для замены. В этом случае мы не хотим ничего заменять, поэтому мы даем пустой диапазон индекса, в который мы хотим вставить. Нам также нужно преобразовать фрагмент в итератор соответствующего типа:

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

v.splice(0..0, s.iter().cloned());

splice Реализация не является тривиальной, но она эффективно делает отслеживание, в котором мы нуждаемся. После удаления фрагмента значений он затем использует этот фрагмент памяти для новых значений. Он также перемещает хвост вектора (возможно, несколько раз, в зависимости от входного итератора). Drop реализация Slice гарантирует, что вещи всегда будут в действительном состоянии.


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

Принимая во внимание то, что сказал Шепмастер, вы можете реализовать функцию, добавляющую срез к Copyспособные элементы к Vec как String::insert_str() делает следующим образом:

use std::ptr;

unsafe fn prepend_slice<T: Copy>(vec: &mut Vec<T>, slice: &[T]) {
    let len = vec.len();
    let amt = slice.len();
    vec.reserve(amt);

    ptr::copy(vec.as_ptr(),
              vec.as_mut_ptr().offset((amt) as isize),
              len);
    ptr::copy(slice.as_ptr(),
              vec.as_mut_ptr(),
              amt);
    vec.set_len(len + amt);
}

fn main() {
    let mut v = vec![4, 5, 6];

    unsafe { prepend_slice(&mut v, &[1, 2, 3]) }

    assert_eq!(&v, &[1, 2, 3, 4, 5, 6]);
}
Другие вопросы по тегам