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代码可能使用的是HaskellData.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看起来很少是这样:)。和通常一样,我认为你的答案更好,更详细地介绍了不同的解决方案。在实际案例中,我会尝试每个解决方案并进行一些基准测试,但这只是一些练习,所以。。。