【问题标题】:Efficient Rust collector of Results holding Vecs持有 Vecs 的高效 Rust 收集器
【发布时间】:2020-05-02 07:34:23
【问题描述】:

我正在学习 Rust,我遇到了以下模式,它将 Result<Vec<_>, _>s 的迭代器折叠成一个大的 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 的意义在于默默地删除错误结果,但我想以某种方式“将flatten 映射到Results 下”。我也知道,一般来说,你不能收集类型的迭代器

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

进入迭代器

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

因为您需要在外部迭代器中实际完成所有计算才能知道最终结果。尽管如此,在这种特殊情况下,当您想.flatten() 然后.collect() 之后,您似乎可以做到这一点。

最后,我可以看到collect() 给了我一种从it 构建向量向量的方法,然后我可以将该向量展平为我想要的单个大向量。但这有很多不必要的内存分配。

标准库能否帮助您以高效的 Rust-ic 方式做到这一点?

【问题讨论】:

  • 我个人认为命令式很好。

标签: rust functional-programming iterator


【解决方案1】:

我想我会从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
        }
    )
}

【讨论】:

  • 我希望某种成语不是复杂到必须分解成一个单独的函数,但如果这是我们能做的最好的,我会接受这个。 :)
【解决方案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 为每个条目生成Oks 的迭代器或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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-12-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-20
    • 2011-05-27
    • 1970-01-01
    相关资源
    最近更新 更多