Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: first version of a fully peekable iterator #1

Merged
merged 1 commit into from
Oct 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/target
/Cargo.lock
.DS_Store
.DS_Store
.idea/
4 changes: 1 addition & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,4 @@ description = "Peek forward in an iterator as far as you'd like, memory allowing
documentation = "https://github.com/clintval/fullypeek/README.md"
categories = ["rust-patterns"]
keywords = ["iterator", "peek", "peekable"]

[dev-dependencies]
rstest = "0.18.2"
exclude = [".github/workflows/*", ".gitignore"]
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,25 @@

[![Build Status](https://github.com/clintval/fullypeek/actions/workflows/rust.yml/badge.svg?branch=main)](https://github.com/clintval/fullypeek/actions/workflows/rust.yml)
[![Coverage Status](https://coveralls.io/repos/github/clintval/fullypeek/badge.svg?branch=main)](https://coveralls.io/github/clintval/fullypeek?branch=main)
[![Language](https://img.shields.io/badge/language-rust-a72144.svg)](https://www.rust-lang.org/)
[![Language](https://img.shields.io/badge/language-rust-DEA584.svg)](https://www.rust-lang.org/)

Peek forward in an iterator as far as you'd like, memory allowing!

![El Chorro, Spain](.github/img/cover.jpg)

```rust
...
let mut peekable = vec![1, 2, 3].into_iter().fully_peekable();

assert_eq!(peekable.peek(), Some(&1));
assert_eq!(peekable.peek_many(2), vec!(Some(&1), Some(&2)));

peekable.next();

assert_eq!(peekable.peek(), Some(&2));
assert_eq!(peekable.peek_many(2), vec!(Some(&2), Some(&3)));

peekable.next();

assert_eq!(peekable.peek(), Some(&3));
assert_eq!(peekable.peek_many(2), vec!(Some(&3), None));
```
333 changes: 325 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,334 @@
//! This crate contains an iterator which will allow you to fully peek any number of elements.
#![forbid(unsafe_code)]
//! This crate contains an iterator which allows you to fully peek forward any number of elements.
use std::collections::VecDeque;
use std::iter::FusedIterator;

pub fn fully_peek(left: usize, right: usize) -> usize {
left + right
/// A trait for an interator which allows you to fully peek forward any number of elements.
#[derive(Clone, Debug)]
pub struct FullyPeekableIterator<I: Iterator> {
iter: I,
queue: VecDeque<I::Item>,
}

/// Create a new fully-peekable iterator from an existing iterator.
impl<I: Iterator> FullyPeekableIterator<I> {
fn new(iter: I) -> FullyPeekableIterator<I> {
FullyPeekableIterator {
iter,
queue: VecDeque::new(),
}
}
}

/// Implementation of the typical iterator methods on the fully-peekable iterator.
impl<I: Iterator> Iterator for FullyPeekableIterator<I> {
type Item = I::Item;

/// Returns the next value which may advance the iterator.
#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.queue.pop_front().or_else(|| self.iter.next())
}

/// Returns the bounds on the remaining length of the iterator.
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let peek_len = self.queue.len();
let (lo, hi) = self.iter.size_hint();
let lo = lo.saturating_add(peek_len);
let hi = match hi {
Some(x) => x.checked_add(peek_len),
None => None,
};
(lo, hi)
}
}

// TODO: Implement `DoubleEndedIterator` for `FullyPeekableIterator`?

impl<I: ExactSizeIterator> ExactSizeIterator for FullyPeekableIterator<I> {}

impl<I: FusedIterator> FusedIterator for FullyPeekableIterator<I> {}

impl<I: Iterator> FullyPeekableIterator<I> {
/// Test if the iterator has another element to yield. May advance the underlying iterator.
#[inline]
pub fn has_next(&mut self) -> bool {
self.peek().is_some()
}

/// Peek forward to an arbitrary element without advancing the iterator.
#[inline]
pub fn lift(&mut self, index: usize) -> Option<&I::Item> {
while self.queue.len() < index + 1 {
match self.iter.next() {
Some(item) => self.queue.push_back(item),
None => break,
}
}
self.queue.get(index)
}

/// Peek forward to a range of arbitrary elements without advancing the iterator.
#[inline]
pub fn lift_many(&mut self, start: usize, end: usize) -> Vec<Option<&I::Item>> {
self.lift(end.max(1) - 1); // Ensure we've filled the queue with as many items as necessary.
let mut result = Vec::with_capacity(end - start);
for index in start..end {
result.push(self.queue.get(index));
}
result
}

/// Peek forward to an arbitrary mutable element without advancing the iterator.
#[inline]
pub fn lift_mut(&mut self, index: usize) -> Option<&mut I::Item> {
while self.queue.len() <= index + 1 {
match self.iter.next() {
Some(item) => self.queue.push_back(item),
None => break,
}
}
self.queue.get_mut(index)
}

/// Peek forward to the next element without advancing the iterator.
#[inline]
pub fn peek(&mut self) -> Option<&I::Item> {
self.lift(0)
}

/// Peek forward to a set number of arbitrary elements without advancing the iterator.
#[inline]
pub fn peek_many(&mut self, n: usize) -> Vec<Option<&I::Item>> {
self.lift_many(0, n)
}

/// Peek forward to the next element marking it as mutable without advancing the iterator.
#[inline]
pub fn peek_mut(&mut self) -> Option<&mut I::Item> {
self.lift_mut(0)
}

/// Consume and return the next value of this iterator if a condition is true.
#[inline]
pub fn next_if(&mut self, func: impl FnOnce(&I::Item) -> bool) -> Option<I::Item> {
match self.next() {
Some(matched) if func(&matched) => Some(matched),
Some(other) => {
self.queue.push_front(other);
None
}
None => None,
}
}

/// Consume and return the next item if it is equal to `expected`.
#[inline]
pub fn next_if_eq<T>(&mut self, expected: &T) -> Option<I::Item>
where
T: ?Sized,
I::Item: PartialEq<T>,
{
self.next_if(|next| next == expected)
}
}

/// A trait for an iterator which allows you to fully peek forward any number of elements.
pub trait IntoFullyPeekableIterator<I>
where
I: Iterator,
{
/// Return a fully peekable iterator.
fn fully_peekable(self) -> FullyPeekableIterator<I>;
}

/// Add a fully-peekable iterator implementation to any iterator implicitly.
impl<I, T> IntoFullyPeekableIterator<I> for I
where
I: Iterator<Item = T>,
{
/// Return a fully peekable iterator.
fn fully_peekable(self) -> FullyPeekableIterator<I> {
FullyPeekableIterator::new(self)
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::{FullyPeekableIterator, IntoFullyPeekableIterator};

#[test]
fn the_class_returns_elements_like_an_iterator_when_using_next() {
let iter = vec![1, 2].into_iter();
let mut peekable = FullyPeekableIterator::new(iter);
assert_eq!(peekable.next(), Some(1));
assert_eq!(peekable.next(), Some(2));
assert_eq!(peekable.next(), None);
}

#[test]
fn it_uses_has_next_to_determine_if_there_are_more_elements() {
let iter = vec![1, 2].into_iter();
let mut peekable = FullyPeekableIterator::new(iter);
assert_eq!(peekable.has_next(), true);
assert_eq!(peekable.next(), Some(1));
assert_eq!(peekable.has_next(), true);
assert_eq!(peekable.next(), Some(2));
assert_eq!(peekable.has_next(), false);
assert_eq!(peekable.next(), None);
assert_eq!(peekable.has_next(), false);
}

#[test]
fn it_can_estimate_its_size_using_size_hint() {
let iter = vec![1, 2].into_iter();
let mut peekable = FullyPeekableIterator::new(iter);
assert_eq!(peekable.size_hint(), (2, Some(2)));
assert_eq!(peekable.next(), Some(1));
assert_eq!(peekable.size_hint(), (1, Some(1)));
assert_eq!(peekable.next(), Some(2));
assert_eq!(peekable.size_hint(), (0, Some(0)));
assert_eq!(peekable.next(), None);
assert_eq!(peekable.size_hint(), (0, Some(0)));
}

#[test]
fn it_can_estimate_a_size_even_if_the_iterator_has_no_high() {
struct TestIterator<I: Iterator> {
iter: I,
}

impl<I: Iterator> TestIterator<I> {
fn new(iter: I) -> TestIterator<I> {
TestIterator { iter }
}
}

impl<I: Iterator> Iterator for TestIterator<I> {
type Item = I::Item;

fn next(&mut self) -> Option<Self::Item> {
self.iter.next()
}

fn size_hint(&self) -> (usize, Option<usize>) {
(0, None)
}
}

let iter = vec![1, 2].into_iter();
let mut peekable = FullyPeekableIterator::new(TestIterator::new(iter));
assert_eq!(peekable.size_hint(), (0, None));
assert_eq!(peekable.peek(), Some(&1));
assert_eq!(peekable.lift(0), Some(&1));
assert_eq!(peekable.size_hint(), (1, None));
assert_eq!(peekable.peek_many(3), vec!(Some(&1), Some(&2), None));
assert_eq!(peekable.size_hint(), (2, None));
assert_eq!(peekable.next(), Some(1));
assert_eq!(peekable.size_hint(), (1, None));
assert_eq!(peekable.next(), Some(2));
assert_eq!(peekable.size_hint(), (0, None));
}

#[test]
fn it_can_lift_elements_without_advancing() {
let iter = vec![1, 2].into_iter();
let mut peekable = FullyPeekableIterator::new(iter);
assert_eq!(peekable.lift(0), Some(&1));
assert_eq!(peekable.lift(1), Some(&2));
assert_eq!(peekable.lift(2), None);
assert_eq!(peekable.next(), Some(1));
assert_eq!(peekable.next(), Some(2));
assert_eq!(peekable.next(), None);
assert_eq!(peekable.lift(10), None);
}

#[test]
fn it_can_lift_many_elements_without_advancing() {
let iter = vec![1, 2].into_iter();
let mut peekable = FullyPeekableIterator::new(iter);
assert_eq!(peekable.lift_many(0, 1), vec!(Some(&1)));
assert_eq!(peekable.lift_many(0, 2), vec!(Some(&1), Some(&2)));
assert_eq!(peekable.lift_many(1, 3), vec!(Some(&2), None));
assert_eq!(peekable.lift_many(5, 7), vec!(None, None));
assert_eq!(peekable.next(), Some(1));
assert_eq!(peekable.next(), Some(2));
assert_eq!(peekable.next(), None);
}

#[test]
fn it_can_peek_at_the_next_element_without_advancing() {
let iter = vec![1, 2].into_iter();
let mut peekable = iter.fully_peekable();
assert_eq!(peekable.peek(), Some(&1));
assert_eq!(peekable.next(), Some(1));
assert_eq!(peekable.peek(), Some(&2));
assert_eq!(peekable.next(), Some(2));
assert_eq!(peekable.peek(), None);
assert_eq!(peekable.next(), None);
}

#[test]
fn it_can_peek_many_elements_without_advancing() {
let iter = vec![1, 2].into_iter();
let mut peekable = iter.fully_peekable();
assert_eq!(peekable.peek_many(0), vec!());
assert_eq!(peekable.peek_many(1), vec!(Some(&1)));
assert_eq!(peekable.peek_many(2), vec!(Some(&1), Some(&2)));
assert_eq!(peekable.peek_many(3), vec!(Some(&1), Some(&2), None));
assert_eq!(peekable.next(), Some(1));
assert_eq!(peekable.peek_many(1), vec!(Some(&2)));
assert_eq!(peekable.peek_many(2), vec!(Some(&2), None));
assert_eq!(peekable.next(), Some(2));
assert_eq!(peekable.peek_many(1), vec!(None));
assert_eq!(peekable.peek_many(2), vec!(None, None));
assert_eq!(peekable.peek_many(0), vec!());
}

#[test]
fn it_can_lift_elements_without_advancing_mut() {
let iter = vec![1, 2].into_iter();
let mut peekable = FullyPeekableIterator::new(iter);
assert_eq!(peekable.lift_mut(0), Some(&mut 1));
assert_eq!(peekable.lift_mut(1), Some(&mut 2));
assert_eq!(peekable.lift_mut(2), None);
assert_eq!(peekable.next(), Some(1));
assert_eq!(peekable.next(), Some(2));
assert_eq!(peekable.next(), None);
}

#[test]
fn it_can_peek_at_the_next_element_without_advancing_mut() {
let iter = vec![1, 2].into_iter();
let mut peekable = iter.fully_peekable();
assert_eq!(peekable.peek_mut(), Some(&mut 1));
assert_eq!(peekable.next(), Some(1));
assert_eq!(peekable.peek_mut(), Some(&mut 2));
assert_eq!(peekable.next(), Some(2));
assert_eq!(peekable.peek_mut(), None);
assert_eq!(peekable.next(), None);
}

#[test]
fn it_can_return_the_next_element_if_a_predicate_is_true() {
let iter = vec![1, 2].into_iter();
let mut peekable = iter.fully_peekable();
assert_eq!(peekable.next_if(|next| next == &0), None);
assert_eq!(peekable.next_if(|next| next == &1), Some(1));
assert_eq!(peekable.next_if(|next| next == &1), None);
assert_eq!(peekable.next_if(|next| next == &2), Some(2));
assert_eq!(peekable.has_next(), false);
assert_eq!(peekable.next_if(|_| true), None);
}

#[test]
fn it_works() {
let result = fully_peek(2, 2);
assert_eq!(result, 4);
fn it_can_return_the_next_element_if_it_is_equal_to_a_supplied_value() {
let iter = vec![1, 2].into_iter();
let mut peekable = iter.fully_peekable();
assert_eq!(peekable.next_if_eq(&0), None);
assert_eq!(peekable.next_if_eq(&1), Some(1));
assert_eq!(peekable.next_if_eq(&1), None);
assert_eq!(peekable.next_if_eq(&2), Some(2));
assert_eq!(peekable.has_next(), false);
}
}
Loading