Skip to content

Commit 0c5e398

Browse files
committed
Add Peekable::until
Useful to parse since you can read from an iterator until a specific predicate becomes true, without consuming the positive item.
1 parent cbe7c5c commit 0c5e398

File tree

2 files changed

+144
-6
lines changed

2 files changed

+144
-6
lines changed

library/core/src/iter/adapters/mod.rs

Lines changed: 123 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -679,7 +679,11 @@ where
679679
fn next_back_index(&self) -> usize {
680680
let rem = self.iter.len() % (self.step + 1);
681681
if self.first_take {
682-
if rem == 0 { self.step } else { rem - 1 }
682+
if rem == 0 {
683+
self.step
684+
} else {
685+
rem - 1
686+
}
683687
} else {
684688
rem
685689
}
@@ -1687,6 +1691,109 @@ impl<I: Iterator> Peekable<I> {
16871691
{
16881692
self.next_if(|next| next == expected)
16891693
}
1694+
1695+
/// Creates an iterator that consumes elements until predicate is
1696+
/// true, without consuming that last element.
1697+
///
1698+
/// `until()` takes a closure as an argument. It will call this
1699+
/// closure on each element of the iterator, and consume elements
1700+
/// until it returns true.
1701+
///
1702+
/// After true is returned, until()'s job is over, and the iterator
1703+
/// is fused.
1704+
///
1705+
/// This is the exact opposite of [`skip_while`].
1706+
///
1707+
/// # Example
1708+
/// Consume numbers until you find a '5'.
1709+
/// ```
1710+
/// #![feature(peekable_next_if)]
1711+
/// let mut iter = (0..10).peekable();
1712+
/// assert_eq!(iter.until(|&x| x == 5).collect::<String>(), "1234".to_string());
1713+
/// ```
1714+
/// [`skip_while`]: trait.Iterator.html#method.skip_while
1715+
#[unstable(feature = "peekable_next_if", issue = "72480")]
1716+
pub fn until<P: FnMut(&I::Item) -> bool>(&mut self, func: P) -> Until<'_, I, P> {
1717+
Until::new(self, func)
1718+
}
1719+
}
1720+
1721+
/// An iterator that iterates over elements until `predicate` returns `false`.
1722+
///
1723+
/// This `struct` is created by the [`until`] method on [`Peekable`]. See its
1724+
/// documentation for more.
1725+
///
1726+
/// [`until`]: trait.Peekable.html#until
1727+
/// [`Iterator`]: trait.Iterator.html
1728+
#[must_use = "iterators are lazy and do nothing unless consumed"]
1729+
#[unstable(feature = "peekable_next_if", issue = "72480")]
1730+
pub struct Until<'a, I, P>
1731+
where
1732+
I: Iterator,
1733+
{
1734+
peekable: &'a mut Peekable<I>,
1735+
flag: bool,
1736+
predicate: P,
1737+
}
1738+
impl<'a, I, P> Until<'a, I, P>
1739+
where
1740+
I: Iterator,
1741+
{
1742+
fn new(peekable: &'a mut Peekable<I>, predicate: P) -> Self {
1743+
Until { peekable, flag: false, predicate }
1744+
}
1745+
}
1746+
1747+
#[stable(feature = "core_impl_debug", since = "1.9.0")]
1748+
impl<I, P> fmt::Debug for Until<'_, I, P>
1749+
where
1750+
I: fmt::Debug + Iterator,
1751+
I::Item: fmt::Debug,
1752+
{
1753+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1754+
f.debug_struct("Until").field("peekable", self.peekable).field("flag", &self.flag).finish()
1755+
}
1756+
}
1757+
1758+
#[unstable(feature = "peekable_next_if", issue = "72480")]
1759+
impl<I: Iterator, P> Iterator for Until<'_, I, P>
1760+
where
1761+
P: FnMut(&I::Item) -> bool,
1762+
{
1763+
type Item = I::Item;
1764+
1765+
#[inline]
1766+
fn next(&mut self) -> Option<I::Item> {
1767+
if self.flag {
1768+
return None;
1769+
}
1770+
match self.peekable.peek() {
1771+
Some(matched) => {
1772+
if (self.predicate)(&matched) {
1773+
// matching value is not consumed.
1774+
self.flag = true;
1775+
None
1776+
} else {
1777+
self.peekable.next()
1778+
}
1779+
}
1780+
None => None,
1781+
}
1782+
}
1783+
1784+
#[inline]
1785+
fn size_hint(&self) -> (usize, Option<usize>) {
1786+
let (_, upper) = self.peekable.size_hint();
1787+
(0, upper) // can't know a lower bound, due to the predicate
1788+
}
1789+
}
1790+
1791+
#[stable(feature = "fused", since = "1.26.0")]
1792+
impl<I, P> FusedIterator for Until<'_, I, P>
1793+
where
1794+
I: FusedIterator,
1795+
P: FnMut(&I::Item) -> bool,
1796+
{
16901797
}
16911798

16921799
/// An iterator that rejects elements while `predicate` returns `true`.
@@ -2106,7 +2213,11 @@ where
21062213
I: DoubleEndedIterator + ExactSizeIterator,
21072214
{
21082215
fn next_back(&mut self) -> Option<Self::Item> {
2109-
if self.len() > 0 { self.iter.next_back() } else { None }
2216+
if self.len() > 0 {
2217+
self.iter.next_back()
2218+
} else {
2219+
None
2220+
}
21102221
}
21112222

21122223
#[inline]
@@ -2136,7 +2247,11 @@ where
21362247
move |acc, x| {
21372248
n -= 1;
21382249
let r = fold(acc, x);
2139-
if n == 0 { LoopState::Break(r) } else { LoopState::from_try(r) }
2250+
if n == 0 {
2251+
LoopState::Break(r)
2252+
} else {
2253+
LoopState::from_try(r)
2254+
}
21402255
}
21412256
}
21422257

@@ -2247,7 +2362,11 @@ where
22472362
move |acc, x| {
22482363
*n -= 1;
22492364
let r = fold(acc, x);
2250-
if *n == 0 { LoopState::Break(r) } else { LoopState::from_try(r) }
2365+
if *n == 0 {
2366+
LoopState::Break(r)
2367+
} else {
2368+
LoopState::from_try(r)
2369+
}
22512370
}
22522371
}
22532372

library/core/tests/iter.rs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,11 @@ impl Iterator for Toggle {
226226
}
227227

228228
fn size_hint(&self) -> (usize, Option<usize>) {
229-
if self.is_empty { (0, Some(0)) } else { (1, Some(1)) }
229+
if self.is_empty {
230+
(0, Some(0))
231+
} else {
232+
(1, Some(1))
233+
}
230234
}
231235
}
232236

@@ -837,6 +841,17 @@ fn test_iterator_peekable_next_if_eq() {
837841
assert_eq!(it.next_if_eq(""), None);
838842
}
839843

844+
#[test]
845+
fn test_until_iter() {
846+
let xs = "Heart of Gold";
847+
let mut it = xs.chars().peekable();
848+
{
849+
let until = it.until(|&c| c == ' ');
850+
assert_eq!(until.collect::<String>(), "Heart".to_string());
851+
}
852+
assert_eq!(it.collect::<String>(), " of Gold".to_string());
853+
}
854+
840855
/// This is an iterator that follows the Iterator contract,
841856
/// but it is not fused. After having returned None once, it will start
842857
/// producing elements if .next() is called again.
@@ -1592,7 +1607,11 @@ fn test_find_map() {
15921607
assert_eq!(iter.next(), Some(&7));
15931608

15941609
fn half_if_even(x: &isize) -> Option<isize> {
1595-
if x % 2 == 0 { Some(x / 2) } else { None }
1610+
if x % 2 == 0 {
1611+
Some(x / 2)
1612+
} else {
1613+
None
1614+
}
15961615
}
15971616
}
15981617

0 commit comments

Comments
 (0)