Skip to content

Add Iterator::find_or_{first,nth,last} #79271

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 173 additions & 0 deletions library/core/src/iter/traits/iterator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2231,6 +2231,179 @@ pub trait Iterator {
self.try_fold((), check(predicate)).break_value()
}

/// Searches for an element of an iterator that satisfies a predicate, returns the first
/// element if no such element is found.
///
/// `find_or_first()` takes a closure that returns `true` or `false`. It applies this closure
/// to each element of the iterator, and if any of them return `true`, then `find_or_first()`
/// returns [`Some(element)`]. If they all return `false`, it returns the same as if [`next`]
/// was invoked instead, though the iterator will be empty.
///
/// `find_or_first()` is short-circuiting; in other words, it will stop processing as soon as
/// closure returns `true`.
///
/// Because `find_or_first()` takes a reference, and many iterators iterate over references,
/// this leads to a possibly confusing situation where the argument is a double reference. You
/// can see this effect in the examples below, with `&&x`.
///
/// [`Some(element)`]: Some
/// [`next`]: Iterator::next
///
/// # Example
///
/// ```
/// #![feature(iter_find_or_fnl)]
///
/// let a = [0, 1, 2, 3];
///
/// assert_eq!(
/// a.iter().find_or_first(|&&x| x > 1),
/// Some(&2) // predicate is satisfied
/// );
/// assert_eq!(
/// a.iter().find_or_first(|&&x| x > 10),
/// Some(&0) // predicate not satisfied, first element is returned
/// );
/// ```
#[inline]
#[unstable(feature = "iter_find_or_fnl", reason = "recently added", issue = "none")]
fn find_or_first<P>(&mut self, mut predicate: P) -> Option<Self::Item>
where
Self: Sized,
P: FnMut(&Self::Item) -> bool,
{
match self.next() {
Some(item) if predicate(&item) => Some(item),
default => self.find(predicate).or(default),
}
}

/// Searches for an element of an iterator that satisfies a predicate, returns the `n`th
/// element if no such element is found.
///
/// `find_or_nth()` takes a closure that returns `true` or `false`. It applies this closure to
/// each element of the iterator, and if any of them return `true`, then `find_or_nth()`
/// returns [`Some(element)`]. If they all return `false`, it returns the same as if [`nth(n)`]
/// was invoked instead, though the iterator will be empty.
///
/// Like most indexing operations, the count starts from zero, so if `predicate` is never
/// satisfied, `find_or_nth(predicate, 0)` returns the first item, `find_or_nth(1)` the second,
/// and so on.
///
/// `find_or_nth()` is short-circuiting; in other words, it will stop processing as soon as the
/// closure returns `true`.
///
/// Because `find_or_nth()` takes a reference, and many iterators iterate over references, this
/// leads to a possibly confusing situation where the argument is a double reference. You can
/// see this effect in the examples below, with `&&x`.
///
/// [`Some(element)`]: Some
/// [`nth(n)`]: Iterator::nth
///
/// # Example
///
/// ```
/// #![feature(iter_find_or_fnl)]
///
/// let a = [0, 1, 2, 3];
///
/// assert_eq!(
/// a.iter().find_or_nth(|&&x| x > 1, 2),
/// Some(&2) // predicate is satisfied
/// );
/// assert_eq!(
/// a.iter().find_or_nth(|&&x| x > 10, 2),
/// Some(&2) // predicate not satisfied, 3rd element is returned
/// );
/// assert_eq!(
/// a.iter().find_or_nth(|&&x| x > 10, 5),
/// None // predicate not satisfied, no 6th element
/// );
/// ```
#[inline]
#[unstable(feature = "iter_find_or_fnl", reason = "recently added", issue = "none")]
fn find_or_nth<P>(&mut self, predicate: P, n: usize) -> Option<Self::Item>
where
Self: Sized,
P: FnMut(&Self::Item) -> bool,
{
#[inline]
fn check<T>(
mut predicate: impl FnMut(&T) -> bool,
n: usize,
) -> impl FnMut(Option<T>, (usize, T)) -> ControlFlow<T, Option<T>> {
move |prev, (idx, cur)| {
if predicate(&cur) {
ControlFlow::Break(cur)
} else if idx == n {
debug_assert!(prev.is_none());
ControlFlow::Continue(Some(cur))
} else {
debug_assert_eq!(idx < n, prev.is_none());
ControlFlow::Continue(prev)
}
}
}

self.enumerate().try_fold(None, check(predicate, n)).into_result().unwrap_or_else(Some)
}

/// Searches for an element of an iterator that satisfies a predicate, returns the last
/// element if no such element is found.
///
/// `find_or_last()` takes a closure that returns `true` or `false`. It applies this closure to
/// each element of the iterator, and if any of them return `true`, then `find_or_last()`
/// returns [`Some(element)`]. If they all return `false`, it returns the same as if [`last`]
/// was invoked instead.
///
/// `find_or_last()` is short-circuiting; in other words, it will stop processing as soon as the
/// closure returns `true`.
///
/// Because `find_or_last()` takes a reference, and many iterators iterate over references, this
/// leads to a possibly confusing situation where the argument is a double reference. You can
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// leads to a possibly confusing situation where the argument is a double reference. You can
/// leads to a special situation where the argument is a double reference. You can

Recommendation to remove passive wording.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copied the wording from Iterator::find. Shall I change it there as well? It also present in some other functions, e.g. Iterator::filter.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's simply a matter of style. I realize that wording which suggests probable 'confusion' is commonly used throughout Rust code and documentation.

FWIW I'm not in any position to be making requests of contributors :)

/// see this effect in the examples below, with `&&x`.
///
/// [`Some(element)`]: Some
/// [`last`]: Iterator::last
///
/// # Example
///
/// ```
/// #![feature(iter_find_or_fnl)]
///
/// let a = [0, 1, 2, 3];
///
/// assert_eq!(
/// a.iter().find_or_last(|&&x| x > 1),
/// Some(&2) // predicate is satisfied
/// );
/// assert_eq!(
/// a.iter().find_or_last(|&&x| x > 10),
/// Some(&3) // predicate not satisfied, last element is returned
/// );
/// ```
#[inline]
#[unstable(feature = "iter_find_or_fnl", reason = "recently added", issue = "none")]
fn find_or_last<P>(&mut self, predicate: P) -> Option<Self::Item>
where
Self: Sized,
P: FnMut(&Self::Item) -> bool,
{
#[inline]
fn check<T>(
mut predicate: impl FnMut(&T) -> bool,
) -> impl FnMut(Option<T>, T) -> ControlFlow<T, Option<T>> {
move |_, cur| {
if predicate(&cur) {
ControlFlow::Break(cur)
} else {
ControlFlow::Continue(Some(cur))
}
}
}
self.try_fold(None, check(predicate)).into_result().unwrap_or_else(Some)
}

/// Applies function to the elements of iterator and returns
/// the first non-none result.
///
Expand Down
56 changes: 56 additions & 0 deletions library/core/tests/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1930,6 +1930,62 @@ fn test_find() {
assert!(v.iter().find(|&&x| x % 12 == 0).is_none());
}

#[test]
fn find_or_first() {
assert_eq!([0, 1, 2, 3].iter().find_or_first(|&&n| n > 1), Some(&2));
assert_eq!([0, 1, 2, 3].iter().find_or_first(|&&n| n > 2), Some(&3));
assert_eq!([0, 1, 2, 3].iter().find_or_first(|&&n| n > 3), Some(&0));
assert_eq!([3, 2, 1, 0].iter().find_or_first(|&&n| n > 1), Some(&3));
assert_eq!([1].iter().find_or_first(|&&n| n > 1), Some(&1));
assert_eq!([2].iter().find_or_first(|&&n| n > 1), Some(&2));
assert_eq!([(); 0].iter().find_or_first(|()| unreachable!()), None);
}

#[test]
fn find_or_nth() {
assert_eq!([0, 1, 2, 3].iter().find_or_nth(|&&n| n > 1, 0), Some(&2));
assert_eq!([0, 1, 2, 3].iter().find_or_nth(|&&n| n > 1, 1), Some(&2));
assert_eq!([0, 1, 2, 3].iter().find_or_nth(|&&n| n > 1, 2), Some(&2));
assert_eq!([0, 1, 2, 3].iter().find_or_nth(|&&n| n > 1, 3), Some(&2));
assert_eq!([0, 1, 2, 3].iter().find_or_nth(|&&n| n > 1, 4), Some(&2));

assert_eq!([0, 1, 2, 3].iter().find_or_nth(|&&n| n > 2, 0), Some(&3));
assert_eq!([0, 1, 2, 3].iter().find_or_nth(|&&n| n > 2, 1), Some(&3));
assert_eq!([0, 1, 2, 3].iter().find_or_nth(|&&n| n > 2, 2), Some(&3));
assert_eq!([0, 1, 2, 3].iter().find_or_nth(|&&n| n > 2, 3), Some(&3));
assert_eq!([0, 1, 2, 3].iter().find_or_nth(|&&n| n > 2, 4), Some(&3));

assert_eq!([0, 1, 2, 3].iter().find_or_nth(|&&n| n > 3, 0), Some(&0));
assert_eq!([0, 1, 2, 3].iter().find_or_nth(|&&n| n > 3, 1), Some(&1));
assert_eq!([0, 1, 2, 3].iter().find_or_nth(|&&n| n > 3, 2), Some(&2));
assert_eq!([0, 1, 2, 3].iter().find_or_nth(|&&n| n > 3, 3), Some(&3));
assert_eq!([0, 1, 2, 3].iter().find_or_nth(|&&n| n > 3, 4), None);

assert_eq!([3, 2, 1, 0].iter().find_or_nth(|&&n| n > 1, 0), Some(&3));
assert_eq!([3, 2, 1, 0].iter().find_or_nth(|&&n| n > 1, 1), Some(&3));
assert_eq!([3, 2, 1, 0].iter().find_or_nth(|&&n| n > 1, 2), Some(&3));
assert_eq!([3, 2, 1, 0].iter().find_or_nth(|&&n| n > 1, 3), Some(&3));
assert_eq!([3, 2, 1, 0].iter().find_or_nth(|&&n| n > 1, 4), Some(&3));

assert_eq!([1].iter().find_or_nth(|&&n| n > 1, 0), Some(&1));
assert_eq!([1].iter().find_or_nth(|&&n| n > 1, 1), None);
assert_eq!([2].iter().find_or_nth(|&&n| n > 1, 0), Some(&2));
assert_eq!([2].iter().find_or_nth(|&&n| n > 1, 1), Some(&2));

assert_eq!([(); 0].iter().find_or_nth(|()| unreachable!(), 0), None);
}

#[test]
fn find_or_last() {
assert_eq!([0, 1, 2, 3].iter().find_or_last(|&&n| n > 1), Some(&2));
assert_eq!([0, 1, 2, 3].iter().find_or_last(|&&n| n > 2), Some(&3));
assert_eq!([0, 1, 2, 3].iter().find_or_last(|&&n| n > 3), Some(&3));
assert_eq!([3, 2, 1, 0].iter().find_or_last(|&&n| n > 1), Some(&3));
assert_eq!([1].iter().find_or_last(|&&n| n > 1), Some(&1));
assert_eq!([2].iter().find_or_last(|&&n| n > 1), Some(&2));
assert_eq!([(); 0].iter().find_or_last(|()| unreachable!()), None);
}

#[test]
fn test_find_map() {
let xs: &[isize] = &[];
Expand Down
1 change: 1 addition & 0 deletions library/core/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
#![feature(int_error_matching)]
#![feature(array_value_iter)]
#![feature(iter_advance_by)]
#![feature(iter_find_or_fnl)]
#![feature(iter_partition_in_place)]
#![feature(iter_is_partitioned)]
#![feature(iter_order_by)]
Expand Down