高效的 Rust 结果收集器,可容纳向量

3

我正在学习Rust,并遇到了以下模式,该模式将Result<Vec<_>, _>的迭代器折叠为一个大的Vec<_>,如果迭代器中的任何结果失败,则会失败:

fn accumulate<T, E>(it: impl Iterator<Item = Result<Vec<T>, E>>) -> Result<Vec<T>, E> {
    let mut result = Vec::new();
    for mut ts in it {
        result.append(&mut ts?)
    }
    Ok(result)
}

我想这个函数可以写成非常简短的“函数式”版本,但我一直在努力找到它。道义上来说,我想做到以下:

it.map(|v| v?.into_iter()).flatten().collect()

但这不符合类型检查。通过运行一些小例子,我认为flatten的目的是默默丢弃错误结果,但我希望以某种方式“在Result下映射flatten”。我也知道通常情况下你无法收集,比如说一个类型的迭代器。
impl Iterator<Item = Result<impl Iterator<Item = T>, Error>>

转换为迭代器

Result<impl Iterator<Item = impl Iterator<Item = T>>, Error>

由于你需要在外部迭代器中完成所有计算才能知道最终结果,因此你需要这样做。然而,在特殊情况下,当你想要使用.flatten()然后立即使用.collect()时,似乎可以使其工作。

最后,我可以看到collect()为我提供了一种从it构建向量向量的方法,然后我可以将这个向量展平成我想要的单个大向量。但这会产生许多不必要的内存分配。

标准库能帮助你以高效、Rust-ic的方式完成这项任务吗?


1
个人而言,我认为命令式形式很好。 - trent
2个回答

3

我认为我会从try_fold开始,因为它可以处理Result并在Err上停止:

fn acc2<T, E>(mut it: impl Iterator<Item = Result<Vec<T>, E>>) -> Result<Vec<T>, E> {
    it.try_fold(
        Vec::new(),
        |mut vec, res_ts: Result<Vec<_>, E>| {
            res_ts.map(move |mut ts| { // map preserves Err
                // In case of Ok(...), append to already found elements
                vec.append(&mut ts);
                vec
            })
        }
    )
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f6f738ddedecda1875df283f221dbfdc

事实证明,Itertools已经有了fold_results,可以满足您的需求。

fn acc3<T, E>(mut it: impl Iterator<Item = Result<Vec<T>, E>>) -> Result<Vec<T>, E> {
    it.fold_results(
        Vec::new(),
        |mut vec, mut ts| {
            vec.append(&mut ts);
            vec
        }
    )
}

1
我希望有一种习语,不需要分解成单独的函数,但如果这是我们能做到的最好的方法,我会接受它。 :) - Keeley Hoek

2
实现这个只使用迭代器方法:
use std::iter::{self, Iterator};

pub fn accumulate<T, E>(it: impl Iterator<Item = Result<Vec<T>, E>>) -> Result<Vec<T>, E> {
    it.flat_map(|v| {
        v.map_or_else(
            |e| Iter::A(iter::once(Err(e))),
            |t| Iter::B(t.into_iter().map(Ok)),
        )
    })
    .collect()
}

// Utility enum that can be generated by the #[auto_enum] derive macro
enum Iter<T, A: Iterator<Item = T>, B: Iterator<Item = T>> {
    A(A),
    B(B),
}
impl<T, A: Iterator<Item = T>, B: Iterator<Item = T>> Iterator for Iter<T, A, B> {
    type Item = T;
    fn next(&mut self) -> Option<T> {
        match self {
            Self::A(a) => a.next(),
            Self::B(b) => b.next(),
        }
    }
}

这里使用了flat_map,对于每个条目,它会产生一个Ok迭代器或一个Err的迭代器。从语义上讲,这与使用for循环的控制流代码等价。
示例:https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=68558e27900940476e443d670a120e91 请参阅auto_enums以派生枚举委托迭代器变体。
或者,您可以使用either::Either替换Iter,其在两个项目的实现相同:

https://docs.rs/either/1.5.3/either/enum.Either.html#impl-Iterator


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