如何重复矢量某些元素根据条件?

问题描述 投票:3回答:4

我一个kata过程中遇到了这个问题。我更可读的实施是以下几点:

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

我对此有两个问题:

  • 是否有必要创建另一个Vec?是可以使用相同的Vec,即修改它,而迭代?
  • 我的解决方案,我想,效率低下:我分配了大量的载体,而且我也没有保证,这将得到优化。有没有更好的解决办法:可读和更低的配置呢?
performance vector rust mutability
4个回答
3
投票

flat_map预计迭代器,这样你就可以返回值的迭代器:

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

需要注意的事项:


如果你真的在试图重新使用内存设置,我会倒退行走沿迭代器,以帮助避免无效指数:

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

这可能是算法差;每个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
}

5
投票

您可以在同一载体中做到这一点,但它需要移动载体的剩余部分(双倍数后)每次遇到偶数,这是低效的时间。这将是更好地使用新的载体,一个简单的循环来做到这一点:

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

该解决方案以保持所有的数字,包括了一倍找齐所需的精确空间分配内存只有一次。


3
投票

是否有必要创建另一个Vec?是可以使用相同的Vec,即修改它,而迭代?

这是可能的,但效率不高。 Vec在堆上,其中每个元件邻近于所述下一个分配的内存块。如果你只是想要做的每个元素上的一些数值运算,然后是的,你可以做的地方运行。但是你需要在他人之间插入新的元素,这意味着移动所有下列元素一个地方的权利和(可能)分配更多的内存。

你都在思考的Haskell代码则可能使用哈斯克尔Data.List这是一个链表不是矢量。如果您使用的内存更有效的结构类似Data.Vector.Unboxedrepa那么你也将无法插入而迭代元素。

我的解决方案,因为我想,低效:我分配了大量的载体,而且我也没有保证,这将得到优化。它是一个更好的解决方案:可读和更低的配置呢?

像这样的东西可能会奏效。它有一个功能性的感觉还在,但通过分配一个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
    })
}

您也可以shrink_to_fit()在最后,但它看起来有点丑陋,因为你无法返回的解决方案作为一种表达。


2
投票
  • 是否有必要创建另一个VEC?是可以使用相同的VEC,即修改它,而迭代?
  • 我的解决方案,我想,效率低下:我分配了大量的载体,而且我也没有保证,这将得到优化。有没有更好的解决办法:可读和更低的配置呢?

有一两件事你可以做这是相当地道是实现你的功能为“迭代器适配器” - 即,而不是处理具体Vec,在Iterator元素i32s看看吧。然后,一切都将在堆栈上的变量,并没有分配会不惜一切进行。它可能是这个样子:

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

然后,你可以写

fn main() {
    let vec = vec![1, 2, 3, 4, 5, 6];
    let double_even = DoubleEven {
        iter: vec.into_iter(),
        next: None,
    };
    for x in double_even {
        print!("{}, ", x)  // prints 1, 2, 2, 3, 4, 4, 5, 6, 6, 
    }
}

更妙的是,你可以添加一个功能double_even到任何东西,都可以变成i32的迭代器,允许你编写如下:

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

现在,我会承认,在这种情况下,样板往上加,但是你可以看到,为调用的代码确实是非常简洁。对于更复杂的适配器,这种模式是非常有用的。此外,超出了最初的Vec分配,没有内存分配任何事情!只要栈上分配的变量,允许在一个发布版本高效的代码。

© www.soinside.com 2019 - 2024. All rights reserved.