Skip to content

Commit

Permalink
Auto merge of #66577 - WaffleLapkin:iter_take_while_map, r=mark-simul…
Browse files Browse the repository at this point in the history
…crum

Add `Iterator::map_while`

In `Iterator` trait there is `*_map` version of [`filter`] — [`filter_map`], however, there is no `*_map` version of [`take_while`], that can also be useful.

### Use cases
In my code, I've found that I need to iterate through iterator of `Option`s, stopping on the first `None`. So I've written code like this:
```rust
let arr = [Some(4), Some(10), None, Some(3)];
let mut iter = arr.iter()
    .take_while(|x| x.is_some())
    .map(|x| x.unwrap());

assert_eq!(iter.next(), Some(4));
assert_eq!(iter.next(), Some(10));
assert_eq!(iter.next(), None);
assert_eq!(iter.next(), None);
```
Thit code
1) isn't clean
2) In theory, can generate bad bytecode (I'm actually **not** sure, but I think that `unwrap` would generate additional branches with `panic!`)

The same code, but with `map_while` (in the original PR message it was named "take_while_map"):
```rust
let arr = [Some(4), Some(10), None, Some(3)];
let mut iter = arr.iter().map_while(std::convert::identity);
```

Also, `map_while` can be useful when converting something (as in [examples]).

[`filter`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.filter
[`filter_map`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.filter_map
[`take_while`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.take_while
[examples]: https://github.com/rust-lang/rust/compare/master...WaffleLapkin:iter_take_while_map?expand=1#diff-7e57917f962fe6ffdfba51e4955ad6acR1042
  • Loading branch information
bors committed Jan 30, 2020
2 parents 212b2c7 + db1a107 commit 34700c1
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 1 deletion.
89 changes: 89 additions & 0 deletions src/libcore/iter/adapters/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1752,6 +1752,95 @@ where
}
}

/// An iterator that only accepts elements while `predicate` returns `Some(_)`.
///
/// This `struct` is created by the [`map_while`] method on [`Iterator`]. See its
/// documentation for more.
///
/// [`map_while`]: trait.Iterator.html#method.map_while
/// [`Iterator`]: trait.Iterator.html
#[must_use = "iterators are lazy and do nothing unless consumed"]
#[unstable(feature = "iter_map_while", reason = "recently added", issue = "68537")]
#[derive(Clone)]
pub struct MapWhile<I, P> {
iter: I,
finished: bool,
predicate: P,
}

impl<I, P> MapWhile<I, P> {
pub(super) fn new(iter: I, predicate: P) -> MapWhile<I, P> {
MapWhile { iter, finished: false, predicate }
}
}

#[unstable(feature = "iter_map_while", reason = "recently added", issue = "68537")]
impl<I: fmt::Debug, P> fmt::Debug for MapWhile<I, P> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MapWhile").field("iter", &self.iter).field("flag", &self.finished).finish()
}
}

#[unstable(feature = "iter_map_while", reason = "recently added", issue = "68537")]
impl<B, I: Iterator, P> Iterator for MapWhile<I, P>
where
P: FnMut(I::Item) -> Option<B>,
{
type Item = B;

#[inline]
fn next(&mut self) -> Option<B> {
if self.finished {
None
} else {
let x = self.iter.next()?;
let ret = (self.predicate)(x);
self.finished = ret.is_none();
ret
}
}

#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
if self.finished {
(0, Some(0))
} else {
let (_, upper) = self.iter.size_hint();
(0, upper) // can't know a lower bound, due to the predicate
}
}

#[inline]
fn try_fold<Acc, Fold, R>(&mut self, init: Acc, fold: Fold) -> R
where
Self: Sized,
Fold: FnMut(Acc, Self::Item) -> R,
R: Try<Ok = Acc>,
{
fn check<'a, B, T, Acc, R: Try<Ok = Acc>>(
flag: &'a mut bool,
p: &'a mut impl FnMut(T) -> Option<B>,
mut fold: impl FnMut(Acc, B) -> R + 'a,
) -> impl FnMut(Acc, T) -> LoopState<Acc, R> + 'a {
move |acc, x| match p(x) {
Some(item) => LoopState::from_try(fold(acc, item)),
None => {
*flag = true;
LoopState::Break(Try::from_ok(acc))
}
}
}

if self.finished {
Try::from_ok(init)
} else {
let flag = &mut self.finished;
let p = &mut self.predicate;
self.iter.try_fold(init, check(flag, p, fold)).into_try()
}
}
}

#[stable(feature = "fused", since = "1.26.0")]
impl<I, P> FusedIterator for TakeWhile<I, P>
where
Expand Down
2 changes: 2 additions & 0 deletions src/libcore/iter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,8 @@ pub use self::adapters::Cloned;
pub use self::adapters::Copied;
#[stable(feature = "iterator_flatten", since = "1.29.0")]
pub use self::adapters::Flatten;
#[unstable(feature = "iter_map_while", reason = "recently added", issue = "68537")]
pub use self::adapters::MapWhile;
#[stable(feature = "iterator_step_by", since = "1.28.0")]
pub use self::adapters::StepBy;
#[stable(feature = "rust1", since = "1.0.0")]
Expand Down
102 changes: 101 additions & 1 deletion src/libcore/iter/traits/iterator.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// ignore-tidy-filelength
// This file almost exclusively consists of the definition of `Iterator`. We
// can't split that into multiple files.

use crate::cmp::{self, Ordering};
use crate::ops::{Add, Try};
Expand All @@ -7,7 +9,9 @@ use super::super::LoopState;
use super::super::{Chain, Cloned, Copied, Cycle, Enumerate, Filter, FilterMap, Fuse};
use super::super::{FlatMap, Flatten};
use super::super::{FromIterator, Product, Sum, Zip};
use super::super::{Inspect, Map, Peekable, Rev, Scan, Skip, SkipWhile, StepBy, Take, TakeWhile};
use super::super::{
Inspect, Map, MapWhile, Peekable, Rev, Scan, Skip, SkipWhile, StepBy, Take, TakeWhile,
};

fn _assert_is_object_safe(_: &dyn Iterator<Item = ()>) {}

Expand Down Expand Up @@ -1026,6 +1030,102 @@ pub trait Iterator {
TakeWhile::new(self, predicate)
}

/// Creates an iterator that both yields elements based on a predicate and maps.
///
/// `map_while()` takes a closure as an argument. It will call this
/// closure on each element of the iterator, and yield elements
/// while it returns [`Some(_)`][`Some`].
///
/// After [`None`] is returned, `map_while()`'s job is over, and the
/// rest of the elements are ignored.
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// #![feature(iter_map_while)]
/// let a = [-1i32, 4, 0, 1];
///
/// let mut iter = a.iter().map_while(|x| 16i32.checked_div(*x));
///
/// assert_eq!(iter.next(), Some(-16));
/// assert_eq!(iter.next(), Some(4));
/// assert_eq!(iter.next(), None);
/// ```
///
/// Here's the same example, but with [`take_while`] and [`map`]:
///
/// [`take_while`]: #method.take_while
/// [`map`]: #method.map
///
/// ```
/// let a = [-1i32, 4, 0, 1];
///
/// let mut iter = a.iter()
/// .map(|x| 16i32.checked_div(*x))
/// .take_while(|x| x.is_some())
/// .map(|x| x.unwrap());
///
/// assert_eq!(iter.next(), Some(-16));
/// assert_eq!(iter.next(), Some(4));
/// assert_eq!(iter.next(), None);
/// ```
///
/// Stopping after an initial [`None`]:
///
/// ```
/// #![feature(iter_map_while)]
/// use std::convert::TryFrom;
///
/// let a = [0, -1, 1, -2];
///
/// let mut iter = a.iter().map_while(|x| u32::try_from(*x).ok());
///
/// assert_eq!(iter.next(), Some(0u32));
///
/// // We have more elements that are fit in u32, but since we already
/// // got a None, map_while() isn't used any more
/// assert_eq!(iter.next(), None);
/// ```
///
/// Because `map_while()` needs to look at the value in order to see if it
/// should be included or not, consuming iterators will see that it is
/// removed:
///
/// ```
/// #![feature(iter_map_while)]
/// use std::convert::TryFrom;
///
/// let a = [1, 2, -3, 4];
/// let mut iter = a.iter();
///
/// let result: Vec<u32> = iter.by_ref()
/// .map_while(|n| u32::try_from(*n).ok())
/// .collect();
///
/// assert_eq!(result, &[1, 2]);
///
/// let result: Vec<i32> = iter.cloned().collect();
///
/// assert_eq!(result, &[4]);
/// ```
///
/// The `-3` is no longer there, because it was consumed in order to see if
/// the iteration should stop, but wasn't placed back into the iterator.
///
/// [`Some`]: ../../std/option/enum.Option.html#variant.Some
/// [`None`]: ../../std/option/enum.Option.html#variant.None
#[inline]
#[unstable(feature = "iter_map_while", reason = "recently added", issue = "68537")]
fn map_while<B, P>(self, predicate: P) -> MapWhile<Self, P>
where
Self: Sized,
P: FnMut(Self::Item) -> Option<B>,
{
MapWhile::new(self, predicate)
}

/// Creates an iterator that skips the first `n` elements.
///
/// After they have been consumed, the rest of the elements are yielded.
Expand Down
2 changes: 2 additions & 0 deletions src/libcore/tests/iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1477,6 +1477,7 @@ fn test_iterator_size_hint() {
assert_eq!(c.clone().take(5).size_hint(), (5, Some(5)));
assert_eq!(c.clone().skip(5).size_hint().1, None);
assert_eq!(c.clone().take_while(|_| false).size_hint(), (0, None));
assert_eq!(c.clone().map_while(|_| None::<()>).size_hint(), (0, None));
assert_eq!(c.clone().skip_while(|_| false).size_hint(), (0, None));
assert_eq!(c.clone().enumerate().size_hint(), (usize::MAX, None));
assert_eq!(c.clone().chain(vi.clone().cloned()).size_hint(), (usize::MAX, None));
Expand All @@ -1491,6 +1492,7 @@ fn test_iterator_size_hint() {
assert_eq!(vi.clone().skip(3).size_hint(), (7, Some(7)));
assert_eq!(vi.clone().skip(12).size_hint(), (0, Some(0)));
assert_eq!(vi.clone().take_while(|_| false).size_hint(), (0, Some(10)));
assert_eq!(vi.clone().map_while(|_| None::<()>).size_hint(), (0, Some(10)));
assert_eq!(vi.clone().skip_while(|_| false).size_hint(), (0, Some(10)));
assert_eq!(vi.clone().enumerate().size_hint(), (10, Some(10)));
assert_eq!(vi.clone().chain(v2).size_hint(), (13, Some(13)));
Expand Down
1 change: 1 addition & 0 deletions src/libcore/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#![feature(iter_is_partitioned)]
#![feature(iter_order_by)]
#![feature(cmp_min_max_by)]
#![feature(iter_map_while)]
#![feature(const_slice_from_raw_parts)]
#![feature(const_raw_ptr_deref)]
#![feature(never_type)]
Expand Down

0 comments on commit 34700c1

Please sign in to comment.