-
Notifications
You must be signed in to change notification settings - Fork 341
New method circular_array_windows(). #1086
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,230 @@ | ||
| use crate::Itertools; | ||
| use std::iter::Fuse; | ||
|
|
||
| /// An iterator over all contiguous windows of the input iterator, | ||
| /// producing arrays of a specific size. | ||
| /// | ||
| /// See [`.array_windows()`](crate::Itertools::array_windows) for more | ||
| /// information. | ||
| #[derive(Debug, Clone)] | ||
| pub struct ArrayWindows<I, const N: usize> | ||
| where | ||
| I: Iterator + Sized, | ||
| I::Item: Clone, | ||
| { | ||
| iter: Fuse<I>, | ||
| inner: Option<ArrayWindowsInner<I::Item, N>>, | ||
| } | ||
|
|
||
| #[derive(Debug, Clone)] | ||
| struct ArrayWindowsInner<T: Clone, const N: usize> { | ||
| // `window` stores the `N` items delivered in the most | ||
| // recent output window. It is stored in the form of a ring | ||
| // buffer, with `window_start` identifying the element | ||
| // that logically comes first. | ||
| window: [T; N], | ||
| window_start: usize, | ||
| } | ||
|
|
||
| impl<T: Clone, const N: usize> ArrayWindowsInner<T, N> { | ||
| /// Replace the least recent item in `window` with a new | ||
| /// item. | ||
| fn add_to_buffer(&mut self, item: T) { | ||
| if N > 0 { | ||
| self.window[self.window_start] = item; | ||
| self.window_start = (self.window_start + 1) % N; | ||
| } | ||
| } | ||
|
|
||
| /// Construct an array window to return. | ||
| fn make_window(&self) -> [T; N] { | ||
| std::array::from_fn(|i| self.window[(i + self.window_start) % N].clone()) | ||
| } | ||
| } | ||
|
|
||
| impl<I, const N: usize> Iterator for ArrayWindows<I, N> | ||
| where | ||
| I: Iterator + Sized, | ||
| I::Item: Clone, | ||
| { | ||
| type Item = [I::Item; N]; | ||
|
|
||
| fn next(&mut self) -> Option<[I::Item; N]> { | ||
| match &mut self.inner { | ||
| // Initialisation code, when next() is called for the first time | ||
| None => match self.iter.next_array() { | ||
| None => { | ||
| // The input iterator was completely empty | ||
| None | ||
| } | ||
| Some(buf) => { | ||
| let inner = ArrayWindowsInner { | ||
| window: buf.clone(), | ||
| window_start: 0, | ||
| }; | ||
| let window = inner.make_window(); | ||
| self.inner = Some(inner); | ||
| Some(window) | ||
| } | ||
| }, | ||
| Some(inner) => match self.iter.next() { | ||
| Some(item) => { | ||
| inner.add_to_buffer(item); | ||
| Some(inner.make_window()) | ||
| } | ||
| None => None, | ||
| }, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| pub fn array_windows<I, const N: usize>(iter: I) -> ArrayWindows<I, N> | ||
| where | ||
| I: Iterator + Sized, | ||
| I::Item: Clone, | ||
| { | ||
| ArrayWindows { | ||
| iter: iter.fuse(), | ||
| inner: None, | ||
| } | ||
| } | ||
|
|
||
| /// An iterator over all windows, wrapping back to the first elements when the | ||
| /// window would otherwise exceed the length of the iterator, producing arrays | ||
| /// of a specific size. | ||
| /// | ||
| /// See [`.circular_array_windows()`](crate::Itertools::circular_array_windows) | ||
| /// for more information. | ||
| #[derive(Debug, Clone)] | ||
| pub struct CircularArrayWindows<I, const N: usize> | ||
| where | ||
| I: Iterator + Sized, | ||
| I::Item: Clone, | ||
| { | ||
| iter: Fuse<I>, | ||
| inner: Option<CircularArrayWindowsInner<I::Item, N>>, | ||
| } | ||
|
|
||
| #[derive(Debug, Clone)] | ||
| struct CircularArrayWindowsInner<T: Clone, const N: usize> { | ||
| // `prefix` stores the first `N` items output from this iterator. | ||
| // If the input contained fewer than `N` items, then it is filled | ||
| // with clones of the previous items in a cycle. | ||
| // | ||
| // `prefix_pos` tracks the number of items that have been _used_ | ||
| // from `prefix`. It begins counting up from 0 once the input runs | ||
| // out. (So in the case where the input iterator is shorter than | ||
| // `N`, it will begin counting up before `prefix` has even been | ||
| // populated during setup.) | ||
| prefix: [T; N], | ||
| prefix_pos: usize, | ||
|
|
||
| // For delivering the output arrays, we reuse `ArrayWindowsInner` | ||
| // unchanged. | ||
| arraywin: ArrayWindowsInner<T, N>, | ||
| } | ||
|
|
||
| impl<I, const N: usize> Iterator for CircularArrayWindows<I, N> | ||
| where | ||
| I: Iterator + Sized, | ||
| I::Item: Clone, | ||
| { | ||
| type Item = [I::Item; N]; | ||
|
|
||
| fn next(&mut self) -> Option<[I::Item; N]> { | ||
| match &mut self.inner { | ||
| // Initialisation code, when next() is called for the first time | ||
| None => match self.iter.next() { | ||
| None => { | ||
| // The input iterator was completely empty | ||
| None | ||
| } | ||
| Some(first) => { | ||
| // We have at least one item, so we can definitely | ||
| // populate `prefix` (even if we have to make N | ||
| // copies of this element). | ||
|
|
||
| // Construct [Option<T>; N] and convert to [T; N] | ||
| // once it's full. TODO: can this be improved? | ||
| let mut items = std::array::from_fn(|_| None); | ||
| let mut prefix_pos = 0; | ||
| if N > 0 { | ||
| // The first item stored is the one passed to | ||
| // us from our caller. | ||
| items[0] = Some(first); | ||
| } | ||
| for i in 1..N { | ||
| // Populate the remaining slots in `items` | ||
| // from the input iterator. | ||
| items[i] = self.iter.next(); | ||
| if items[i].is_none() { | ||
| // If the input iterator runs out early, | ||
| // populate the rest of `items` by | ||
| // recycling from the beginning, and set | ||
| // `prefix_pos` to indicate that we have | ||
| // already consumed those items. | ||
| for j in i..N { | ||
| items[j] = items[j - i].clone(); | ||
| } | ||
| prefix_pos = N - i; | ||
| break; | ||
| } | ||
| } | ||
| let items = items.map(Option::unwrap); | ||
|
|
||
| let inner = CircularArrayWindowsInner { | ||
| prefix: items.clone(), | ||
| prefix_pos, | ||
| arraywin: ArrayWindowsInner { | ||
| window: items, | ||
| window_start: 0, | ||
| }, | ||
| }; | ||
|
|
||
| let window = inner.arraywin.make_window(); | ||
| self.inner = Some(inner); | ||
| Some(window) | ||
| } | ||
| }, | ||
| Some(inner) => { | ||
| // Normal case. Read the next item in the logical | ||
| // input sequence (consisting of the contents of the | ||
| // input iterator followed by N-1 items recycling from | ||
| // the beginning), and add it to the ring buffer. | ||
| let item = if let Some(item) = self.iter.next() { | ||
| // Read from the input iterator. | ||
| item | ||
| } else if N == 0 { | ||
| return None; | ||
| } else { | ||
| assert!(N == 0 || inner.prefix_pos < N); | ||
| if inner.prefix_pos + 1 == N { | ||
| // The input iterator has run out, and we've | ||
| // emitted as many windows as we read items, | ||
| // so we've finished. | ||
| return None; | ||
| } | ||
| let item = inner.prefix[inner.prefix_pos].clone(); | ||
| inner.prefix_pos += 1; | ||
| item | ||
| }; | ||
|
|
||
| if N > 0 { | ||
| inner.arraywin.add_to_buffer(item); | ||
| } | ||
| Some(inner.arraywin.make_window()) | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| pub fn circular_array_windows<I, const N: usize>(iter: I) -> CircularArrayWindows<I, N> | ||
| where | ||
| I: Iterator + Sized, | ||
| I::Item: Clone, | ||
| { | ||
| CircularArrayWindows { | ||
| iter: iter.fuse(), | ||
| inner: None, | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -97,6 +97,7 @@ pub mod structs { | |
| FilterOk, Interleave, InterleaveShortest, MapInto, MapOk, Positions, Product, PutBack, | ||
| TakeWhileRef, TupleCombinations, Update, WhileSome, | ||
| }; | ||
| pub use crate::array_impl::{ArrayWindows, CircularArrayWindows}; | ||
| #[cfg(feature = "use_alloc")] | ||
| pub use crate::combinations::{ArrayCombinations, Combinations}; | ||
| #[cfg(feature = "use_alloc")] | ||
|
|
@@ -174,6 +175,7 @@ pub use crate::unziptuple::{multiunzip, MultiUnzip}; | |
| pub use crate::with_position::Position; | ||
| pub use crate::ziptuple::multizip; | ||
| mod adaptors; | ||
| mod array_impl; | ||
| mod either_or_both; | ||
| pub use crate::either_or_both::EitherOrBoth; | ||
| #[doc(hidden)] | ||
|
|
@@ -900,6 +902,121 @@ pub trait Itertools: Iterator { | |
| tuple_impl::tuples(self) | ||
| } | ||
|
|
||
| /// Return an iterator over all contiguous windows, producing | ||
| /// arrays of size `N`. | ||
| /// | ||
| /// `array_windows` clones the iterator elements so that they can be | ||
| /// part of successive windows. This makes it most suited for iterators | ||
| /// of references and other values that are cheap to copy. | ||
| /// | ||
| /// If the input iterator contains fewer than `N` items, no | ||
| /// windows are returned. Otherwise, if the input iterator | ||
| /// contains `k` items, exactly `k+N-1` windows are returned. | ||
| /// | ||
| /// ``` | ||
| /// use itertools::Itertools; | ||
| /// | ||
| /// // Three-element windows from the items [1, 2, 3, 4, 5]. | ||
| /// itertools::assert_equal( | ||
| /// (1..6).array_windows::<3>(), | ||
| /// vec![[1, 2, 3], [2, 3, 4], [3, 4, 5]] | ||
| /// ); | ||
| /// | ||
| /// // When the input list is shorter than the window size, no windows | ||
| /// // are returned at all. | ||
| /// let mut windows = (1..6).array_windows::<10>(); | ||
| /// assert_eq!(None, windows.next()); | ||
| /// | ||
| /// // In some cases you don't have to specify the window size | ||
| /// // explicitly with a type hint, because Rust can infer it | ||
| /// for [a, b, c] in (1..6).array_windows() { | ||
| /// println!("{a} {b} {c}"); | ||
| /// } | ||
| /// | ||
| /// // You can also specify the complete type. | ||
| /// use itertools::ArrayWindows; | ||
| /// use std::ops::Range; | ||
| /// | ||
| /// let it: ArrayWindows<Range<u32>, 3> = (1..6).array_windows(); | ||
| /// itertools::assert_equal(it, vec![[1, 2, 3], [2, 3, 4], [3, 4, 5]]); | ||
| /// ``` | ||
| fn array_windows<const N: usize>(self) -> ArrayWindows<Self, N> | ||
| where | ||
| Self: Sized, | ||
| Self::Item: Clone, | ||
| { | ||
| array_impl::array_windows(self) | ||
| } | ||
|
|
||
| /// Return an iterator over all windows, wrapping back to the first | ||
| /// elements when the window would otherwise exceed the length of the | ||
| /// iterator, producing arrays of size `N`. | ||
| /// | ||
| /// `circular_array_windows` clones the iterator elements so that | ||
| /// they can be part of successive windows, this makes it most | ||
| /// suited for iterators of references and other values that are | ||
| /// cheap to copy. | ||
| /// | ||
| /// One window is returned per element of the input iterator. This | ||
| /// is true even if the input contains fewer elements than the | ||
| /// window size. In that situation, input elements are repeated | ||
| /// within each window. The results are as if the input had been | ||
| /// treated as a cyclic list, and a window of `N` items had been | ||
| /// returned for every starting point in the cycle. | ||
| /// | ||
| /// ``` | ||
| /// use itertools::Itertools; | ||
| /// | ||
| /// // Three-element windows from [1, 2, 3, 4, 5], with two of | ||
| /// // them wrapping round from 5 to 1. | ||
| /// itertools::assert_equal( | ||
| /// (1..6).circular_array_windows::<3>(), | ||
| /// vec![[1, 2, 3], [2, 3, 4], [3, 4, 5], [4, 5, 1], [5, 1, 2]] | ||
| /// ); | ||
| /// | ||
| /// // If the input is shorter than the window size, input | ||
| /// // items are repeated even within the same window. | ||
| /// itertools::assert_equal( | ||
| /// (1..3).circular_array_windows::<5>(), | ||
| /// vec![[1, 2, 1, 2, 1], [2, 1, 2, 1, 2]] | ||
| /// ); | ||
| /// | ||
| /// // If the input contains only one item, the returned window | ||
| /// // repeats it N times. | ||
| /// let once = std::iter::once(1); | ||
| /// itertools::assert_equal( | ||
| /// once.circular_array_windows::<3>(), | ||
| /// vec![[1, 1, 1]] | ||
| /// ); | ||
| /// | ||
| /// // If the input is empty, no windows are returned at all. | ||
| /// let empty = std::iter::empty::<i32>(); | ||
| /// let mut windows = empty.circular_array_windows::<5>(); | ||
| /// assert_eq!(None, windows.next()); | ||
| /// | ||
| /// // In some cases you don't have to specify the window size | ||
| /// // explicitly with a type hint, because Rust can infer it. | ||
| /// for [a, b, c] in (1..10).circular_array_windows() { | ||
| /// println!("{a} {b} {c}"); | ||
| /// } | ||
| /// | ||
| /// // You can also specify the complete type. | ||
| /// use itertools::CircularArrayWindows; | ||
| /// use std::ops::Range; | ||
| /// | ||
| /// let it: CircularArrayWindows<Range<u32>, 2> = | ||
| /// (1..6).circular_array_windows(); | ||
| /// itertools::assert_equal( | ||
| /// it, vec![[1, 2], [2, 3], [3, 4], [4, 5], [5, 1]]); | ||
| /// ``` | ||
|
Comment on lines
968
to
1011
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please make the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, I should have known that copying the demo code directly from
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Haha - (I'm) Guilty as charged. Sorry for being pedantic, but I think if we touch the code, we should make it (and its documentation) as good as we can.
Comment on lines
968
to
1011
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this describe what happens for e.g. |
||
| fn circular_array_windows<const N: usize>(self) -> CircularArrayWindows<Self, N> | ||
| where | ||
| Self: Sized, | ||
| Self::Item: Clone, | ||
| { | ||
| array_impl::circular_array_windows(self) | ||
| } | ||
|
|
||
| /// Split into an iterator pair that both yield all elements from | ||
| /// the original iterator. | ||
| /// | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Behavior for unfused iterators (iterators that yield
Some(...)after they already yieldedNone) is questionable here - it would createinneron a non-first-call tonext.Let's
fusethe underlying iterator.windowsfor unfused iterators are confusing anyway.