Performance 如何根据条件在向量中重复某些元素?

Performance 如何根据条件在向量中重复某些元素?,performance,vector,rust,mutability,Performance,Vector,Rust,Mutability,我在一次旅行中遇到了这个问题。我的更具可读性的实现如下所示: use std::vec::Vec; fn repeat_even(v: Vec<i32>) -> Vec<i32> { v.into_iter().flat_map(|x| match x % 2 { 0 => vec![x, x], _ => vec![x] }).collect() } fn main() { let v = vec![1, 2, 3, 4, 6];

我在一次旅行中遇到了这个问题。我的更具可读性的实现如下所示:

use std::vec::Vec;

fn repeat_even(v: Vec<i32>) -> Vec<i32> {
    v.into_iter().flat_map(|x| match x % 2 { 0 => vec![x, x], _ => vec![x] }).collect()
}

fn main() {
    let v = vec![1, 2, 3, 4, 6];
    assert_eq!(repeat_even(v), vec![1, 2, 2, 3, 4, 4, 6, 6]);
}
trait DoubleEvenExt : IntoIterator + Sized {
    fn double_even(self) -> DoubleEven<Self::IntoIter> {
        DoubleEven {
            iter: self.into_iter(),
            next: None,
        }
    }
}

impl<I> DoubleEvenExt for I where I: IntoIterator<Item=i32> {}

fn main() {
    let vec = vec![1, 2, 3, 4, 5, 6];
    for x in vec.double_even() {
        print!("{}, ", x)  // prints 1, 2, 2, 3, 4, 4, 5, 6, 6, 
    }
}
使用std::vec::vec;
fn重复_偶数(v:Vec)->Vec{
v、 into_iter().flat_map(|x | match x%2{0=>vec![x,x],=>vec![x]})。collect()
}
fn main(){
设v=vec![1,2,3,4,6];
断言(重复偶数(v),vec![1,2,2,3,4,4,6,6]);
}
我有两个问题:

  • 是否需要创建另一个
    Vec
    ?是否可以使用相同的
    Vec
    ,即在迭代时对其进行修改

  • 我想,我的解决方案是低效的:我分配了很多向量,我不能保证这会得到优化。是否有更好的解决方案:可读性好,分配更少


您可以在同一个向量内执行此操作,但每次遇到偶数时都需要移动向量的其余部分(在双倍数之后),这是效率低下的。最好使用一个新的向量和一个简单的循环:

fn main() {
    let v = vec![1, 2, 3, 4, 6];

    let mut v2 = Vec::with_capacity(v.len() + v.iter().filter(|&n| n % 2 == 0).count());

    for n in v {
        v2.push(n);
        if n % 2 == 0 { v2.push(n) }
    }

    assert_eq!(v2, vec![1, 2, 2, 3, 4, 4, 6, 6]);
}
此解决方案只分配一次内存,并提供容纳所有数字(包括双倍偶数)所需的确切空间

是否需要创建另一个
Vec
?是否可以使用相同的
Vec
,即在迭代时对其进行修改

这是可能的,但效率不高
Vec
在堆上分配一块内存,其中每个元素与下一个相邻。如果您只想对每个元素执行一些数值操作,那么是的,您可以就地执行该操作。但是您需要在其他元素之间插入新元素,这意味着将以下所有元素向右移动一个位置,并(可能)分配更多内存

您正在考虑的Haskell代码可能使用的是Haskell
Data.List
,它是一个链表而不是向量。如果您使用了更高效的内存结构,如或,那么您也将无法在迭代时插入元素

正如我想象的那样,我的解决方案是低效的:我分配了很多向量,我不能保证这会得到优化。这是一个更好的解决方案:可读性和分配更少

像这样的东西可能有用。它仍然有一种功能上的感觉,但通过分配一个
Vec
然后对其进行变异来工作:

fn double_even(v: Vec<i32>) -> Vec<i32> {
    // allocate for the worst case (i.e. all elements of v are even)
    let result = Vec::with_capacity(v.len() * 2);
    v.into_iter().fold(result, |mut acc, n| {
        acc.push(n);
        if n % 2 == 0 {
            acc.push(n);
        }
        acc
    })
}
fn双偶数(v:Vec)->Vec{
//分配最坏情况(即v的所有元素均为偶数)
设结果=Vec::具有_容量(v.len()*2);
v、 折叠(结果,| mut acc,n |{
acc.push(n);
如果n%2==0{
acc.push(n);
}
行政协调会
})
}

您也可以在结尾处
收缩到_-fit()
,但它看起来有点难看,因为您无法将解决方案作为表达式返回。

平面映射需要迭代器,因此您可以返回值的迭代器:

use std::iter;

fn double_even(v: &[i32]) -> Vec<i32> {
    v.iter().flat_map(|&x| {
        let count = if x % 2 == 0 { 2 } else { 1 };
        iter::repeat(x).take(count)
    }).collect()
}

fn main() {
    let v = vec![1, 2, 3, 4, 6];
    assert_eq!(double_even(&v), vec![1, 2, 2, 3, 4, 4, 6, 6]);
}
这可能在算法上更糟糕;每个
insert
都会在其后面移动所有数据。我相信最坏的情况是当每个元素都是偶数时,O(n^2)

我通常也不会在这里按值计算。我宁愿采用可变引用。如果您真的需要它,您可以将它包装回一个值:

fn double_even_ref(v: &mut Vec<i32>) {
    for i in (0..v.len()).rev() {
        let val = v[i];
        if val % 2 == 0 {
            v.insert(i, val);
        }
    }
}

fn double_even(mut v: Vec<i32>) -> Vec<i32> {
    double_even_ref(&mut v);
    v
}
fn双偶数参考(v:&mut-Vec){
对于(0..v.len()).rev()中的i{
设val=v[i];
如果val%2==0{
v、 插入(i,val);
}
}
}
fn双偶数(mut v:Vec)->Vec{
双偶数参考(&mut v);
v
}
  • 是否需要创建另一个Vec?是否可以使用相同的Vec,即在迭代时对其进行修改

  • 我想,我的解决方案是低效的:我分配了很多向量,我不能保证这会得到优化。是否有更好的解决方案:可读性好,分配更少

您可以做的一件非常惯用的事情是将您的函数实现为一个“迭代器适配器”——也就是说,不要处理
Vec
,特别是查看
i32
元素的
iterator
s。然后,所有内容都将是堆栈上的一个变量,并且根本不会进行分配。它可能看起来像这样:

struct DoubleEven<I> {
    iter: I,
    next: Option<i32>,
}

impl<I> Iterator for DoubleEven<I>
    where I: Iterator<Item=i32>
{
    type Item = i32;
    fn next(&mut self) -> Option<Self::Item> {
        self.next.take().or_else(||
            self.iter.next().map(|value| {
                if value % 2 == 0 { self.next = Some(value) }
                value
            })
        )
    }
}
更好的是,您可以向任何可以转换为
i32
迭代器的对象添加一个函数
double\u偶
,从而可以编写以下内容:

use std::vec::Vec;

fn repeat_even(v: Vec<i32>) -> Vec<i32> {
    v.into_iter().flat_map(|x| match x % 2 { 0 => vec![x, x], _ => vec![x] }).collect()
}

fn main() {
    let v = vec![1, 2, 3, 4, 6];
    assert_eq!(repeat_even(v), vec![1, 2, 2, 3, 4, 4, 6, 6]);
}
trait DoubleEvenExt : IntoIterator + Sized {
    fn double_even(self) -> DoubleEven<Self::IntoIter> {
        DoubleEven {
            iter: self.into_iter(),
            next: None,
        }
    }
}

impl<I> DoubleEvenExt for I where I: IntoIterator<Item=i32> {}

fn main() {
    let vec = vec![1, 2, 3, 4, 5, 6];
    for x in vec.double_even() {
        print!("{}, ", x)  // prints 1, 2, 2, 3, 4, 4, 5, 6, 6, 
    }
}
trait DoubleEvenExt:intoi迭代器+大小{
fn双偶数(自)->双偶数{
双倍{
iter:self.into_iter(),
下一个:没有,
}
}
}
I的impl DoubleEvenExt,其中I:IntoIterator{}
fn main(){
让vec=vec![1,2,3,4,5,6];
对于向量中的x.double_偶数(){
print!(“{},”,x)//打印1,2,2,3,4,4,5,6,6,
}
}

现在我承认,在本例中,样板文件是累加的,但是您可以看到,在调用站点,代码非常简洁。对于更复杂的适配器,此模式非常有用。此外,除了初始
Vec
分配之外,没有任何内存分配!只需堆叠分配的变量,就可以在发布版本中实现高效的代码。

这是一个直截了当的解决方案,但我们失去了功能性气味。我喜欢用一行创建一个集合的可能性,就像haskell一样。@Boiethios:这很吸引人,没错,但请注意,高效的(尤其是在向量方面)haskell看起来很少是这样:)。和通常一样,我认为你的答案更好,更详细地介绍了不同的解决方案。在实际案例中,我会尝试每个解决方案并进行一些基准测试,但这只是一些练习,所以。。。