How do I perform iterator computations over iterat

2020-01-29 02:29发布

I'm looking for a way to eliminate the temporary vector allocation in this example:

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());
}

This is a variation of the iterator with error handling theme, except that I don't want to create a collection (so collect() doesn't quite do the job), but I want to perform further operations on the elements being iterated over.

Note that this gives the wrong result because Ok is less than Err:

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

It would give the right result for max() by accident, but it would not stop iterating on the first error.

标签: iterator rust
3条回答
Deceive 欺骗
2楼-- · 2020-01-29 03:08

"Lifting" a function to handle an iterator of results is a fairly common pattern and, as usual, itertools has a solution — 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);
}

This code began life as ResultShunt in the standard library before being extracted to itertools. It's what underlies the implementation of sum and product for iterators of Result.

查看更多
女痞
3楼-- · 2020-01-29 03:09

It's possible to abuse collect() for this:

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) }
            }
        }
    }
}

This can be used via iter.collect::<Min<_>>().value(). This is a lot of machinery, and I don't see a way to abstract over it (so that you only need to supply std::cmp::min or some other semigroup operation).

I didn't look in the direction of Iterator::try_fold, which provides most of the machinery.

查看更多
叼着烟拽天下
4楼-- · 2020-01-29 03:16

Iterator::try_fold provides the framework for what you need, and it's available since 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());
}

Before that, I think your only option is manually iterating (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());
}
查看更多
登录 后发表回答