Skip to content

Commit d081998

Browse files
Merge #303
303: Introduce peek_nth r=jswrenn a=davidcornu I've been implementing Bob Nystrom's [Crafting Interpreters](https://www.craftinginterpreters.com) in Rust as a way to learn the language and stumbled upon [`multipeek`](https://docs.rs/itertools/0.7.8/itertools/fn.multipeek.html) while looking for a way to `peek` more than one value ahead (as is required in order to [tokenize number literals](http://www.craftinginterpreters.com/scanning.html#number-literals)). `multipeek` seemed like a great solution to this but I find it's tricky to use correctly. Because it makes `peek` stateful, you have to be careful to call [`reset_peek`](https://docs.rs/itertools/0.7.8/itertools/structs/struct.MultiPeek.html#method.reset_peek) whenever handing control of the iterator to another part of the code or risk running into bugs when `peek` doesn't return what you expect. To work around this I implemented `peek_nth` which allows you to `peek` more than one iteration into the future without relying on a cursor. I have a working version in my project but I figured I'd submit it here for discussion. **Example usage** ```rust let xs = vec![1,2,3]; let mut iter = peek_nth(xs.iter()); assert_eq!(iter.peek(), Some(&&1)); assert_eq!(iter.next(), Some(&1)); // The iterator does not advance even if we call `peek_nth` multiple times assert_eq!(iter.peek_nth(0), Some(&&2)); assert_eq!(iter.peek_nth(1), Some(&&3)); assert_eq!(iter.next(), Some(&2)); // Calling `peek_nth` past the end of the iterator will return `None` assert_eq!(iter.peek_nth(1), None); ``` Note - `peek` equivalent to `peek_nth(0)` and is included for convenience. ---- ⚠️ I'm _very_ new to Rust and took a lot of inspiration from`multipeek` to implement this. I'm more than willing to rewrite/fix any mistakes I may have made 😅. Co-authored-by: David Cornu <[email protected]>
2 parents 398fd38 + 3529cab commit d081998

File tree

5 files changed

+183
-0
lines changed

5 files changed

+183
-0
lines changed

src/free.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ pub use crate::put_back_n_impl::put_back_n;
2222
#[cfg(feature = "use_std")]
2323
pub use crate::multipeek_impl::multipeek;
2424
#[cfg(feature = "use_std")]
25+
pub use crate::peek_nth::peek_nth;
26+
#[cfg(feature = "use_std")]
2527
pub use crate::kmerge_impl::kmerge;
2628
pub use crate::zip_eq_impl::zip_eq;
2729
pub use crate::merge_join::merge_join_by;

src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,8 @@ pub mod structs {
113113
pub use crate::merge_join::MergeJoinBy;
114114
#[cfg(feature = "use_std")]
115115
pub use crate::multipeek_impl::MultiPeek;
116+
#[cfg(feature = "use_std")]
117+
pub use crate::peek_nth::PeekNth;
116118
pub use crate::pad_tail::PadUsing;
117119
pub use crate::peeking_take_while::PeekingTakeWhile;
118120
#[cfg(feature = "use_std")]
@@ -187,6 +189,8 @@ mod minmax;
187189
#[cfg(feature = "use_std")]
188190
mod multipeek_impl;
189191
mod pad_tail;
192+
#[cfg(feature = "use_std")]
193+
mod peek_nth;
190194
mod peeking_take_while;
191195
#[cfg(feature = "use_std")]
192196
mod permutations;

src/peek_nth.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
use crate::size_hint;
2+
use crate::PeekingNext;
3+
use std::collections::VecDeque;
4+
use std::iter::Fuse;
5+
6+
/// See [`peek_nth()`](../fn.peek_nth.html) for more information.
7+
#[derive(Clone, Debug)]
8+
pub struct PeekNth<I>
9+
where
10+
I: Iterator,
11+
{
12+
iter: Fuse<I>,
13+
buf: VecDeque<I::Item>,
14+
}
15+
16+
/// A drop-in replacement for `std::iter::Peekable` which adds a `peek_nth`
17+
/// method allowing the user to `peek` at a value several iterations forward
18+
/// without advancing the base iterator.
19+
///
20+
/// This differs from `multipeek` in that subsequent calls to `peek` or
21+
/// `peek_nth` will always return the same value until `next` is called
22+
/// (making `reset_peek` unnecessary).
23+
pub fn peek_nth<I>(iterable: I) -> PeekNth<I::IntoIter>
24+
where
25+
I: IntoIterator,
26+
{
27+
PeekNth {
28+
iter: iterable.into_iter().fuse(),
29+
buf: VecDeque::new(),
30+
}
31+
}
32+
33+
impl<I> PeekNth<I>
34+
where
35+
I: Iterator,
36+
{
37+
/// Works exactly like the `peek` method in `std::iter::Peekable`
38+
pub fn peek(&mut self) -> Option<&I::Item> {
39+
self.peek_nth(0)
40+
}
41+
42+
/// Returns a reference to the `nth` value without advancing the iterator.
43+
///
44+
/// # Examples
45+
///
46+
/// Basic usage:
47+
///
48+
/// ```rust
49+
/// use itertools::peek_nth;
50+
///
51+
/// let xs = vec![1,2,3];
52+
/// let mut iter = peek_nth(xs.iter());
53+
///
54+
/// assert_eq!(iter.peek_nth(0), Some(&&1));
55+
/// assert_eq!(iter.next(), Some(&1));
56+
///
57+
/// // The iterator does not advance even if we call `peek_nth` multiple times
58+
/// assert_eq!(iter.peek_nth(0), Some(&&2));
59+
/// assert_eq!(iter.peek_nth(1), Some(&&3));
60+
/// assert_eq!(iter.next(), Some(&2));
61+
///
62+
/// // Calling `peek_nth` past the end of the iterator will return `None`
63+
/// assert_eq!(iter.peek_nth(1), None);
64+
/// ```
65+
pub fn peek_nth(&mut self, n: usize) -> Option<&I::Item> {
66+
let unbuffered_items = (n + 1).saturating_sub(self.buf.len());
67+
68+
self.buf.extend(self.iter.by_ref().take(unbuffered_items));
69+
70+
self.buf.get(n)
71+
}
72+
}
73+
74+
impl<I> Iterator for PeekNth<I>
75+
where
76+
I: Iterator,
77+
{
78+
type Item = I::Item;
79+
80+
fn next(&mut self) -> Option<I::Item> {
81+
self.buf.pop_front().or_else(|| self.iter.next())
82+
}
83+
84+
fn size_hint(&self) -> (usize, Option<usize>) {
85+
size_hint::add_scalar(self.iter.size_hint(), self.buf.len())
86+
}
87+
}
88+
89+
impl<I> ExactSizeIterator for PeekNth<I> where I: ExactSizeIterator {}
90+
91+
impl<I> PeekingNext for PeekNth<I>
92+
where
93+
I: Iterator,
94+
{
95+
fn peeking_next<F>(&mut self, accept: F) -> Option<Self::Item>
96+
where
97+
F: FnOnce(&Self::Item) -> bool,
98+
{
99+
self.peek().filter(|item| accept(item))?;
100+
self.next()
101+
}
102+
}

tests/quick.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use itertools::free::{
1919
cloned,
2020
enumerate,
2121
multipeek,
22+
peek_nth,
2223
put_back,
2324
put_back_n,
2425
rciter,
@@ -487,6 +488,15 @@ quickcheck! {
487488
exact_size(it)
488489
}
489490

491+
fn size_peek_nth(a: Iter<u16, Exact>, s: u8) -> bool {
492+
let mut it = peek_nth(a);
493+
// peek a few times
494+
for n in 0..s {
495+
it.peek_nth(n as usize);
496+
}
497+
exact_size(it)
498+
}
499+
490500
fn equal_merge(a: Vec<i16>, b: Vec<i16>) -> bool {
491501
let mut sa = a.clone();
492502
let mut sb = b.clone();

tests/test_std.rs

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use itertools as it;
33
use crate::it::Itertools;
44
use crate::it::multizip;
55
use crate::it::multipeek;
6+
use crate::it::peek_nth;
67
use crate::it::free::rciter;
78
use crate::it::free::put_back_n;
89
use crate::it::FoldWhile;
@@ -371,6 +372,70 @@ fn test_multipeek_peeking_next() {
371372
assert_eq!(mp.peek(), None);
372373
}
373374

375+
#[test]
376+
fn test_peek_nth() {
377+
let nums = vec![1u8,2,3,4,5];
378+
379+
let iter = peek_nth(nums.iter().map(|&x| x));
380+
assert_eq!(nums, iter.collect::<Vec<_>>());
381+
382+
let mut iter = peek_nth(nums.iter().map(|&x| x));
383+
384+
assert_eq!(iter.peek_nth(0), Some(&1));
385+
assert_eq!(iter.peek_nth(0), Some(&1));
386+
assert_eq!(iter.next(), Some(1));
387+
388+
assert_eq!(iter.peek_nth(0), Some(&2));
389+
assert_eq!(iter.peek_nth(1), Some(&3));
390+
assert_eq!(iter.next(), Some(2));
391+
392+
assert_eq!(iter.peek_nth(0), Some(&3));
393+
assert_eq!(iter.peek_nth(1), Some(&4));
394+
assert_eq!(iter.peek_nth(2), Some(&5));
395+
assert_eq!(iter.peek_nth(3), None);
396+
397+
assert_eq!(iter.next(), Some(3));
398+
assert_eq!(iter.next(), Some(4));
399+
400+
assert_eq!(iter.peek_nth(0), Some(&5));
401+
assert_eq!(iter.peek_nth(1), None);
402+
assert_eq!(iter.next(), Some(5));
403+
assert_eq!(iter.next(), None);
404+
405+
assert_eq!(iter.peek_nth(0), None);
406+
assert_eq!(iter.peek_nth(1), None);
407+
}
408+
409+
#[test]
410+
fn test_peek_nth_peeking_next() {
411+
use it::PeekingNext;
412+
let nums = vec![1u8,2,3,4,5,6,7];
413+
let mut iter = peek_nth(nums.iter().map(|&x| x));
414+
415+
assert_eq!(iter.peeking_next(|&x| x != 0), Some(1));
416+
assert_eq!(iter.next(), Some(2));
417+
418+
assert_eq!(iter.peek_nth(0), Some(&3));
419+
assert_eq!(iter.peek_nth(1), Some(&4));
420+
assert_eq!(iter.peeking_next(|&x| x == 3), Some(3));
421+
assert_eq!(iter.peek(), Some(&4));
422+
423+
assert_eq!(iter.peeking_next(|&x| x != 4), None);
424+
assert_eq!(iter.peeking_next(|&x| x == 4), Some(4));
425+
assert_eq!(iter.peek_nth(0), Some(&5));
426+
assert_eq!(iter.peek_nth(1), Some(&6));
427+
428+
assert_eq!(iter.peeking_next(|&x| x != 5), None);
429+
assert_eq!(iter.peek(), Some(&5));
430+
431+
assert_eq!(iter.peeking_next(|&x| x == 5), Some(5));
432+
assert_eq!(iter.peeking_next(|&x| x == 6), Some(6));
433+
assert_eq!(iter.peek_nth(0), Some(&7));
434+
assert_eq!(iter.peek_nth(1), None);
435+
assert_eq!(iter.next(), Some(7));
436+
assert_eq!(iter.peek(), None);
437+
}
438+
374439
#[test]
375440
fn pad_using() {
376441
it::assert_equal((0..0).pad_using(1, |_| 1), 1..2);

0 commit comments

Comments
 (0)