diff --git a/.gitignore b/.gitignore index 1d14bc1..6f9ee73 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /target /Cargo.lock -.DS_Store \ No newline at end of file +.DS_Store +.idea/ \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index e1baa8c..ccc3d63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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/*", ".gitignore"] \ No newline at end of file diff --git a/README.md b/README.md index 65ee0e4..73796c7 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,22 @@ [![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 iter = vec![1, 2, 3].into_iter(); +let mut peekable = 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(&2)); +assert_eq!(peekable.peek_many(2), vec!(Some(&3), None)); ``` diff --git a/src/lib.rs b/src/lib.rs index 2aca270..7495931 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,17 +1,336 @@ -//! 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 { + iter: I, + queue: VecDeque, +} + +/// Create a new fully-peekable iterator from an existing iterator. +impl FullyPeekableIterator { + fn new(iter: I) -> FullyPeekableIterator { + FullyPeekableIterator { + iter, + queue: VecDeque::new(), + } + } +} + +/// Implementation of the typical iterator methods on the fully-peekable iterator. +impl Iterator for FullyPeekableIterator { + type Item = I::Item; + + /// Returns the next value which may advance the iterator. + #[inline] + fn next(&mut self) -> Option { + 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) { + 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 ExactSizeIterator for FullyPeekableIterator {} + +impl FusedIterator for FullyPeekableIterator {} + +impl FullyPeekableIterator { + /// 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> { + 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> { + 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 { + 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(&mut self, expected: &T) -> Option + where + T: ?Sized, + I::Item: PartialEq, + { + 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 +where + I: Iterator, +{ + /// Return a fully peekable iterator. + fn fully_peekable(self) -> FullyPeekableIterator; +} + +/// Add a fully-peekable iterator implementation to any iterator implicitly. +impl IntoFullyPeekableIterator for I +where + I: Iterator, +{ + /// Return a fully peekable iterator. + fn fully_peekable(self) -> FullyPeekableIterator { + 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 { + iter: I, + } + + impl TestIterator { + fn new(iter: I) -> TestIterator { + TestIterator { + iter, + } + } + } + + impl Iterator for TestIterator { + type Item = I::Item; + + fn next(&mut self) -> Option { + self.iter.next() + } + + fn size_hint(&self) -> (usize, Option) { + (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); } }