Skip to content

Commit

Permalink
Rollup merge of #87054 - kit-981:master, r=scottmcm
Browse files Browse the repository at this point in the history
Add a `try_reduce` method to the Iterator trait

Tracking issue: #87053
  • Loading branch information
matthiaskrgr authored Dec 4, 2021
2 parents 887999d + aef59e4 commit c223a1c
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 0 deletions.
80 changes: 80 additions & 0 deletions library/core/src/iter/traits/iterator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2216,6 +2216,86 @@ pub trait Iterator {
Some(self.fold(first, f))
}

/// Reduces the elements to a single one by repeatedly applying a reducing operation. If the
/// closure returns a failure, the failure is propagated back to the caller immediately.
///
/// The return type of this method depends on the return type of the closure. If the closure
/// returns `Result<Self::Item, E>`, then this function will return `Result<Option<Self::Item>,
/// E>`. If the closure returns `Option<Self::Item>`, then this function will return
/// `Option<Option<Self::Item>>`.
///
/// When called on an empty iterator, this function will return either `Some(None)` or
/// `Ok(None)` depending on the type of the provided closure.
///
/// For iterators with at least one element, this is essentially the same as calling
/// [`try_fold()`] with the first element of the iterator as the initial accumulator value.
///
/// [`try_fold()`]: Iterator::try_fold
///
/// # Examples
///
/// Safely calculate the sum of a series of numbers:
///
/// ```
/// #![feature(iterator_try_reduce)]
///
/// let numbers: Vec<usize> = vec![10, 20, 5, 23, 0];
/// let sum = numbers.into_iter().try_reduce(|x, y| x.checked_add(y));
/// assert_eq!(sum, Some(Some(58)));
/// ```
///
/// Determine when a reduction short circuited:
///
/// ```
/// #![feature(iterator_try_reduce)]
///
/// let numbers = vec![1, 2, 3, usize::MAX, 4, 5];
/// let sum = numbers.into_iter().try_reduce(|x, y| x.checked_add(y));
/// assert_eq!(sum, None);
/// ```
///
/// Determine when a reduction was not performed because there are no elements:
///
/// ```
/// #![feature(iterator_try_reduce)]
///
/// let numbers: Vec<usize> = Vec::new();
/// let sum = numbers.into_iter().try_reduce(|x, y| x.checked_add(y));
/// assert_eq!(sum, Some(None));
/// ```
///
/// Use a [`Result`] instead of an [`Option`]:
///
/// ```
/// #![feature(iterator_try_reduce)]
///
/// let numbers = vec!["1", "2", "3", "4", "5"];
/// let max: Result<Option<_>, <usize as std::str::FromStr>::Err> =
/// numbers.into_iter().try_reduce(|x, y| {
/// if x.parse::<usize>()? > y.parse::<usize>()? { Ok(x) } else { Ok(y) }
/// });
/// assert_eq!(max, Ok(Some("5")));
/// ```
#[inline]
#[unstable(feature = "iterator_try_reduce", reason = "new API", issue = "87053")]
fn try_reduce<F, R>(&mut self, f: F) -> ChangeOutputType<R, Option<R::Output>>
where
Self: Sized,
F: FnMut(Self::Item, Self::Item) -> R,
R: Try<Output = Self::Item>,
R::Residual: Residual<Option<Self::Item>>,
{
let first = match self.next() {
Some(i) => i,
None => return Try::from_output(None),
};

match self.try_fold(first, f).branch() {
ControlFlow::Break(r) => FromResidual::from_residual(r),
ControlFlow::Continue(i) => Try::from_output(Some(i)),
}
}

/// Tests if every element of the iterator matches a predicate.
///
/// `all()` takes a closure that returns `true` or `false`. It applies
Expand Down
28 changes: 28 additions & 0 deletions library/core/tests/iter/traits/iterator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,34 @@ fn test_find_map() {
}
}

#[test]
fn test_try_reduce() {
let v: Vec<usize> = vec![1, 2, 3, 4, 5];
let sum = v.into_iter().try_reduce(|x, y| x.checked_add(y));
assert_eq!(sum, Some(Some(15)));

let v: Vec<usize> = vec![1, 2, 3, 4, 5, usize::MAX];
let sum = v.into_iter().try_reduce(|x, y| x.checked_add(y));
assert_eq!(sum, None);

let v: Vec<usize> = Vec::new();
let sum = v.into_iter().try_reduce(|x, y| x.checked_add(y));
assert_eq!(sum, Some(None));

let v = vec!["1", "2", "3", "4", "5"];
let max = v.into_iter().try_reduce(|x, y| {
if x.parse::<usize>().ok()? > y.parse::<usize>().ok()? { Some(x) } else { Some(y) }
});
assert_eq!(max, Some(Some("5")));

let v = vec!["1", "2", "3", "4", "5"];
let max: Result<Option<_>, <usize as std::str::FromStr>::Err> =
v.into_iter().try_reduce(|x, y| {
if x.parse::<usize>()? > y.parse::<usize>()? { Ok(x) } else { Ok(y) }
});
assert_eq!(max, Ok(Some("5")));
}

#[test]
fn test_iterator_len() {
let v: &[_] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
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 @@ -56,6 +56,7 @@
#![feature(iter_intersperse)]
#![feature(iter_is_partitioned)]
#![feature(iter_order_by)]
#![feature(iterator_try_reduce)]
#![feature(const_mut_refs)]
#![feature(const_pin)]
#![feature(const_slice_from_raw_parts)]
Expand Down

0 comments on commit c223a1c

Please sign in to comment.