Skip to content

fold_first iterator consumer #106348

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
65 changes: 65 additions & 0 deletions library/core/src/iter/traits/double_ended.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,71 @@ pub trait DoubleEndedIterator: Iterator {
accum
}

/// Folds every element into an accumulator by applying an operation,
/// returning the final result, starting from the back.
/// The initial value is derived from the last element using the provided method.
///
/// This is the reverse version of [`Iterator::fold_first()`]: it takes elements
/// starting from the back of the iterator.
///
/// If the iterator is empty, returns [`None`]; otherwise, returns the
/// result of the fold.
///
/// The folding function is a closure with two arguments: an 'accumulator', and an element.
///
/// # Example
///
/// ```
/// #![feature(iterator_rfold_last)]
///
/// let numbers = [1, 2, 3, 4, 5];
///
/// let result = numbers.iter().rfold_last(
/// |last| last.to_string(),
/// |acc, &x| format!("({x} + {acc})"),
/// ).unwrap();
///
/// assert_eq!(result, "(1 + (2 + (3 + (4 + 5))))");
/// ```
#[inline]
#[unstable(feature = "iterator_rfold_last", reason = "new API", issue = "none")]
fn rfold_last<B, F1, FR>(mut self, init: F1, folding: FR) -> Option<B>
where
Self: Sized,
F1: FnOnce(Self::Item) -> B,
FR: FnMut(B, Self::Item) -> B,
{
let last = init(self.next_back()?);
Some(self.rfold(last, folding))
}

/// This is the reverse version of [`Iterator::try_fold_first()`]: it takes
/// elements starting from the back of the iterator.
#[inline]
#[unstable(feature = "iterator_try_rfold_last", reason = "new API", issue = "none")]
fn try_rfold_last<F1, FR, R>(
&mut self,
init: F1,
folding: FR,
) -> ChangeOutputType<R, Option<R::Output>>
where
Self: Sized,
F1: FnOnce(Self::Item) -> R,
FR: FnMut(R::Output, Self::Item) -> R,
R: Try,
R::Residual: Residual<Option<R::Output>>,
{
let last = match self.next_back() {
Some(i) => init(i)?,
None => return Try::from_output(None),
};

match self.try_rfold(last, folding).branch() {
ControlFlow::Break(r) => FromResidual::from_residual(r),
ControlFlow::Continue(i) => Try::from_output(Some(i)),
}
}

/// Searches for an element of an iterator from the back that satisfies a predicate.
///
/// `rfind()` takes a closure that returns `true` or `false`. It applies
Expand Down
124 changes: 119 additions & 5 deletions library/core/src/iter/traits/iterator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2448,13 +2448,12 @@ pub trait Iterator {
/// ```
#[inline]
#[stable(feature = "iterator_fold_self", since = "1.51.0")]
fn reduce<F>(mut self, f: F) -> Option<Self::Item>
fn reduce<F>(self, f: F) -> Option<Self::Item>
where
Self: Sized,
F: FnMut(Self::Item, Self::Item) -> Self::Item,
{
let first = self.next()?;
Some(self.fold(first, f))
self.fold_first(core::convert::identity, f)
}

/// Reduces the elements to a single one by repeatedly applying a reducing operation. If the
Expand Down Expand Up @@ -2525,13 +2524,128 @@ pub trait Iterator {
F: FnMut(Self::Item, Self::Item) -> R,
R: Try<Output = Self::Item>,
R::Residual: Residual<Option<Self::Item>>,
{
self.try_fold_first(Try::from_output, f)
}

/// Folds every element into an accumulator by applying an operation,
/// returning the final result. The initial value is derived from the
/// first element using the provided method.
///
/// If the iterator is empty, returns [`None`]; otherwise, returns the
/// result of the fold.
///
/// The folding function is a closure with two arguments: an 'accumulator', and an element.
/// For iterators with at least one element, this is the same as [`reduce()`]
/// with the first element being fed into the init function
///
/// [`reduce()`]: Iterator::reduce
///
/// # Example
///
/// ```
/// #![feature(iterator_fold_first)]
///
/// let min_max: (i32, i32) = [3, 1, 4, 1, 5, 9, 2]
/// .into_iter()
/// .fold_first(
/// |first| (first, first),
/// |(min, max), next| (i32::min(min, next), i32::max(max, next)),
/// ).unwrap();
/// assert_eq!(min_max, (1, 9));
///
/// // Which is equivalent to doing it with `fold`:
/// let folded: (i32, i32) = [3, 1, 4, 1, 5, 9, 2]
/// .into_iter()
/// .fold(
/// (i32::MAX, i32::MIN),
/// |(min, max), next| (i32::min(min, next), i32::max(max, next)),
/// );
/// assert_eq!(min_max, folded);
/// ```
#[inline]
#[unstable(feature = "iterator_fold_first", reason = "new API", issue = "none")]
fn fold_first<B, F1, FR>(mut self, init: F1, folding: FR) -> Option<B>
where
Self: Sized,
F1: FnOnce(Self::Item) -> B,
FR: FnMut(B, Self::Item) -> B,
{
let first = init(self.next()?);
Some(self.fold(first, folding))
}

/// Folds every element into an accumulator by applying an operation,
/// returning the final result. The initial value is derived from the
/// first element using the provided method.
///
/// If the closure returns a failure, the failure is propagated back to the caller immediately.
///
/// # Example
///
/// Replaying a series of events from creation
///
/// ```
/// #![feature(iterator_try_fold_first)]
///
/// enum Events {
/// Create,
/// Update,
/// }
///
/// let events = [Events::Create, Events::Update, Events::Update];
/// let replayed_state = events.into_iter()
/// .try_fold_first(
/// |first| match first {
/// Events::Create => Ok(1),
/// _ => Err("only creation event supported at start"),
/// },
/// |state, next| match next {
/// Events::Update => Ok(state + 1),
/// _ => Err("only update events should follow a creation"),
/// },
/// );
/// assert_eq!(replayed_state, Ok(Some(3)));
///
/// // Which is equivalent to doing it with `try_fold`:
/// let events = [Events::Create, Events::Update, Events::Update];
/// let folded = events.into_iter()
/// .try_fold(
/// None,
/// |state, event| {
/// match (state, event) {
/// // init
/// (None, Events::Create) => Ok(Some(1)),
/// (None, Events::Update) => Err("only update events should follow a creation"),
///
/// // fold
/// (Some(state), Events::Update) => Ok(Some(state + 1)),
/// (Some(_), Events::Create) => Err("only creation event supported at start"),
/// }
/// },
/// );
/// assert_eq!(replayed_state, folded);
/// ```
#[inline]
#[unstable(feature = "iterator_try_fold_first", reason = "new API", issue = "none")]
fn try_fold_first<F1, FR, R>(
&mut self,
init: F1,
folding: FR,
) -> ChangeOutputType<R, Option<R::Output>>
where
Self: Sized,
F1: FnOnce(Self::Item) -> R,
FR: FnMut(R::Output, Self::Item) -> R,
R: Try,
R::Residual: Residual<Option<R::Output>>,
{
let first = match self.next() {
Some(i) => i,
Some(i) => init(i)?,
None => return Try::from_output(None),
};

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