我一个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
,即修改它,而迭代?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]);
}
需要注意的事项:
use std::vec::Vec
。它通过prelude已经导入。vec!()
;使用vec![]
代替。不要紧,编译器,但它关系到人类。如果你真的在试图重新使用内存设置,我会倒退行走沿迭代器,以帮助避免无效指数:
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
}
您可以在同一载体中做到这一点,但它需要移动载体的剩余部分(双倍数后)每次遇到偶数,这是低效的时间。这将是更好地使用新的载体,一个简单的循环来做到这一点:
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代码则可能使用哈斯克尔Data.List
这是一个链表不是矢量。如果您使用的内存更有效的结构类似Data.Vector.Unboxed
或repa那么你也将无法插入而迭代元素。
我的解决方案,因为我想,低效:我分配了大量的载体,而且我也没有保证,这将得到优化。它是一个更好的解决方案:可读和更低的配置呢?
像这样的东西可能会奏效。它有一个功能性的感觉还在,但通过分配一个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()
在最后,但它看起来有点丑陋,因为你无法返回的解决方案作为一种表达。
- 是否有必要创建另一个VEC?是可以使用相同的VEC,即修改它,而迭代?
- 我的解决方案,我想,效率低下:我分配了大量的载体,而且我也没有保证,这将得到优化。有没有更好的解决办法:可读和更低的配置呢?
有一两件事你可以做这是相当地道是实现你的功能为“迭代器适配器” - 即,而不是处理具体Vec
,在Iterator
元素i32
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
})
)
}
}
然后,你可以写
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
分配,没有内存分配任何事情!只要栈上分配的变量,允许在一个发布版本高效的代码。