我有一个包含数字的输入向量。在输出向量中,我需要按从右到左的顺序获取部分乘积序列。输出的最后一个元素必须等于输入中的最后一个元素;输出的倒数第二个元素必须是输入的倒数第二个元素的乘积;等等。例如,如果输入向量是
let input = vec![2, 3, 4];
然后我需要输出为[24, 12, 4]
。
我的实现在输入上使用了迭代器,将其反转map
s,然后再次反转并collect
s:
fn main() {
let input = vec![2, 3, 4];
let mut prod = 1;
let p: Vec<usize> = input
.iter()
.rev()
.map(|v| {
prod *= v;
prod
}).rev()
.collect();
println!("{:?}", p);
}
The result is [2, 6, 24]
,就像我删除两个rev()
一样。这两个rev()
无法解决问题,它们只是互相“歼灭”。
此任务是否可以不使用for
而以“呼叫链”方式解决?
答案 0 :(得分:8)
此行为实际上是explicitly described in the documentation:
关于副作用的说明
map
迭代器实现DoubleEndedIterator
,这意味着 您也可以向后map
:[…]
但是,如果您的闭包处于状态,则向后迭代可能会以某种方式起作用 没想到。 […]
A way to solve this是通过添加中介collect
来确保第二个rev
不适用于Map
:
fn main() {
let input = vec![2, 3, 4];
let mut prod = 1;
let p: Vec<usize> = input
.iter()
.map(|v| {
prod *= v;
prod
}).rev()
.collect::<Vec<_>>()
.into_iter()
.rev()
.collect();
println!("{:?}", p);
}
但这需要额外的分配。 Another way将要收集,然后reverse:
fn main() {
let input = vec![2, 3, 4];
let mut prod = 1;
let mut p: Vec<usize> = input
.iter()
.rev()
.map(|v| {
prod *= v;
prod
}).collect();
p.reverse();
println!("{:?}", p);
}
答案 1 :(得分:2)
您的prod
变量将状态从一项传递到另一项,这不是映射的作用。映射独立地作用于每个元素,这使它们易于并行化并易于推理。您要求的结果是精确的right scan(prefix sum的相反情况),但是我不确定是否有方便的方法可以从右边进行收集(可能是最简单的可变方法)方式将使用VecDeque::push_front
)。这导致我为第一个版本分两次通过了该操作:
fn main() {
let input: Vec<usize> = vec![2, 3, 4];
let initprod = 1;
let prev: Vec<usize> = input
.iter()
.rev()
.scan(initprod, |prod, &v| {
*prod *= v;
Some(*prod)
}).collect();
let p: Vec<usize> = prev.into_iter().rev().collect();
println!("{:?}", p);
}
请注意,initprod
是不可变的; prod
带有状态。使用into_iter
也意味着prev
被消耗了。我们可以使用mcarton所示的vec.reverse
,但是我们需要有一个可变的变量。扫描可以并行化,但程度要低于地图。参见例如discussion on adding them to Rayon。人们可能还会考虑,ExactSizeIterator
是否应允许反向收集到普通向量中,但是标准库scan
方法使用Option
打破了已知大小(根据next
约定将其转换为“边扫描边扫描”)。
使用预分配的VecDeque从右边收集的副本变少了。我使用了一个额外的范围来限制可变性。它还需要Rust 1.21或更高版本才能使用for_each
。跟踪项目的数量和环形缓冲区结构没有不必要的开销,但是至少还是有些可读的。
use std::collections::VecDeque;
fn main() {
let input: Vec<usize> = vec![2,3,4];
let p = {
let mut pmut = VecDeque::with_capacity(input.len());
let initprod = 1;
input
.iter()
.rev()
.scan(initprod, |prod, &v| {
*prod *= v;
Some(*prod)
})
.for_each(|v| {
pmut.push_front(v)
});
pmut
};
println!("{:?}", p);
}
顺便说一句,遵循关于Lisp程序员了解一切的价值和一无所获的古老格言,这是一个Haskell版本,我真的不知道它的效率如何:
scanr1 (*) [2, 3, 4]