rust-itertools / itertools

Extra iterator adaptors, iterator methods, free functions, and macros.
https://docs.rs/itertools/
Apache License 2.0
2.76k stars 309 forks source link

Return first Ok or last Err #983

Closed axelkar closed 3 months ago

axelkar commented 3 months ago

This "collector" should return the first Ok value it finds as Ok(Some(value)). If no Ok values are found, return the last Err. If no values are in the iterator, return Ok(None)

I've written this a couple of times in my own code and figured it'd be useful for Itertools to include.

fn find_first_ok<T, E, I>(iter: I) -> Result<Option<T>, E>
where
    I: IntoIterator<Item = Result<T, E>>,
{
    let mut last_err = None;

    for item in iter {
        match item {
            Ok(value) => return Ok(Some(value)),
            Err(err) => last_err = Some(err),
        }
    }

    match last_err {
        Some(err) => Err(err),
        None => Ok(None),
    }
}

#[test]
fn test_no_items() {
    assert_eq!(find_first_ok::<(), (), _>([]), Ok(None));
}
#[test]
fn test_only_err() {
    assert_eq!(find_first_ok::<(), u8, _>([
        Err(1),
        Err(2),
        Err(3),
    ]), Err(3));
}
#[test]
fn test_ok() {
    assert_eq!(find_first_ok::<(), u8, _>([
        Err(1),
        Ok(()),
        Err(2),
    ]), Ok(Some(())));
}
phimuemue commented 3 months ago

If we include this, we should think about a generalized version, right? To me, it seems that this basically groups the iterator's items into two categories. If we find an item of the first category, return it right away. If there's no such item, return the last item of the second category. (Please correct me if I got that wrong.)

If that's true, I guess a natural API would be something like this:

 fn get_first_satisfying_predicate_or_last_not_satisfying_predicate(self, mut pred: impl FnMut(&Item)->bool) -> Option<Item> {
  let mut last_not_satisfying_predicate = None;

    for item in self {
        if pred(&item) {
         return Some(item); 
        } else {
         last_not_satisfying_predicate = Some(item);
        }
    }
    return last_not_satisfying_predicate;
 }

That would allow to use it for iterators with arbitrary Items.

... which makes me think: Isn't this find_or_last?

axelkar commented 3 months ago

... which makes me think: Isn't this find_or_last?

Yes:

fn find_first_ok<T, E, I>(iter: I) -> Result<Option<T>, E>
where
    I: IntoIterator<Item = Result<T, E>>,
{
    iter.into_iter().find_or_last(|res| res.is_ok()).transpose()
}

Can an example for Results be added to the documentation?

phimuemue commented 3 months ago

Sure. Could you make a PR?

axelkar commented 3 months ago

Sure. Could you make a PR?

Yes