为什么双向反转的迭代器的作用好像从未反转过?

时间:2018-08-24 21:24:06

标签: rust

我有一个包含数字的输入向量。在输出向量中,我需要按从右到左的顺序获取部分乘积序列。输出的最后一个元素必须等于输入中的最后一个元素;输出的倒数第二个元素必须是输入的倒数第二个元素的乘积;等等。例如,如果输入向量是

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而以“呼叫链”方式解决?

2 个答案:

答案 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 scanprefix 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]