【问题标题】:Efficiently insert or replace multiple elements in the middle or at the beginning of a Vec?在 Vec 的中间或开头有效地插入或替换多个元素?
【发布时间】:2015-02-23 16:37:30
【问题描述】:

是否有任何直接的方法可以在线性时间内插入或替换来自&[T] 和/或Vec<T> 的多个元素在Vec 的中间或开头?

我只能找到std::vec::Vec::insert,但这只是为了在O(n)时间插入一个元素,所以我显然不能循环调用它。

可以在该索引处执行split_offextend 将新元素放入拆分的左半部分,然后将extend 后半部分放入第一部分,但在那里更好的方法?

【问题讨论】:

  • 然而,split_off 是 O(n),就像 insert。 AFAIK,使用 Vec,在随机位置添加元素的所有方法都是 O(n)。 “将索引 i 设置为该项目,并将 j+1 设置为 j ≥ i 的项目”的更有效解决方案依赖于 BTreeMap,在 O(log n) 中。

标签: rust


【解决方案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 接口中没有合适的方法(如我所见)。但是我们总是可以自己实现相同的东西。

    记忆移动

    TCopy时,可能最明显的方式就是移动内存,像这样:

    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的末尾,并移动它在线性时间内使用 swap 到所需位置:

    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);
            },
        }
    }
    

    迭代器连接

    也许最好的选择是根本不修改 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);
    

    【讨论】:

    • 对于第三种解决方案,您可以使用skip,而不是使用split_at
    • 是的,但它需要额外的“offset
    【解决方案3】:

    我试图添加到 rust 中的向量并找到链接到此处的 this closed question,(尽管这个问题是 both 前置和插入 AND 效率。我认为我的答案会更好另一个更精确的问题的答案,因为我无法证明效率),但下面的代码帮助我预先准备,(反之亦然。)[我确信其他两个答案更有效,但是以我的学习方式,我喜欢有可以剪切和粘贴的答案,并附有演示答案应用的示例。]

    pub trait Unshift<T> { fn unshift(&mut self, s: &[T]) -> (); }
    pub trait UnshiftVec<T> { fn unshift_vec(&mut self, s: Vec<T>) -> (); }
    pub trait UnshiftMemoryHog<T> { fn unshift_memory_hog(&mut self, s: Vec<T>) -> (); }
    pub trait Shift<T> { fn shift(&mut self) -> (); }
    pub trait ShiftN<T> { fn shift_n(&mut self, s: usize) -> (); }
    
    impl<T: std::clone::Clone> ShiftN<T> for Vec<T> {
        fn shift_n(&mut self, s: usize) -> ()
        // where
        //    T: std::clone::Clone,
        {   
            self.drain(0..s);
        }
    }
    
    impl<T: std::clone::Clone> Shift<T> for Vec<T> {
        fn shift(&mut self) -> ()
        // where
        //    T: std::clone::Clone,
        {   
            self.drain(0..1);
        }
    }
    
    impl<T: std::clone::Clone> Unshift<T> for Vec<T> {
        fn unshift(&mut self, s: &[T]) -> ()
        // where
        //    T: std::clone::Clone,
        {   
            self.splice(0..0, s.to_vec());
        }
    }
    impl<T: std::clone::Clone> UnshiftVec<T> for Vec<T> {
        fn unshift_vec(&mut self, s: Vec<T>) -> ()
        where
            T: std::clone::Clone,
        {   
            self.splice(0..0, s);
        }
    }
    
    impl<T: std::clone::Clone> UnshiftMemoryHog<T> for Vec<T> {
        fn unshift_memory_hog(&mut self, s: Vec<T>) -> ()
        where
            T: std::clone::Clone,
        {
            let mut tmp: Vec<_> = s.to_owned();
            //let mut tmp: Vec<_> = s.clone(); // this also works for some data types
            /*
            let local_s: Vec<_> = self.clone(); // explicit clone()
            tmp.extend(local_s);                // to vec is possible
            */
            tmp.extend(self.clone());
            *self = tmp;
            //*self = (*tmp).to_vec(); // Just because it compiles, doesn't make it right.
        }
    }
    
    // this works for: v = unshift(v, &vec![8]);
    // (If you don't want to impl Unshift for Vec<T>)
    
    #[allow(dead_code)]
    fn unshift_fn<T>(v: Vec<T>, s: &[T]) -> Vec<T>
    where
        T: Clone,
    {
        // create a mutable vec and fill it
        // with a clone of the array that we want
        // at the start of the vec.
        let mut tmp: Vec<_> = s.to_owned();
        // then we add the existing vector to the end
        // of the temporary vector.
        tmp.extend(v);
        // return the tmp vec that is identitcal
        // to unshift-ing the original vec.
        tmp
    }
    
    /*
        N.B. It is sometimes (often?) more memory efficient to reverse
        the vector and use push/pop, rather than splice/drain;
        Especially if you create your vectors in "stack order" to begin with.
    */
    
    fn main() {
        let mut v: Vec<usize> = vec![1, 2, 3];
        println!("Before push:\t {:?}", v);
        v.push(0);
        println!("After push:\t {:?}", v);
        v.pop();
        println!("popped:\t\t {:?}", v);
        v.drain(0..1);
        println!("drain(0..1)\t {:?}", v);
        /*
            // We could use a function
        let c = v.clone();
        v = unshift_fn(c, &vec![0]);
        */
        v.splice(0..0, vec![0]);
        println!("splice(0..0, vec![0]) {:?}", v);
        v.shift_n(1);
        println!("shift\t\t {:?}", v);
        v.unshift_memory_hog(vec![8, 16, 31, 1]);
        println!("MEMORY guzzler unshift {:?}", v);
        //v.drain(0..3);
        v.drain(0..=2);
        println!("back to the start: {:?}", v);
        v.unshift_vec(vec![0]);
        println!("zerothed with unshift: {:?}", v);
    
        let mut w = vec![4, 5, 6];
        /*
        let prepend_this = &[1, 2, 3];
        w.unshift_vec(prepend_this.to_vec());
        */
        w.unshift(&[1, 2, 3]);
    
        assert_eq!(&w, &[1, 2, 3, 4, 5, 6]);
        println!("{:?} == {:?}", &w, &[1, 2, 3, 4, 5, 6]);
    }
    

    【讨论】:

    • 在您的unshift 中,我认为您最好使用s.iter().cloned(),因为这样可以避免分配中间向量。
    • 其中大多数看起来像是splice 的不必要重命名版本,已在接受的答案中提到,那么这个答案的意义何在?当你已经可以写.splice(..0, v)时,你真的需要写.unshift_vec(v)吗?
    • 我对这段代码有很多想法,所以我将它们编译成playground
    • 作为一个几十年来习惯于 push/pop 被 unshift/shift 镜像的人,这就是我学习惯用的 Rust 方式的方式。也许它会帮助那些总是乐于学习的人。
    • @trentcl 我发现你的游乐场很有帮助。谢谢。
    猜你喜欢
    • 2013-06-20
    • 2014-08-11
    • 1970-01-01
    • 2010-10-17
    • 2015-11-09
    • 1970-01-01
    • 1970-01-01
    • 2020-12-09
    • 1970-01-01
    相关资源
    最近更新 更多