如何在不收集到临时向量的情况下对结果迭代器进行迭代计算?

7
我正在寻找一种方法来消除此示例中的临时向量分配:
fn doit<T: Iterator<Item = Result<i32, &'static str>>>(name: &str, iter: T) {
    println!(
        "{}: {:?}",
        name,
        iter.collect::<Result<Vec<_>, _>>()
            .map(|v| v.into_iter().min())
    );
}

fn main() {
    let without_errors = vec![Ok(1), Ok(2), Ok(3)];
    let with_errors = vec![Ok(1), Err("error"), Ok(2)];

    doit("without errors", without_errors.into_iter());
    doit("with errors", with_errors.into_iter());
}

这是一个带有错误处理的迭代器变体,但我不想创建一个集合(因此collect()不能完成任务),而是想对正在迭代的元素执行进一步的操作。
请注意,这会给出错误的结果,因为Ok小于Err
fn doit<T: Iterator<Item = Result<i32, &'static str>>>(name: &str, iter: T) {
    println!("{}: {:?}", name, iter.min());
}

这会意外地给出max()的正确结果,但它不会在第一个错误处停止迭代。


2
如果您希望在第一个错误处停止,应该是什么结果?错误还是其他什么? - Andriy Tylychko
@AndriyTylychko,你是对的,我有点搞砸了这个例子;现在已经修复了。一旦遇到第一个错误,我想立即得到一个错误提示。 - Florian Weimer
给Rust学习者的一条提示:使用Result时,代码具有异常控制流。迭代器可能不是这种逻辑的最佳选择。即使采用try_fold方法,也可能会显得有些笨拙和嘈杂。我们不应忘记简单的for循环。 - Fifnmar
3个回答

7

Iterator::try_fold 提供了所需的框架,它自Rust 1.27起就可用(Playground):

fn fold_ok<I, T, E, F>(mut iter: I, f: F) -> Result<Option<T>, E>
where
    I: Iterator<Item = Result<T, E>>,
    T: Ord,
    F: Fn(T, T) -> T,
{
    iter.try_fold(None, |r, i| {
        let i = i?;
        Ok(Some(if let Some(r) = r { f(r, i) } else { i }))
    })
}

fn main() {
    let without_errors = vec![Ok(1), Ok(2), Ok(3)];
    let with_errors = vec![Ok(1), Err("error"), Ok(2)];

    fn doit<'r, T>(name: &str, iter: T)
    where
        T: Iterator<Item = &'r Result<i32, &'static str>> + Clone,
    {
        println!("{}: {:?}", name, fold_ok(iter.cloned(), ::std::cmp::min));
    }

    doit("without errors", without_errors.iter());
    doit("with errors", with_errors.iter());
}

在此之前,我认为你唯一的选择是手动迭代 (Playground)

fn fold_ok<I, T, E, F>(mut iter: I, f: F) -> Result<Option<T>, E>
where
    I: Iterator<Item = Result<T, E>>,
    T: Ord,
    F: Fn(T, T) -> T,
{
    let mut result = match iter.next() {
        None => return Ok(None),
        Some(r) => r?,
    };

    for item in iter {
        result = f(result, item?);
    }

    Ok(Some(result))
}

fn main() {
    let without_errors = vec![Ok(1), Ok(2), Ok(3)];
    let with_errors = vec![Ok(1), Err("error"), Ok(2)];

    fn doit<'r, T>(name: &str, iter: T)
    where
        T: Iterator<Item = &'r Result<i32, &'static str>> + Clone,
    {
        println!(
            "{}: {:?}",
            name,
            fold_ok(iter.clone().cloned(), ::std::cmp::min)
        );
    }

    doit("without errors", without_errors.iter());
    doit("with errors", with_errors.iter());
}

1
https://docs.rs/itertools/0.7.6/itertools/trait.Itertools.html#method.fold_results - Shepmaster

5

将函数升级以处理结果迭代器是一个相当常见的模式,而且如往常一样,itertools已经提供了解决方案 — process_results:

use itertools; // 0.8.0

fn doit(name: &str, iter: impl Iterator<Item = Result<i32, &'static str>>) {
    let min = itertools::process_results(iter, |i| i.min());
    println!("{}: {:?}", name, min);
}

这段代码最初是作为标准库中的 ResultShunt 而存在的,后来被提取到 itertools 中。它是实现对 Result 迭代器进行 sumproduct 的基础。


0

可以滥用collect()来实现这个功能:

pub struct Min<T> {
    value: Option<T>,
}

impl<T> Min<T> {
    pub fn value(self) -> Option<T> {
        self.value
    }
}

impl<T> std::iter::FromIterator<T> for Min<T>
where
    T: Ord,
{
    fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
        let mut iter = iter.into_iter();
        match iter.next() {
            None => Min { value: None },
            Some(mut value) => {
                for i in iter {
                    value = std::cmp::min(value, i);
                }
                Min { value: Some(value) }
            }
        }
    }
}

可以通过iter.collect::<Min<_>>().value()使用。这是很多机械,我没有看到一种抽象它的方法(这样你只需要提供std::cmp::min或其他半群操作)。

我没有考虑Iterator::try_fold,它提供了大部分机制。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接