From 0c5e39866de2637d00694b5139c8ae0e4c352ac3 Mon Sep 17 00:00:00 2001 From: Antoine Pelisse Date: Fri, 14 Aug 2020 16:00:06 -0700 Subject: [PATCH 1/4] Add Peekable::until Useful to parse since you can read from an iterator until a specific predicate becomes true, without consuming the positive item. --- library/core/src/iter/adapters/mod.rs | 127 +++++++++++++++++++++++++- library/core/tests/iter.rs | 23 ++++- 2 files changed, 144 insertions(+), 6 deletions(-) diff --git a/library/core/src/iter/adapters/mod.rs b/library/core/src/iter/adapters/mod.rs index 133643a0c7f03..5d3326f8f893a 100644 --- a/library/core/src/iter/adapters/mod.rs +++ b/library/core/src/iter/adapters/mod.rs @@ -679,7 +679,11 @@ where fn next_back_index(&self) -> usize { let rem = self.iter.len() % (self.step + 1); if self.first_take { - if rem == 0 { self.step } else { rem - 1 } + if rem == 0 { + self.step + } else { + rem - 1 + } } else { rem } @@ -1687,6 +1691,109 @@ impl Peekable { { self.next_if(|next| next == expected) } + + /// Creates an iterator that consumes elements until predicate is + /// true, without consuming that last 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()); + /// ``` + /// [`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`. @@ -2106,7 +2213,11 @@ where I: DoubleEndedIterator + ExactSizeIterator, { fn next_back(&mut self) -> Option { - if self.len() > 0 { self.iter.next_back() } else { None } + if self.len() > 0 { + self.iter.next_back() + } else { + None + } } #[inline] @@ -2136,7 +2247,11 @@ where move |acc, x| { n -= 1; let r = fold(acc, x); - if n == 0 { LoopState::Break(r) } else { LoopState::from_try(r) } + if n == 0 { + LoopState::Break(r) + } else { + LoopState::from_try(r) + } } } @@ -2247,7 +2362,11 @@ where move |acc, x| { *n -= 1; let r = fold(acc, x); - if *n == 0 { LoopState::Break(r) } else { LoopState::from_try(r) } + if *n == 0 { + LoopState::Break(r) + } else { + LoopState::from_try(r) + } } } diff --git a/library/core/tests/iter.rs b/library/core/tests/iter.rs index 3b854b56c320d..f137713d17496 100644 --- a/library/core/tests/iter.rs +++ b/library/core/tests/iter.rs @@ -226,7 +226,11 @@ impl Iterator for Toggle { } fn size_hint(&self) -> (usize, Option) { - if self.is_empty { (0, Some(0)) } else { (1, Some(1)) } + if self.is_empty { + (0, Some(0)) + } else { + (1, Some(1)) + } } } @@ -837,6 +841,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. @@ -1592,7 +1607,11 @@ fn test_find_map() { assert_eq!(iter.next(), Some(&7)); fn half_if_even(x: &isize) -> Option { - if x % 2 == 0 { Some(x / 2) } else { None } + if x % 2 == 0 { + Some(x / 2) + } else { + None + } } } From 18b7e8a315089916981618367304e3443761c9c3 Mon Sep 17 00:00:00 2001 From: Antoine Pelisse Date: Fri, 14 Aug 2020 16:21:48 -0700 Subject: [PATCH 2/4] Remove bad formatting from wrong fmt version --- library/core/src/iter/adapters/mod.rs | 24 ++++-------------------- library/core/tests/iter.rs | 12 ++---------- 2 files changed, 6 insertions(+), 30 deletions(-) diff --git a/library/core/src/iter/adapters/mod.rs b/library/core/src/iter/adapters/mod.rs index 5d3326f8f893a..7586dbab766c8 100644 --- a/library/core/src/iter/adapters/mod.rs +++ b/library/core/src/iter/adapters/mod.rs @@ -679,11 +679,7 @@ where fn next_back_index(&self) -> usize { let rem = self.iter.len() % (self.step + 1); if self.first_take { - if rem == 0 { - self.step - } else { - rem - 1 - } + if rem == 0 { self.step } else { rem - 1 } } else { rem } @@ -2213,11 +2209,7 @@ where I: DoubleEndedIterator + ExactSizeIterator, { fn next_back(&mut self) -> Option { - if self.len() > 0 { - self.iter.next_back() - } else { - None - } + if self.len() > 0 { self.iter.next_back() } else { None } } #[inline] @@ -2247,11 +2239,7 @@ where move |acc, x| { n -= 1; let r = fold(acc, x); - if n == 0 { - LoopState::Break(r) - } else { - LoopState::from_try(r) - } + if n == 0 { LoopState::Break(r) } else { LoopState::from_try(r) } } } @@ -2362,11 +2350,7 @@ where move |acc, x| { *n -= 1; let r = fold(acc, x); - if *n == 0 { - LoopState::Break(r) - } else { - LoopState::from_try(r) - } + if *n == 0 { LoopState::Break(r) } else { LoopState::from_try(r) } } } diff --git a/library/core/tests/iter.rs b/library/core/tests/iter.rs index f137713d17496..a5b32895891bc 100644 --- a/library/core/tests/iter.rs +++ b/library/core/tests/iter.rs @@ -226,11 +226,7 @@ impl Iterator for Toggle { } fn size_hint(&self) -> (usize, Option) { - if self.is_empty { - (0, Some(0)) - } else { - (1, Some(1)) - } + if self.is_empty { (0, Some(0)) } else { (1, Some(1)) } } } @@ -1607,11 +1603,7 @@ fn test_find_map() { assert_eq!(iter.next(), Some(&7)); fn half_if_even(x: &isize) -> Option { - if x % 2 == 0 { - Some(x / 2) - } else { - None - } + if x % 2 == 0 { Some(x / 2) } else { None } } } From 9c84d118d788561a78555fc0ccdea51a26c311d5 Mon Sep 17 00:00:00 2001 From: Antoine Pelisse Date: Sat, 15 Aug 2020 08:25:08 -0700 Subject: [PATCH 3/4] Update library/core/src/iter/adapters/mod.rs Co-authored-by: Lonami --- library/core/src/iter/adapters/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/library/core/src/iter/adapters/mod.rs b/library/core/src/iter/adapters/mod.rs index 7586dbab766c8..7f8a441f018ea 100644 --- a/library/core/src/iter/adapters/mod.rs +++ b/library/core/src/iter/adapters/mod.rs @@ -1706,6 +1706,7 @@ impl Peekable { /// #![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")] From 2dc8bff6f0e7bbf2af7ef1f06d20a29314878f5a Mon Sep 17 00:00:00 2001 From: Antoine Pelisse Date: Sat, 15 Aug 2020 08:26:47 -0700 Subject: [PATCH 4/4] Update library/core/src/iter/adapters/mod.rs Co-authored-by: Lonami --- library/core/src/iter/adapters/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/iter/adapters/mod.rs b/library/core/src/iter/adapters/mod.rs index 7f8a441f018ea..e242c0c8385c6 100644 --- a/library/core/src/iter/adapters/mod.rs +++ b/library/core/src/iter/adapters/mod.rs @@ -1689,7 +1689,7 @@ impl Peekable { } /// Creates an iterator that consumes elements until predicate is - /// true, without consuming that last element. + /// 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