diff --git a/library/core/src/iter/adapters/mod.rs b/library/core/src/iter/adapters/mod.rs index 133643a0c7f03..e242c0c8385c6 100644 --- a/library/core/src/iter/adapters/mod.rs +++ b/library/core/src/iter/adapters/mod.rs @@ -1687,6 +1687,110 @@ impl Peekable { { self.next_if(|next| next == expected) } + + /// Creates an iterator that consumes elements until predicate is + /// true, without consuming the last matching element. + /// + /// `until()` takes a closure as an argument. It will call this + /// closure on each element of the iterator, and consume elements + /// until it returns true. + /// + /// After true is returned, until()'s job is over, and the iterator + /// is fused. + /// + /// This is the exact opposite of [`skip_while`]. + /// + /// # Example + /// Consume numbers until you find a '5'. + /// ``` + /// #![feature(peekable_next_if)] + /// let mut iter = (0..10).peekable(); + /// assert_eq!(iter.until(|&x| x == 5).collect::(), "1234".to_string()); + /// assert_eq!(iter.next(), Some(5)); + /// ``` + /// [`skip_while`]: trait.Iterator.html#method.skip_while + #[unstable(feature = "peekable_next_if", issue = "72480")] + pub fn until bool>(&mut self, func: P) -> Until<'_, I, P> { + Until::new(self, func) + } +} + +/// An iterator that iterates over elements until `predicate` returns `false`. +/// +/// This `struct` is created by the [`until`] method on [`Peekable`]. See its +/// documentation for more. +/// +/// [`until`]: trait.Peekable.html#until +/// [`Iterator`]: trait.Iterator.html +#[must_use = "iterators are lazy and do nothing unless consumed"] +#[unstable(feature = "peekable_next_if", issue = "72480")] +pub struct Until<'a, I, P> +where + I: Iterator, +{ + peekable: &'a mut Peekable, + flag: bool, + predicate: P, +} +impl<'a, I, P> Until<'a, I, P> +where + I: Iterator, +{ + fn new(peekable: &'a mut Peekable, predicate: P) -> Self { + Until { peekable, flag: false, predicate } + } +} + +#[stable(feature = "core_impl_debug", since = "1.9.0")] +impl fmt::Debug for Until<'_, I, P> +where + I: fmt::Debug + Iterator, + I::Item: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Until").field("peekable", self.peekable).field("flag", &self.flag).finish() + } +} + +#[unstable(feature = "peekable_next_if", issue = "72480")] +impl Iterator for Until<'_, I, P> +where + P: FnMut(&I::Item) -> bool, +{ + type Item = I::Item; + + #[inline] + fn next(&mut self) -> Option { + if self.flag { + return None; + } + match self.peekable.peek() { + Some(matched) => { + if (self.predicate)(&matched) { + // matching value is not consumed. + self.flag = true; + None + } else { + self.peekable.next() + } + } + None => None, + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + let (_, upper) = self.peekable.size_hint(); + (0, upper) // can't know a lower bound, due to the predicate + } +} + +#[stable(feature = "fused", since = "1.26.0")] +impl FusedIterator for Until<'_, I, P> +where + I: FusedIterator, + P: FnMut(&I::Item) -> bool, +{ } /// An iterator that rejects elements while `predicate` returns `true`. diff --git a/library/core/tests/iter.rs b/library/core/tests/iter.rs index 3b854b56c320d..a5b32895891bc 100644 --- a/library/core/tests/iter.rs +++ b/library/core/tests/iter.rs @@ -837,6 +837,17 @@ fn test_iterator_peekable_next_if_eq() { assert_eq!(it.next_if_eq(""), None); } +#[test] +fn test_until_iter() { + let xs = "Heart of Gold"; + let mut it = xs.chars().peekable(); + { + let until = it.until(|&c| c == ' '); + assert_eq!(until.collect::(), "Heart".to_string()); + } + assert_eq!(it.collect::(), " of Gold".to_string()); +} + /// This is an iterator that follows the Iterator contract, /// but it is not fused. After having returned None once, it will start /// producing elements if .next() is called again.