diff options
| author | kit <kit@hastur.io> | 2021-07-11 15:43:30 +1000 |
|---|---|---|
| committer | kit <kit@hastur.io> | 2021-12-04 15:17:14 +1100 |
| commit | aef59e4fb87fe8c79d3c5d6e7164c02c84fe2b8b (patch) | |
| tree | 5028209a930f9327ba270218818fca8302399b2e | |
| parent | ff2439b7b9bafcfdff86b7847128014699df8442 (diff) | |
| download | rust-aef59e4fb87fe8c79d3c5d6e7164c02c84fe2b8b.tar.gz rust-aef59e4fb87fe8c79d3c5d6e7164c02c84fe2b8b.zip | |
Add a `try_reduce` method to the Iterator trait
| -rw-r--r-- | library/core/src/iter/traits/iterator.rs | 80 | ||||
| -rw-r--r-- | library/core/tests/iter/traits/iterator.rs | 28 | ||||
| -rw-r--r-- | library/core/tests/lib.rs | 1 |
3 files changed, 109 insertions, 0 deletions
diff --git a/library/core/src/iter/traits/iterator.rs b/library/core/src/iter/traits/iterator.rs index 88e7623eba1..267fa406798 100644 --- a/library/core/src/iter/traits/iterator.rs +++ b/library/core/src/iter/traits/iterator.rs @@ -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 diff --git a/library/core/tests/iter/traits/iterator.rs b/library/core/tests/iter/traits/iterator.rs index 422e389e380..d38bca1e3b3 100644 --- a/library/core/tests/iter/traits/iterator.rs +++ b/library/core/tests/iter/traits/iterator.rs @@ -455,6 +455,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]; assert_eq!(v[..4].iter().count(), 4); diff --git a/library/core/tests/lib.rs b/library/core/tests/lib.rs index d49c2552cfd..9ab98ba8886 100644 --- a/library/core/tests/lib.rs +++ b/library/core/tests/lib.rs @@ -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)] |
