From ae40f2882686537c60a27779c6682ba165d7e257 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 5 Feb 2026 09:02:49 +0000 Subject: [PATCH 1/5] New methods [circular_]array_windows(). These are very like the existing `tuple_windows` and `circular_tuple_windows`, but they impose the minimum possible bounds on the input iterator: it must have cloneable items because each item is returned N times, and it must be Sized so that it can be stored in a struct. Unlike the tuple versions, they don't require the input iterator itself to have extra traits, like Clone or ExactSizeIterator. Because the return type is an array (as suggested in #1084), we must handle the zero-length case, because you can't have a constraint `N>0`. In that situation we still read to the end of the input iterator to determine how many zero-length arrays to return, discarding each item as we read it. This preserves the same invariants that hold for nonzero window sizes: `array_windows::` on `k` items returns `min(0, k-N+1)` windows (therefore, `k+1` of them if `N==0`), and `circular_array_windows` on `k` items always returns exactly `k` windows. --- src/array_impl.rs | 230 ++++++++++++++++++++++++ src/lib.rs | 117 ++++++++++++ tests/arrays.rs | 448 ++++++++++++++++++++++++++++++++++++++++++++++ tests/laziness.rs | 6 + tests/quick.rs | 73 ++++++++ 5 files changed, 874 insertions(+) create mode 100644 src/array_impl.rs create mode 100644 tests/arrays.rs diff --git a/src/array_impl.rs b/src/array_impl.rs new file mode 100644 index 000000000..90b327c1f --- /dev/null +++ b/src/array_impl.rs @@ -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 +where + I: Iterator + Sized, + I::Item: Clone, +{ + iter: Fuse, + inner: Option>, +} + +#[derive(Debug, Clone)] +struct ArrayWindowsInner { + // `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 ArrayWindowsInner { + /// 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 Iterator for ArrayWindows +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(iter: I) -> ArrayWindows +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 +where + I: Iterator + Sized, + I::Item: Clone, +{ + iter: Fuse, + inner: Option>, +} + +#[derive(Debug, Clone)] +struct CircularArrayWindowsInner { + // `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, +} + +impl Iterator for CircularArrayWindows +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; 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(iter: I) -> CircularArrayWindows +where + I: Iterator + Sized, + I::Item: Clone, +{ + CircularArrayWindows { + iter: iter.fuse(), + inner: None, + } +} diff --git a/src/lib.rs b/src/lib.rs index 36ddef6cc..22d3928d3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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, 3> = (1..6).array_windows(); + /// itertools::assert_equal(it, vec![[1, 2, 3], [2, 3, 4], [3, 4, 5]]); + /// ``` + fn array_windows(self) -> ArrayWindows + 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::(); + /// 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, 2> = + /// (1..6).circular_array_windows(); + /// itertools::assert_equal( + /// it, vec![[1, 2], [2, 3], [3, 4], [4, 5], [5, 1]]); + /// ``` + fn circular_array_windows(self) -> CircularArrayWindows + 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. /// diff --git a/tests/arrays.rs b/tests/arrays.rs new file mode 100644 index 000000000..6c6de9ca7 --- /dev/null +++ b/tests/arrays.rs @@ -0,0 +1,448 @@ +use itertools::Itertools; + +#[test] +fn array_windows() { + let [vec0, vec1, vec2, vec4, vec10] = [ + vec![], + vec![1], + vec![1, 2], + vec![1, 2, 3, 4], + vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + ]; + + assert_eq!( + vec10 + .iter() + .copied() + .array_windows::<2>() + .collect::>(), + vec![ + [1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [6, 7], + [7, 8], + [8, 9], + [9, 10], + ] + ); + + assert_eq!( + vec4.iter() + .copied() + .array_windows::<2>() + .collect::>(), + vec![[1, 2], [2, 3], [3, 4]] + ); + + assert_eq!( + vec2.iter() + .copied() + .array_windows::<2>() + .collect::>(), + vec![[1, 2]] + ); + + assert_eq!( + vec1.iter() + .copied() + .array_windows::<2>() + .collect::>(), + Vec::<[i32; 2]>::new() + ); + + assert_eq!( + vec0.iter() + .copied() + .array_windows::<2>() + .collect::>(), + Vec::<[i32; 2]>::new() + ); + + assert_eq!( + vec10 + .iter() + .copied() + .array_windows::<4>() + .collect::>(), + vec![ + [1, 2, 3, 4], + [2, 3, 4, 5], + [3, 4, 5, 6], + [4, 5, 6, 7], + [5, 6, 7, 8], + [6, 7, 8, 9], + [7, 8, 9, 10], + ] + ); + + // For zero-length output windows, the equation + // + // output length = input length + N - 1 + // + // implies that we return one _more_ zero-length window than there + // are input items, as if we were returning a zero-length window + // for each position between elements of the input list, including + // the positions at the start and end. + assert_eq!( + vec0.iter() + .copied() + .array_windows::<0>() + .collect::>(), + vec![[]] + ); + + assert_eq!( + vec1.iter() + .copied() + .array_windows::<0>() + .collect::>(), + vec![[], []] + ); + + assert_eq!( + vec2.iter() + .copied() + .array_windows::<0>() + .collect::>(), + vec![[], [], []] + ); + + assert_eq!( + vec0.iter() + .copied() + .array_windows::<1>() + .collect::>(), + Vec::<[i32; 1]>::new() + ); + + assert_eq!( + vec1.iter() + .copied() + .array_windows::<1>() + .collect::>(), + vec![[1]] + ); + + assert_eq!( + vec2.iter() + .copied() + .array_windows::<1>() + .collect::>(), + vec![[1], [2]] + ); + + assert_eq!( + vec1.iter() + .copied() + .array_windows::<7>() + .collect::>(), + Vec::<[i32; 7]>::new() + ); + + assert_eq!( + vec2.iter() + .copied() + .array_windows::<7>() + .collect::>(), + Vec::<[i32; 7]>::new() + ); + + assert_eq!( + vec4.iter() + .copied() + .array_windows::<7>() + .collect::>(), + Vec::<[i32; 7]>::new() + ); + + // Check that array_windows agrees with tuple_windows + assert_eq!( + vec4.iter().copied().array_windows().collect::>(), + vec4.iter() + .copied() + .tuple_windows() + .map(|(a,)| [a]) + .collect::>(), + ); + + assert_eq!( + vec4.iter().copied().array_windows().collect::>(), + vec4.iter() + .copied() + .tuple_windows() + .map(|(a, b)| [a, b]) + .collect::>(), + ); + + assert_eq!( + vec4.iter().copied().array_windows().collect::>(), + vec4.iter() + .copied() + .tuple_windows() + .map(|(a, b, c, d, e, f, g)| [a, b, c, d, e, f, g]) + .collect::>(), + ); + + assert_eq!( + vec2.iter().copied().array_windows().collect::>(), + vec2.iter() + .copied() + .tuple_windows() + .map(|(a, b, c, d, e, f, g)| [a, b, c, d, e, f, g]) + .collect::>(), + ); + + assert_eq!( + vec1.iter().copied().array_windows().collect::>(), + vec1.iter() + .copied() + .tuple_windows() + .map(|(a, b, c, d, e, f, g)| [a, b, c, d, e, f, g]) + .collect::>(), + ); + + assert_eq!( + vec0.iter().copied().array_windows().collect::>(), + vec0.iter() + .copied() + .tuple_windows() + .map(|(a, b, c, d, e, f, g)| [a, b, c, d, e, f, g]) + .collect::>(), + ); +} + +#[test] +fn circular_array_windows() { + let [vec0, vec1, vec2, vec4, vec10] = [ + vec![], + vec![1], + vec![1, 2], + vec![1, 2, 3, 4], + vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + ]; + + assert_eq!( + vec10 + .iter() + .copied() + .circular_array_windows::<2>() + .collect::>(), + vec![ + [1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [6, 7], + [7, 8], + [8, 9], + [9, 10], + [10, 1] + ] + ); + + assert_eq!( + vec4.iter() + .copied() + .circular_array_windows::<2>() + .collect::>(), + vec![[1, 2], [2, 3], [3, 4], [4, 1]] + ); + + assert_eq!( + vec2.iter() + .copied() + .circular_array_windows::<2>() + .collect::>(), + vec![[1, 2], [2, 1]] + ); + + assert_eq!( + vec1.iter() + .copied() + .circular_array_windows::<2>() + .collect::>(), + vec![[1, 1]] + ); + + assert_eq!( + vec0.iter() + .copied() + .circular_array_windows::<2>() + .collect::>(), + Vec::<[i32; 2]>::new() + ); + + assert_eq!( + vec10 + .iter() + .copied() + .circular_array_windows::<4>() + .collect::>(), + vec![ + [1, 2, 3, 4], + [2, 3, 4, 5], + [3, 4, 5, 6], + [4, 5, 6, 7], + [5, 6, 7, 8], + [6, 7, 8, 9], + [7, 8, 9, 10], + [8, 9, 10, 1], + [9, 10, 1, 2], + [10, 1, 2, 3], + ] + ); + + assert_eq!( + vec0.iter() + .copied() + .circular_array_windows::<0>() + .collect::>(), + Vec::<[i32; 0]>::new() + ); + + assert_eq!( + vec1.iter() + .copied() + .circular_array_windows::<0>() + .collect::>(), + vec![[]] + ); + + assert_eq!( + vec2.iter() + .copied() + .circular_array_windows::<0>() + .collect::>(), + vec![[], []] + ); + + assert_eq!( + vec0.iter() + .copied() + .circular_array_windows::<1>() + .collect::>(), + Vec::<[i32; 1]>::new() + ); + + assert_eq!( + vec1.iter() + .copied() + .circular_array_windows::<1>() + .collect::>(), + vec![[1]] + ); + + assert_eq!( + vec2.iter() + .copied() + .circular_array_windows::<1>() + .collect::>(), + vec![[1], [2]] + ); + + assert_eq!( + vec1.iter() + .copied() + .circular_array_windows::<7>() + .collect::>(), + vec![[1, 1, 1, 1, 1, 1, 1]] + ); + + assert_eq!( + vec2.iter() + .copied() + .circular_array_windows::<7>() + .collect::>(), + vec![[1, 2, 1, 2, 1, 2, 1], [2, 1, 2, 1, 2, 1, 2]] + ); + + assert_eq!( + vec4.iter() + .copied() + .circular_array_windows::<7>() + .collect::>(), + vec![ + [1, 2, 3, 4, 1, 2, 3], + [2, 3, 4, 1, 2, 3, 4], + [3, 4, 1, 2, 3, 4, 1], + [4, 1, 2, 3, 4, 1, 2], + ] + ); + + // Check that circular_array_windows agrees with circular_tuple_windows + assert_eq!( + vec4.iter() + .copied() + .circular_array_windows() + .collect::>(), + vec4.iter() + .copied() + .circular_tuple_windows() + .map(|(a,)| [a]) + .collect::>(), + ); + + assert_eq!( + vec4.iter() + .copied() + .circular_array_windows() + .collect::>(), + vec4.iter() + .copied() + .circular_tuple_windows() + .map(|(a, b)| [a, b]) + .collect::>(), + ); + + assert_eq!( + vec4.iter() + .copied() + .circular_array_windows() + .collect::>(), + vec4.iter() + .copied() + .circular_tuple_windows() + .map(|(a, b, c, d, e, f, g)| [a, b, c, d, e, f, g]) + .collect::>(), + ); + + assert_eq!( + vec2.iter() + .copied() + .circular_array_windows() + .collect::>(), + vec2.iter() + .copied() + .circular_tuple_windows() + .map(|(a, b, c, d, e, f, g)| [a, b, c, d, e, f, g]) + .collect::>(), + ); + + assert_eq!( + vec1.iter() + .copied() + .circular_array_windows() + .collect::>(), + vec1.iter() + .copied() + .circular_tuple_windows() + .map(|(a, b, c, d, e, f, g)| [a, b, c, d, e, f, g]) + .collect::>(), + ); + + assert_eq!( + vec0.iter() + .copied() + .circular_array_windows() + .collect::>(), + vec0.iter() + .copied() + .circular_tuple_windows() + .map(|(a, b, c, d, e, f, g)| [a, b, c, d, e, f, g]) + .collect::>(), + ); +} diff --git a/tests/laziness.rs b/tests/laziness.rs index dfeee68f8..16801b99e 100644 --- a/tests/laziness.rs +++ b/tests/laziness.rs @@ -104,6 +104,12 @@ must_use_tests! { let _ = Panicking.circular_tuple_windows::<(_, _)>(); let _ = Panicking.circular_tuple_windows::<(_, _, _)>(); } + circular_array_windows { + let _ = Panicking.circular_array_windows::<0>(); + let _ = Panicking.circular_array_windows::<1>(); + let _ = Panicking.circular_array_windows::<2>(); + let _ = Panicking.circular_array_windows::<3>(); + } tuples { let _ = Panicking.tuples::<(_,)>(); let _ = Panicking.tuples::<(_, _)>(); diff --git a/tests/quick.rs b/tests/quick.rs index faae8d698..538346bb9 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -1300,6 +1300,79 @@ quickcheck! { } } +// array iterators +quickcheck! { + fn equal_array_windows_0(a: Vec) -> bool { + let x = (0..=a.len()).map(|_| [&0u8; 0] ); + let y = a.iter().array_windows::<0>(); + itertools::assert_equal(x,y); + true + } + + fn equal_array_windows_1(a: Vec) -> bool { + let x = a.iter().map(|e| [e] ); + let y = a.iter().array_windows::<1>(); + itertools::assert_equal(x,y); + true + } + + fn equal_array_windows_2(a: Vec) -> bool { + let x = (0..a.len().saturating_sub(1)).map(|start_idx| [ + &a[start_idx], + &a[start_idx + 1], + ]); + let y = a.iter().array_windows::<2>(); + itertools::assert_equal(x,y); + true + } + + fn equal_array_windows_3(a: Vec) -> bool { + let x = (0..a.len().saturating_sub(2)).map(|start_idx| [ + &a[start_idx], + &a[start_idx + 1], + &a[start_idx + 2], + ]); + let y = a.iter().array_windows::<3>(); + itertools::assert_equal(x,y); + true + } + + fn equal_circular_array_windows_0(a: Vec) -> bool { + let x = a.iter().map(|_| [&0u8; 0] ); + let y = a.iter().circular_array_windows::<0>(); + itertools::assert_equal(x,y); + true + } + + fn equal_circular_array_windows_1(a: Vec) -> bool { + let x = a.iter().map(|e| [e] ); + let y = a.iter().circular_array_windows::<1>(); + itertools::assert_equal(x,y); + true + } + + fn equal_circular_array_windows_2(a: Vec) -> bool { + let x = (0..a.len()).map(|start_idx| [ + &a[start_idx], + &a[(start_idx + 1) % a.len()], + ]); + let y = a.iter().circular_array_windows::<2>(); + itertools::assert_equal(x,y); + true + } + + fn equal_circular_array_windows_3(a: Vec) -> bool { + let x = (0..a.len()).map(|start_idx| [ + &a[start_idx], + &a[(start_idx + 1) % a.len()], + &a[(start_idx + 2) % a.len()], + ]); + let y = a.iter().circular_array_windows::<3>(); + itertools::assert_equal(x,y); + true + } +} + // with_position quickcheck! { fn with_position_exact_size_1(a: Vec) -> bool { From 6038583b774d8ee4f88872e7806d2d9b7789f01f Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 20 Feb 2026 12:38:41 +0000 Subject: [PATCH 2/5] Address inline review comments --- src/array_impl.rs | 10 +++------- src/lib.rs | 16 ---------------- tests/laziness.rs | 6 ++++++ 3 files changed, 9 insertions(+), 23 deletions(-) diff --git a/src/array_impl.rs b/src/array_impl.rs index 90b327c1f..f22739213 100644 --- a/src/array_impl.rs +++ b/src/array_impl.rs @@ -59,7 +59,7 @@ where } Some(buf) => { let inner = ArrayWindowsInner { - window: buf.clone(), + window: buf, window_start: 0, }; let window = inner.make_window(); @@ -194,11 +194,9 @@ where 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 { + if N == 0 || 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. @@ -209,9 +207,7 @@ where item }; - if N > 0 { - inner.arraywin.add_to_buffer(item); - } + inner.arraywin.add_to_buffer(item); Some(inner.arraywin.make_window()) } } diff --git a/src/lib.rs b/src/lib.rs index 22d3928d3..ec2485fab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -932,13 +932,6 @@ pub trait Itertools: Iterator { /// 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, 3> = (1..6).array_windows(); - /// itertools::assert_equal(it, vec![[1, 2, 3], [2, 3, 4], [3, 4, 5]]); /// ``` fn array_windows(self) -> ArrayWindows where @@ -999,15 +992,6 @@ pub trait Itertools: Iterator { /// 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, 2> = - /// (1..6).circular_array_windows(); - /// itertools::assert_equal( - /// it, vec![[1, 2], [2, 3], [3, 4], [4, 5], [5, 1]]); /// ``` fn circular_array_windows(self) -> CircularArrayWindows where diff --git a/tests/laziness.rs b/tests/laziness.rs index 16801b99e..c14622b19 100644 --- a/tests/laziness.rs +++ b/tests/laziness.rs @@ -104,6 +104,12 @@ must_use_tests! { let _ = Panicking.circular_tuple_windows::<(_, _)>(); let _ = Panicking.circular_tuple_windows::<(_, _, _)>(); } + array_windows { + let _ = Panicking.array_windows::<0>(); + let _ = Panicking.array_windows::<1>(); + let _ = Panicking.array_windows::<2>(); + let _ = Panicking.array_windows::<3>(); + } circular_array_windows { let _ = Panicking.circular_array_windows::<0>(); let _ = Panicking.circular_array_windows::<1>(); From 59b7fe346de506c9aa14386899e8f8b0ed6295a6 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 20 Feb 2026 12:45:16 +0000 Subject: [PATCH 3/5] Change array/tuple cross-check to the suggested form --- tests/arrays.rs | 282 ++++++++++++++++++++++++++---------------------- 1 file changed, 156 insertions(+), 126 deletions(-) diff --git a/tests/arrays.rs b/tests/arrays.rs index 6c6de9ca7..a7cfc66a9 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -157,61 +157,85 @@ fn array_windows() { .collect::>(), Vec::<[i32; 7]>::new() ); +} - // Check that array_windows agrees with tuple_windows - assert_eq!( - vec4.iter().copied().array_windows().collect::>(), - vec4.iter() - .copied() - .tuple_windows() - .map(|(a,)| [a]) - .collect::>(), - ); - - assert_eq!( - vec4.iter().copied().array_windows().collect::>(), - vec4.iter() - .copied() - .tuple_windows() - .map(|(a, b)| [a, b]) - .collect::>(), - ); - - assert_eq!( - vec4.iter().copied().array_windows().collect::>(), - vec4.iter() - .copied() - .tuple_windows() - .map(|(a, b, c, d, e, f, g)| [a, b, c, d, e, f, g]) - .collect::>(), - ); - - assert_eq!( - vec2.iter().copied().array_windows().collect::>(), - vec2.iter() - .copied() - .tuple_windows() - .map(|(a, b, c, d, e, f, g)| [a, b, c, d, e, f, g]) - .collect::>(), - ); - - assert_eq!( - vec1.iter().copied().array_windows().collect::>(), - vec1.iter() - .copied() - .tuple_windows() - .map(|(a, b, c, d, e, f, g)| [a, b, c, d, e, f, g]) - .collect::>(), - ); - - assert_eq!( - vec0.iter().copied().array_windows().collect::>(), - vec0.iter() - .copied() - .tuple_windows() - .map(|(a, b, c, d, e, f, g)| [a, b, c, d, e, f, g]) - .collect::>(), - ); +#[test] +fn array_windows_equal_tuple_windows() { + fn internal(slice: &[usize]) { + itertools::assert_equal( + slice + .iter() + .tuple_windows::<(_,)>() + .map(Into::<[_; 1]>::into), + slice.iter().array_windows::<1>() + ); + itertools::assert_equal( + slice + .iter() + .tuple_windows::<(_, _,)>() + .map(Into::<[_; 2]>::into), + slice.iter().array_windows::<2>() + ); + itertools::assert_equal( + slice + .iter() + .tuple_windows::<(_, _, _,)>() + .map(Into::<[_; 3]>::into), + slice.iter().array_windows::<3>() + ); + itertools::assert_equal( + slice + .iter() + .tuple_windows::<(_, _, _, _,)>() + .map(Into::<[_; 4]>::into), + slice.iter().array_windows::<4>() + ); + itertools::assert_equal( + slice + .iter() + .tuple_windows::<(_, _, _, _, _,)>() + .map(Into::<[_; 5]>::into), + slice.iter().array_windows::<5>() + ); + itertools::assert_equal( + slice + .iter() + .tuple_windows::<(_, _, _, _, _, _,)>() + .map(Into::<[_; 6]>::into), + slice.iter().array_windows::<6>() + ); + itertools::assert_equal( + slice + .iter() + .tuple_windows::<(_, _, _, _, _, _, _,)>() + .map(Into::<[_; 7]>::into), + slice.iter().array_windows::<7>() + ); + itertools::assert_equal( + slice + .iter() + .tuple_windows::<(_, _, _, _, _, _, _, _,)>() + .map(Into::<[_; 8]>::into), + slice.iter().array_windows::<8>() + ); + itertools::assert_equal( + slice + .iter() + .tuple_windows::<(_, _, _, _, _, _, _, _, _,)>() + .map(Into::<[_; 9]>::into), + slice.iter().array_windows::<9>() + ); + itertools::assert_equal( + slice + .iter() + .tuple_windows::<(_, _, _, _, _, _, _, _, _, _,)>() + .map(Into::<[_; 10]>::into), + slice.iter().array_windows::<10>() + ); + } + for i in 0..100 { + internal(&(0..i).collect::>()); + } } #[test] @@ -372,77 +396,83 @@ fn circular_array_windows() { [4, 1, 2, 3, 4, 1, 2], ] ); +} - // Check that circular_array_windows agrees with circular_tuple_windows - assert_eq!( - vec4.iter() - .copied() - .circular_array_windows() - .collect::>(), - vec4.iter() - .copied() - .circular_tuple_windows() - .map(|(a,)| [a]) - .collect::>(), - ); - - assert_eq!( - vec4.iter() - .copied() - .circular_array_windows() - .collect::>(), - vec4.iter() - .copied() - .circular_tuple_windows() - .map(|(a, b)| [a, b]) - .collect::>(), - ); - - assert_eq!( - vec4.iter() - .copied() - .circular_array_windows() - .collect::>(), - vec4.iter() - .copied() - .circular_tuple_windows() - .map(|(a, b, c, d, e, f, g)| [a, b, c, d, e, f, g]) - .collect::>(), - ); - - assert_eq!( - vec2.iter() - .copied() - .circular_array_windows() - .collect::>(), - vec2.iter() - .copied() - .circular_tuple_windows() - .map(|(a, b, c, d, e, f, g)| [a, b, c, d, e, f, g]) - .collect::>(), - ); - - assert_eq!( - vec1.iter() - .copied() - .circular_array_windows() - .collect::>(), - vec1.iter() - .copied() - .circular_tuple_windows() - .map(|(a, b, c, d, e, f, g)| [a, b, c, d, e, f, g]) - .collect::>(), - ); - - assert_eq!( - vec0.iter() - .copied() - .circular_array_windows() - .collect::>(), - vec0.iter() - .copied() - .circular_tuple_windows() - .map(|(a, b, c, d, e, f, g)| [a, b, c, d, e, f, g]) - .collect::>(), - ); +#[test] +fn circular_array_windows_equal_circular_tuple_windows() { + fn internal(slice: &[usize]) { + itertools::assert_equal( + slice + .iter() + .circular_tuple_windows::<(_,)>() + .map(Into::<[_; 1]>::into), + slice.iter().circular_array_windows::<1>() + ); + itertools::assert_equal( + slice + .iter() + .circular_tuple_windows::<(_, _,)>() + .map(Into::<[_; 2]>::into), + slice.iter().circular_array_windows::<2>() + ); + itertools::assert_equal( + slice + .iter() + .circular_tuple_windows::<(_, _, _,)>() + .map(Into::<[_; 3]>::into), + slice.iter().circular_array_windows::<3>() + ); + itertools::assert_equal( + slice + .iter() + .circular_tuple_windows::<(_, _, _, _,)>() + .map(Into::<[_; 4]>::into), + slice.iter().circular_array_windows::<4>() + ); + itertools::assert_equal( + slice + .iter() + .circular_tuple_windows::<(_, _, _, _, _,)>() + .map(Into::<[_; 5]>::into), + slice.iter().circular_array_windows::<5>() + ); + itertools::assert_equal( + slice + .iter() + .circular_tuple_windows::<(_, _, _, _, _, _,)>() + .map(Into::<[_; 6]>::into), + slice.iter().circular_array_windows::<6>() + ); + itertools::assert_equal( + slice + .iter() + .circular_tuple_windows::<(_, _, _, _, _, _, _,)>() + .map(Into::<[_; 7]>::into), + slice.iter().circular_array_windows::<7>() + ); + itertools::assert_equal( + slice + .iter() + .circular_tuple_windows::<(_, _, _, _, _, _, _, _,)>() + .map(Into::<[_; 8]>::into), + slice.iter().circular_array_windows::<8>() + ); + itertools::assert_equal( + slice + .iter() + .circular_tuple_windows::<(_, _, _, _, _, _, _, _, _,)>() + .map(Into::<[_; 9]>::into), + slice.iter().circular_array_windows::<9>() + ); + itertools::assert_equal( + slice + .iter() + .circular_tuple_windows::<(_, _, _, _, _, _, _, _, _, _,)>() + .map(Into::<[_; 10]>::into), + slice.iter().circular_array_windows::<10>() + ); + } + for i in 0..100 { + internal(&(0..i).collect::>()); + } } From 44024e16532edbf9e791b729d07737a582d75b1b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 20 Feb 2026 12:53:34 +0000 Subject: [PATCH 4/5] Document zero-length window behaviour. --- src/lib.rs | 21 +++++++++++++++++++++ tests/arrays.rs | 5 +++++ 2 files changed, 26 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index ec2485fab..a3fb9b1db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -913,6 +913,9 @@ pub trait Itertools: Iterator { /// windows are returned. Otherwise, if the input iterator /// contains `k` items, exactly `k+N-1` windows are returned. /// + /// (This formula still applies when `N==0`, and means that `k+1` + /// zero-length windows are returned for `k` input items.) + /// /// ``` /// use itertools::Itertools; /// @@ -927,6 +930,10 @@ pub trait Itertools: Iterator { /// let mut windows = (1..6).array_windows::<10>(); /// assert_eq!(None, windows.next()); /// + /// // When the window size is zero, one more window is returned + /// // than there are items. + /// itertools::assert_equal((1..6).array_windows::<0>(), vec![[]; 6]); + /// /// // 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() { @@ -957,6 +964,9 @@ pub trait Itertools: Iterator { /// treated as a cyclic list, and a window of `N` items had been /// returned for every starting point in the cycle. /// + /// (If the window size is zero, the function _still_ returns one + /// empty window per element of the input iterator.) + /// /// ``` /// use itertools::Itertools; /// @@ -987,6 +997,17 @@ pub trait Itertools: Iterator { /// let mut windows = empty.circular_array_windows::<5>(); /// assert_eq!(None, windows.next()); /// + /// // If the input is empty, no windows are returned at all. + /// let empty = std::iter::empty::(); + /// let mut windows = empty.circular_array_windows::<5>(); + /// assert_eq!(None, windows.next()); + /// + /// // One window is returned per item, even if the windows are empty. + /// itertools::assert_equal( + /// (1..6).circular_array_windows::<0>(), + /// vec![[]; 5] + /// ); + /// /// // 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() { diff --git a/tests/arrays.rs b/tests/arrays.rs index a7cfc66a9..1c3fc4e56 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -320,6 +320,11 @@ fn circular_array_windows() { ] ); + // For zero-length output windows, we preserve the invariant that + // output length = input length, and return the same number of + // zero-length windows as there were input items, as if we were + // returning a zero-length window for every possible starting + // position in the cyclic input list. assert_eq!( vec0.iter() .copied() From 951a574808f41b35f0f25db11eabab6664992f6d Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Fri, 20 Feb 2026 13:03:15 +0000 Subject: [PATCH 5/5] Reformat (oops) --- tests/arrays.rs | 76 ++++++++++++++++++++++++------------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/tests/arrays.rs b/tests/arrays.rs index 1c3fc4e56..45dd683b0 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -167,70 +167,70 @@ fn array_windows_equal_tuple_windows() { .iter() .tuple_windows::<(_,)>() .map(Into::<[_; 1]>::into), - slice.iter().array_windows::<1>() + slice.iter().array_windows::<1>(), ); itertools::assert_equal( slice .iter() - .tuple_windows::<(_, _,)>() + .tuple_windows::<(_, _)>() .map(Into::<[_; 2]>::into), - slice.iter().array_windows::<2>() + slice.iter().array_windows::<2>(), ); itertools::assert_equal( slice .iter() - .tuple_windows::<(_, _, _,)>() + .tuple_windows::<(_, _, _)>() .map(Into::<[_; 3]>::into), - slice.iter().array_windows::<3>() + slice.iter().array_windows::<3>(), ); itertools::assert_equal( slice .iter() - .tuple_windows::<(_, _, _, _,)>() + .tuple_windows::<(_, _, _, _)>() .map(Into::<[_; 4]>::into), - slice.iter().array_windows::<4>() + slice.iter().array_windows::<4>(), ); itertools::assert_equal( slice .iter() - .tuple_windows::<(_, _, _, _, _,)>() + .tuple_windows::<(_, _, _, _, _)>() .map(Into::<[_; 5]>::into), - slice.iter().array_windows::<5>() + slice.iter().array_windows::<5>(), ); itertools::assert_equal( slice .iter() - .tuple_windows::<(_, _, _, _, _, _,)>() + .tuple_windows::<(_, _, _, _, _, _)>() .map(Into::<[_; 6]>::into), - slice.iter().array_windows::<6>() + slice.iter().array_windows::<6>(), ); itertools::assert_equal( slice .iter() - .tuple_windows::<(_, _, _, _, _, _, _,)>() + .tuple_windows::<(_, _, _, _, _, _, _)>() .map(Into::<[_; 7]>::into), - slice.iter().array_windows::<7>() + slice.iter().array_windows::<7>(), ); itertools::assert_equal( slice .iter() - .tuple_windows::<(_, _, _, _, _, _, _, _,)>() + .tuple_windows::<(_, _, _, _, _, _, _, _)>() .map(Into::<[_; 8]>::into), - slice.iter().array_windows::<8>() + slice.iter().array_windows::<8>(), ); itertools::assert_equal( slice .iter() - .tuple_windows::<(_, _, _, _, _, _, _, _, _,)>() + .tuple_windows::<(_, _, _, _, _, _, _, _, _)>() .map(Into::<[_; 9]>::into), - slice.iter().array_windows::<9>() + slice.iter().array_windows::<9>(), ); itertools::assert_equal( slice .iter() - .tuple_windows::<(_, _, _, _, _, _, _, _, _, _,)>() + .tuple_windows::<(_, _, _, _, _, _, _, _, _, _)>() .map(Into::<[_; 10]>::into), - slice.iter().array_windows::<10>() + slice.iter().array_windows::<10>(), ); } for i in 0..100 { @@ -411,70 +411,70 @@ fn circular_array_windows_equal_circular_tuple_windows() { .iter() .circular_tuple_windows::<(_,)>() .map(Into::<[_; 1]>::into), - slice.iter().circular_array_windows::<1>() + slice.iter().circular_array_windows::<1>(), ); itertools::assert_equal( slice .iter() - .circular_tuple_windows::<(_, _,)>() + .circular_tuple_windows::<(_, _)>() .map(Into::<[_; 2]>::into), - slice.iter().circular_array_windows::<2>() + slice.iter().circular_array_windows::<2>(), ); itertools::assert_equal( slice .iter() - .circular_tuple_windows::<(_, _, _,)>() + .circular_tuple_windows::<(_, _, _)>() .map(Into::<[_; 3]>::into), - slice.iter().circular_array_windows::<3>() + slice.iter().circular_array_windows::<3>(), ); itertools::assert_equal( slice .iter() - .circular_tuple_windows::<(_, _, _, _,)>() + .circular_tuple_windows::<(_, _, _, _)>() .map(Into::<[_; 4]>::into), - slice.iter().circular_array_windows::<4>() + slice.iter().circular_array_windows::<4>(), ); itertools::assert_equal( slice .iter() - .circular_tuple_windows::<(_, _, _, _, _,)>() + .circular_tuple_windows::<(_, _, _, _, _)>() .map(Into::<[_; 5]>::into), - slice.iter().circular_array_windows::<5>() + slice.iter().circular_array_windows::<5>(), ); itertools::assert_equal( slice .iter() - .circular_tuple_windows::<(_, _, _, _, _, _,)>() + .circular_tuple_windows::<(_, _, _, _, _, _)>() .map(Into::<[_; 6]>::into), - slice.iter().circular_array_windows::<6>() + slice.iter().circular_array_windows::<6>(), ); itertools::assert_equal( slice .iter() - .circular_tuple_windows::<(_, _, _, _, _, _, _,)>() + .circular_tuple_windows::<(_, _, _, _, _, _, _)>() .map(Into::<[_; 7]>::into), - slice.iter().circular_array_windows::<7>() + slice.iter().circular_array_windows::<7>(), ); itertools::assert_equal( slice .iter() - .circular_tuple_windows::<(_, _, _, _, _, _, _, _,)>() + .circular_tuple_windows::<(_, _, _, _, _, _, _, _)>() .map(Into::<[_; 8]>::into), - slice.iter().circular_array_windows::<8>() + slice.iter().circular_array_windows::<8>(), ); itertools::assert_equal( slice .iter() - .circular_tuple_windows::<(_, _, _, _, _, _, _, _, _,)>() + .circular_tuple_windows::<(_, _, _, _, _, _, _, _, _)>() .map(Into::<[_; 9]>::into), - slice.iter().circular_array_windows::<9>() + slice.iter().circular_array_windows::<9>(), ); itertools::assert_equal( slice .iter() - .circular_tuple_windows::<(_, _, _, _, _, _, _, _, _, _,)>() + .circular_tuple_windows::<(_, _, _, _, _, _, _, _, _, _)>() .map(Into::<[_; 10]>::into), - slice.iter().circular_array_windows::<10>() + slice.iter().circular_array_windows::<10>(), ); } for i in 0..100 {