How do I perform iterator computations over iterators of Results without collecting to a temporary vector?

后端 未结 3 864
忘了有多久
忘了有多久 2020-12-02 02:21

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

fn doit>         


        
相关标签:
3条回答
  • 2020-12-02 02:57

    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.

    0 讨论(0)
  • 2020-12-02 03:07

    "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.

    0 讨论(0)
  • 2020-12-02 03:19

    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());
    }
    
    0 讨论(0)
提交回复
热议问题