From d8bbef9a01c8ab2e5088b600c84bccd0ece9f454 Mon Sep 17 00:00:00 2001 From: Hugo Demeyere Date: Wed, 15 Jan 2025 21:58:52 +0100 Subject: [PATCH 1/6] feat(corelib): Iterator::peekable Not complete: still some compilation issue --- corelib/src/iter.cairo | 1 + corelib/src/iter/adapters.cairo | 5 ++ corelib/src/iter/adapters/peekable.cairo | 81 ++++++++++++++++++++++++ corelib/src/iter/traits/iterator.cairo | 42 +++++++++++- corelib/src/test/iter_test.cairo | 23 +++++++ 5 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 corelib/src/iter/adapters/peekable.cairo diff --git a/corelib/src/iter.cairo b/corelib/src/iter.cairo index 527313a5d77..b45c626ad15 100644 --- a/corelib/src/iter.cairo +++ b/corelib/src/iter.cairo @@ -231,4 +231,5 @@ //! [`map`]: Iterator::map mod adapters; mod traits; +pub use adapters::PeekableTrait; pub use traits::{FromIterator, IntoIterator, Iterator}; diff --git a/corelib/src/iter/adapters.cairo b/corelib/src/iter/adapters.cairo index 900e71ff356..e938418f280 100644 --- a/corelib/src/iter/adapters.cairo +++ b/corelib/src/iter/adapters.cairo @@ -12,3 +12,8 @@ mod zip; pub use zip::Zip; #[allow(unused_imports)] pub(crate) use zip::zipped_iterator; + +mod peekable; +#[allow(unused_imports)] +pub(crate) use peekable::peekable_iterator; +pub use peekable::{Peekable, PeekableTrait}; diff --git a/corelib/src/iter/adapters/peekable.cairo b/corelib/src/iter/adapters/peekable.cairo new file mode 100644 index 00000000000..82aa58e9abf --- /dev/null +++ b/corelib/src/iter/adapters/peekable.cairo @@ -0,0 +1,81 @@ +/// An iterator with a `peek()` that returns a copy to the next +/// element. +/// +/// This `struct` is created by the [`peekable`] method on [`Iterator`]. See its +/// documentation for more. +/// +/// [`peekable`]: Iterator::peekable +#[must_use] +#[derive(Drop, Debug)] +pub struct Peekable { + iter: I, + /// Remember a peeked value, even if it was None. + peeked: Option>, +} + +pub fn peekable_iterator>(iter: I) -> Peekable { + Peekable { iter, peeked: Option::None } +} + +impl PeekableIterator< + I, impl IterI: Iterator, +Drop, +Drop, +> of Iterator> { + type Item = IterI::Item; + + fn next(ref self: Peekable) -> Option { + // `take()` makes sure that if a value was already peeked, `peeked` will be reset to None + match self.peeked.take() { + Option::Some(v) => v, + Option::None => self.iter.next(), + } + } +} + +#[generate_trait] +pub impl PeekableTraitImpl< + I, impl IterI: Iterator, +Copy, +Drop, +> of PeekableTrait { + /// Returns a copy to the next() value without advancing the iterator. + /// + /// Like [`next`], if there is a value, it is wrapped in a `Some(T)`. + /// But if the iteration is over, `None` is returned. + /// + /// [`next`]: Iterator::next + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// let xs = array![1, 2, 3]; + /// + /// let mut iter = xs.into_iter().peekable(); + /// + /// // peek() lets us see one step into the future + /// assert_eq!(iter.peek(), Some(1)); + /// assert_eq!(iter.next(), Some(1)); + /// + /// assert_eq!(iter.next(), Some(2)); + /// + /// // The iterator does not advance even if we `peek` multiple times + /// assert_eq!(iter.peek(), Some(3)); + /// assert_eq!(iter.peek(), Some(3)); + /// + /// assert_eq!(iter.next(), Some(3)); + /// + /// // After the iterator is finished, so is `peek()` + /// assert_eq!(iter.peek(), None); + /// assert_eq!(iter.next(), None); + /// ``` + #[inline] + fn peek(ref self: Peekable) -> Option { + match self.peeked { + Option::Some(value) => value, + Option::None => { + let next_value = self.iter.next(); + self.peeked = Option::Some(next_value); + next_value + }, + } + } +} diff --git a/corelib/src/iter/traits/iterator.cairo b/corelib/src/iter/traits/iterator.cairo index b90a337154e..713cb2dfe62 100644 --- a/corelib/src/iter/traits/iterator.cairo +++ b/corelib/src/iter/traits/iterator.cairo @@ -1,5 +1,6 @@ use crate::iter::adapters::{ - Enumerate, Map, Zip, enumerated_iterator, mapped_iterator, zipped_iterator, + Enumerate, Map, Peekable, Zip, enumerated_iterator, mapped_iterator, peekable_iterator, + zipped_iterator, }; /// A trait for dealing with iterators. @@ -312,4 +313,43 @@ pub trait Iterator { ) -> Zip { zipped_iterator(self, other.into_iter()) } + + /// Creates an iterator which can use the [`peek`] method to look at the next element of the + /// iterator. See its documentation for more information. + /// + /// Note that the underlying iterator is still advanced when [`peek`] is called for the first + /// time: In order to retrieve the next element, [`next`] is called on the underlying iterator, + /// hence any side effects (i.e. anything other than fetching the next value) of the [`next`] + /// method will occur. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// let xs = array![1, 2, 3]; + /// + /// let mut iter = xs.into_iter().peekable(); + /// + /// // peek() lets us see one step into the future + /// assert_eq!(iter.peek(), Option::Some(1)); + /// assert_eq!(iter.next(), Option::Some(1)); + /// + /// assert_eq!(iter.next(), Option::Some(2)); + /// + /// // we can peek() multiple times, the iterator won't advance + /// assert_eq!(iter.peek(), Option::Some(3)); + /// assert_eq!(iter.peek(), Option::Some(3)); + /// + /// assert_eq!(iter.next(), Option::Some(3)); + /// + /// // after the iterator is finished, so is peek() + /// assert_eq!(iter.peek(), Option::None); + /// assert_eq!(iter.next(), Option::None); + /// ``` + #[inline] + #[must_use] + fn peekable(self: T) -> Peekable { + peekable_iterator(self) + } } diff --git a/corelib/src/test/iter_test.cairo b/corelib/src/test/iter_test.cairo index 605472e239c..4789906796c 100644 --- a/corelib/src/test/iter_test.cairo +++ b/corelib/src/test/iter_test.cairo @@ -1,3 +1,5 @@ +use core::iter::PeekableTrait; + #[test] fn test_advance_by() { let mut iter = array![1_u8, 2, 3, 4].into_iter(); @@ -53,3 +55,24 @@ fn test_iter_adapter_fold() { assert_eq!(sum, 6); } + +#[test] +fn test_iter_adapter_peekable() { + let xs = array![1, 2, 3]; + + let mut iter = xs.into_iter().peekable(); + + // peek() lets us see one step into the future + assert_eq!(iter.peek(), Option::Some(1)); + assert_eq!(iter.next(), Option::Some(1)); + assert_eq!(iter.next(), Option::Some(2)); + + // The iterator does not advance even if we `peek` multiple times + assert_eq!(iter.peek(), Option::Some(3)); + assert_eq!(iter.peek(), Option::Some(3)); + assert_eq!(iter.next(), Option::Some(3)); + + // After the iterator is finished, so is `peek()` + assert_eq!(iter.peek(), Option::None); + assert_eq!(iter.next(), Option::None); +} From 7843a3418f630d8cbab2b4f6e32e17a916655418 Mon Sep 17 00:00:00 2001 From: Hugo Demeyere Date: Thu, 16 Jan 2025 22:32:09 +0100 Subject: [PATCH 2/6] feat(corelib): Inline range instead of array variable --- corelib/src/iter/adapters/peekable.cairo | 4 +--- corelib/src/iter/traits/iterator.cairo | 4 +--- corelib/src/test/iter_test.cairo | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/corelib/src/iter/adapters/peekable.cairo b/corelib/src/iter/adapters/peekable.cairo index 82aa58e9abf..b6fe2dc0e97 100644 --- a/corelib/src/iter/adapters/peekable.cairo +++ b/corelib/src/iter/adapters/peekable.cairo @@ -47,9 +47,7 @@ pub impl PeekableTraitImpl< /// Basic usage: /// /// ``` - /// let xs = array![1, 2, 3]; - /// - /// let mut iter = xs.into_iter().peekable(); + /// let mut iter = (1..4_u8).into_iter().peekable(); /// /// // peek() lets us see one step into the future /// assert_eq!(iter.peek(), Some(1)); diff --git a/corelib/src/iter/traits/iterator.cairo b/corelib/src/iter/traits/iterator.cairo index 713cb2dfe62..3e0a068eeaa 100644 --- a/corelib/src/iter/traits/iterator.cairo +++ b/corelib/src/iter/traits/iterator.cairo @@ -327,9 +327,7 @@ pub trait Iterator { /// Basic usage: /// /// ``` - /// let xs = array![1, 2, 3]; - /// - /// let mut iter = xs.into_iter().peekable(); + /// let mut iter = (1..4_u8).into_iter().peekable(); /// /// // peek() lets us see one step into the future /// assert_eq!(iter.peek(), Option::Some(1)); diff --git a/corelib/src/test/iter_test.cairo b/corelib/src/test/iter_test.cairo index 4789906796c..2913e362a5a 100644 --- a/corelib/src/test/iter_test.cairo +++ b/corelib/src/test/iter_test.cairo @@ -58,9 +58,7 @@ fn test_iter_adapter_fold() { #[test] fn test_iter_adapter_peekable() { - let xs = array![1, 2, 3]; - - let mut iter = xs.into_iter().peekable(); + let mut iter = (1..4_u8).into_iter().peekable(); // peek() lets us see one step into the future assert_eq!(iter.peek(), Option::Some(1)); From f34a401f4e40017ee3f1060b3d7da661f46ae2c7 Mon Sep 17 00:00:00 2001 From: Hugo Demeyere Date: Fri, 17 Jan 2025 23:19:00 +0100 Subject: [PATCH 3/6] fix(corelib): Remove unnecessary trait requirement --- corelib/src/iter/adapters/peekable.cairo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/corelib/src/iter/adapters/peekable.cairo b/corelib/src/iter/adapters/peekable.cairo index b6fe2dc0e97..c1c5bb21420 100644 --- a/corelib/src/iter/adapters/peekable.cairo +++ b/corelib/src/iter/adapters/peekable.cairo @@ -13,7 +13,7 @@ pub struct Peekable { peeked: Option>, } -pub fn peekable_iterator>(iter: I) -> Peekable { +pub fn peekable_iterator(iter: I) -> Peekable { Peekable { iter, peeked: Option::None } } From ba2c3812eeb4e80d88c293a5bee4629b89dd7052 Mon Sep 17 00:00:00 2001 From: Hugo Demeyere Date: Sat, 18 Jan 2025 12:30:02 +0100 Subject: [PATCH 4/6] fix(corelib): Use +Destruct instead of +Drop --- corelib/src/iter/adapters/peekable.cairo | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/corelib/src/iter/adapters/peekable.cairo b/corelib/src/iter/adapters/peekable.cairo index c1c5bb21420..5a0ed7e3516 100644 --- a/corelib/src/iter/adapters/peekable.cairo +++ b/corelib/src/iter/adapters/peekable.cairo @@ -18,16 +18,17 @@ pub fn peekable_iterator(iter: I) -> Peekable { } impl PeekableIterator< - I, impl IterI: Iterator, +Drop, +Drop, + I, impl IterI: Iterator, +Destruct, +Destruct, > of Iterator> { type Item = IterI::Item; fn next(ref self: Peekable) -> Option { - // `take()` makes sure that if a value was already peeked, `peeked` will be reset to None - match self.peeked.take() { + let next_value = match self.peeked { Option::Some(v) => v, Option::None => self.iter.next(), - } + }; + self.peeked = Option::None; + next_value } } From 5057201beea3ebc36b780a02d06381dbaa49f074 Mon Sep 17 00:00:00 2001 From: Hugo Demeyere Date: Wed, 22 Jan 2025 23:24:51 +0100 Subject: [PATCH 5/6] doc(corelib): Add doc to Peekable struct --- corelib/src/iter/adapters/peekable.cairo | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/corelib/src/iter/adapters/peekable.cairo b/corelib/src/iter/adapters/peekable.cairo index 5a0ed7e3516..691f742960f 100644 --- a/corelib/src/iter/adapters/peekable.cairo +++ b/corelib/src/iter/adapters/peekable.cairo @@ -8,8 +8,19 @@ #[must_use] #[derive(Drop, Debug)] pub struct Peekable { + /// This field stores the underlying iterator the `Peekable` struct wraps, + /// providing it the ability to peek at the next element. iter: I, - /// Remember a peeked value, even if it was None. + /// Caches the next value of the iterator when `peek()` is called, + /// consuming the iterator only once even when several consecutive calls + /// to `peek()` are made. + /// + /// - `None`: Indicates that no peek has been performed. + /// - `Some(None)`: Indicates that a peek has been performed but the iterator is exhausted. + /// - `Some(Some(R))`: Contains the next element in the iterator, ready to be returned by + /// `peek()`. + /// + /// `peeked` is reset to `None` when `next()` is called. peeked: Option>, } From 1f583fc214def3e8eed176feec6d4f5acb422b98 Mon Sep 17 00:00:00 2001 From: Hugo Demeyere Date: Sat, 25 Jan 2025 22:34:20 +0100 Subject: [PATCH 6/6] doc(corelib): Update peekable struct doc --- corelib/src/iter/adapters/peekable.cairo | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/corelib/src/iter/adapters/peekable.cairo b/corelib/src/iter/adapters/peekable.cairo index 691f742960f..30975094ec1 100644 --- a/corelib/src/iter/adapters/peekable.cairo +++ b/corelib/src/iter/adapters/peekable.cairo @@ -8,8 +8,7 @@ #[must_use] #[derive(Drop, Debug)] pub struct Peekable { - /// This field stores the underlying iterator the `Peekable` struct wraps, - /// providing it the ability to peek at the next element. + /// The underlying iterator. iter: I, /// Caches the next value of the iterator when `peek()` is called, /// consuming the iterator only once even when several consecutive calls