From a7489e48d175894c37c6b5586c87944fe97b08ac Mon Sep 17 00:00:00 2001 From: Ronno Das Date: Sun, 2 Mar 2025 19:00:19 +0100 Subject: [PATCH 1/8] array_chunks --- src/array_chunks.rs | 82 +++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 55 ++++++++++++++++++++++++++++++ src/next_array.rs | 21 +++++++++++- 3 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 src/array_chunks.rs diff --git a/src/array_chunks.rs b/src/array_chunks.rs new file mode 100644 index 000000000..205dd557a --- /dev/null +++ b/src/array_chunks.rs @@ -0,0 +1,82 @@ +use alloc::vec::Vec; + +use crate::next_array::ArrayBuilder; + +/// An iterator that groups the items in arrays of const generic size `N`. +/// +/// See [`.next_array()`](crate::Itertools::next_array) for details. +#[derive(Debug, Clone)] +pub struct ArrayChunks { + iter: I, + partial: Vec, +} + +impl ArrayChunks { + pub(crate) fn new(iter: I) -> Self { + // TODO should we use iter.fuse() instead? Otherwise remainder may behave strangely + Self { + iter, + partial: Vec::new(), + } + } + + /// Returns an iterator that yields all the items that have + /// not been included in any of the arrays. Use this to access the + /// leftover elements if the total number of elements yielded by + /// the original iterator is not a multiple of `N`. + /// + /// If `self` is not exhausted (i.e. `next()` has not returned `None`) + /// then the iterator returned by `remainder()` will also include + /// the elements that *would* have been included in the arrays + /// produced by `next()`. + /// + /// ``` + /// use itertools::Itertools; + /// + /// let mut it = (1..9).array_chunks(); + /// assert_eq!(Some([1, 2, 3]), it.next()); + /// assert_eq!(Some([4, 5, 6]), it.next()); + /// assert_eq!(None, it.next()); + /// itertools::assert_equal(it.remainder(), [7,8]); + /// + /// let mut it = (1..9).array_chunks(); + /// assert_eq!(Some([1, 2, 3]), it.next()); + /// itertools::assert_equal(it.remainder(), 4..9); + /// ``` + pub fn remainder(self) -> impl Iterator { + self.partial.into_iter().chain(self.iter) + } +} + +impl Iterator for ArrayChunks { + type Item = [I::Item; N]; + + fn next(&mut self) -> Option { + if !self.partial.is_empty() { + return None; + } + let mut builder = ArrayBuilder::new(); + for _ in 0..N { + if let Some(item) = self.iter.next() { + builder.push(item); + } else { + break; + } + } + if let Some(array) = builder.take() { + Some(array) + } else { + self.partial = builder.into_vec(); + None + } + } + + fn size_hint(&self) -> (usize, Option) { + if N == 0 { + (usize::MAX, None) + } else { + let (lo, hi) = self.iter.size_hint(); + (lo / N, hi.map(|hi| hi / N)) + } + } +} diff --git a/src/lib.rs b/src/lib.rs index ff117800a..a7caec4f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,6 +97,8 @@ pub mod structs { TakeWhileRef, TupleCombinations, Update, WhileSome, }; #[cfg(feature = "use_alloc")] + pub use crate::array_chunks::ArrayChunks; + #[cfg(feature = "use_alloc")] pub use crate::combinations::{ArrayCombinations, Combinations}; #[cfg(feature = "use_alloc")] pub use crate::combinations_with_replacement::CombinationsWithReplacement; @@ -171,6 +173,8 @@ pub use crate::unziptuple::{multiunzip, MultiUnzip}; pub use crate::with_position::Position; pub use crate::ziptuple::multizip; mod adaptors; +#[cfg(feature = "use_alloc")] +mod array_chunks; mod either_or_both; pub use crate::either_or_both::EitherOrBoth; #[doc(hidden)] @@ -741,6 +745,57 @@ pub trait Itertools: Iterator { groupbylazy::new_chunks(self, size) } + /// Return an iterator that groups the items in arrays of const generic size `N`. + /// + /// Use the method `.remainder()` to access leftover items in case + /// the number of items yielded by the original iterator is not a multiple of `N`. + /// + /// If `N` is 0, the resulting iterator will be equivalent to `repeat([])`, i.e. + /// `next()` will always return `Some([])`. + /// + /// See also the method [`.next_array()`](Itertools::next_array). + /// + /// ``` + /// use itertools::Itertools; + /// let mut v = Vec::new(); + /// for [a, b] in (1..5).array_chunks() { + /// v.push([a, b]); + /// } + /// assert_eq!(v, vec![[1, 2], [3, 4]]); + /// + /// let mut it = (1..9).array_chunks(); + /// assert_eq!(Some([1, 2, 3]), it.next()); + /// assert_eq!(Some([4, 5, 6]), it.next()); + /// assert_eq!(None, it.next()); + /// itertools::assert_equal(it.remainder(), [7,8]); + /// + /// // this requires a type hint + /// let it = (1..7).array_chunks::<3>(); + /// itertools::assert_equal(it, vec![[1, 2, 3], [4, 5, 6]]); + /// + /// // you can also specify the complete type + /// use itertools::ArrayChunks; + /// use std::ops::Range; + /// + /// let it: ArrayChunks, 3> = (1..7).array_chunks(); + /// itertools::assert_equal(it, vec![[1, 2, 3], [4, 5, 6]]); + /// + /// let mut it = (1..3).array_chunks::<0>(); + /// assert_eq!(it.next(), Some([])); + /// assert_eq!(it.next(), Some([])); + /// // and so on for any further calls to `it.next()` + /// itertools::assert_equal(it.remainder(), 1..3); + /// ``` + /// + /// See also [`Tuples::into_buffer`]. + #[cfg(feature = "use_alloc")] + fn array_chunks(self) -> ArrayChunks + where + Self: Sized, + { + ArrayChunks::new(self) + } + /// Return an iterator over all contiguous windows producing tuples of /// a specific size (up to 12). /// diff --git a/src/next_array.rs b/src/next_array.rs index 86480b197..09e27fc6c 100644 --- a/src/next_array.rs +++ b/src/next_array.rs @@ -1,7 +1,9 @@ +#[cfg(feature = "use_alloc")] +use alloc::vec::Vec; use core::mem::{self, MaybeUninit}; /// An array of at most `N` elements. -struct ArrayBuilder { +pub(crate) struct ArrayBuilder { /// The (possibly uninitialized) elements of the `ArrayBuilder`. /// /// # Safety @@ -86,6 +88,23 @@ impl ArrayBuilder { None } } + + #[cfg(feature = "use_alloc")] + pub(crate) fn into_vec(mut self) -> Vec { + let len = self.len; + // SAFETY: Decreasing the value of `self.len` cannot violate the + // safety invariant on `self.arr`. + self.len = 0; + (0..len) + .map(|i| { + // SAFETY: Since `self.len` is 0, `self.arr` may safely contain + // uninitialized elements. + let item = mem::replace(&mut self.arr[i], MaybeUninit::uninit()); + // SAFETY: we know that item is valid since i < len + unsafe { item.assume_init() } + }) + .collect() + } } impl AsMut<[T]> for ArrayBuilder { From 7fa1908ce9323a75af80fc9dc78ce6132a066ba3 Mon Sep 17 00:00:00 2001 From: Ronno Das Date: Mon, 3 Mar 2025 00:12:03 +0100 Subject: [PATCH 2/8] copy-paste artefact --- src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a7caec4f2..a26d5e092 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -786,8 +786,6 @@ pub trait Itertools: Iterator { /// // and so on for any further calls to `it.next()` /// itertools::assert_equal(it.remainder(), 1..3); /// ``` - /// - /// See also [`Tuples::into_buffer`]. #[cfg(feature = "use_alloc")] fn array_chunks(self) -> ArrayChunks where From d866b7dda7c24b04d7f7499975ff6172c7594563 Mon Sep 17 00:00:00 2001 From: Ronno Das Date: Mon, 3 Mar 2025 00:18:14 +0100 Subject: [PATCH 3/8] PME on array_chunks::<0> --- src/array_chunks.rs | 3 +++ src/lib.rs | 16 ++++++++-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/array_chunks.rs b/src/array_chunks.rs index 205dd557a..faed3c6bf 100644 --- a/src/array_chunks.rs +++ b/src/array_chunks.rs @@ -13,6 +13,9 @@ pub struct ArrayChunks { impl ArrayChunks { pub(crate) fn new(iter: I) -> Self { + const { + assert!(N > 0); + } // TODO should we use iter.fuse() instead? Otherwise remainder may behave strangely Self { iter, diff --git a/src/lib.rs b/src/lib.rs index a26d5e092..0c72df429 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -750,12 +750,11 @@ pub trait Itertools: Iterator { /// Use the method `.remainder()` to access leftover items in case /// the number of items yielded by the original iterator is not a multiple of `N`. /// - /// If `N` is 0, the resulting iterator will be equivalent to `repeat([])`, i.e. - /// `next()` will always return `Some([])`. + /// `N == 0` is a compile-time (but post-monomorphization) error. /// /// See also the method [`.next_array()`](Itertools::next_array). /// - /// ``` + /// ```rust /// use itertools::Itertools; /// let mut v = Vec::new(); /// for [a, b] in (1..5).array_chunks() { @@ -779,12 +778,13 @@ pub trait Itertools: Iterator { /// /// let it: ArrayChunks, 3> = (1..7).array_chunks(); /// itertools::assert_equal(it, vec![[1, 2, 3], [4, 5, 6]]); + /// ``` + /// + /// ```compile_fail + /// use itertools::Itertools; /// - /// let mut it = (1..3).array_chunks::<0>(); - /// assert_eq!(it.next(), Some([])); - /// assert_eq!(it.next(), Some([])); - /// // and so on for any further calls to `it.next()` - /// itertools::assert_equal(it.remainder(), 1..3); + /// let mut it = (1..5).array_chunks::<0>(); + /// assert_eq!(Some([]), it.next()); /// ``` #[cfg(feature = "use_alloc")] fn array_chunks(self) -> ArrayChunks From 14f004d6a0abdfcfb8150d2f79d9ea7b87323774 Mon Sep 17 00:00:00 2001 From: Ronno Das Date: Mon, 3 Mar 2025 00:28:13 +0100 Subject: [PATCH 4/8] impl ExactSizeIterator --- src/array_chunks.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/array_chunks.rs b/src/array_chunks.rs index faed3c6bf..380a9288e 100644 --- a/src/array_chunks.rs +++ b/src/array_chunks.rs @@ -83,3 +83,5 @@ impl Iterator for ArrayChunks { } } } + +impl ExactSizeIterator for ArrayChunks {} From 1bb3739a4d792b0eee6ac32d89824158a4b95903 Mon Sep 17 00:00:00 2001 From: Ronno Das Date: Mon, 3 Mar 2025 00:32:47 +0100 Subject: [PATCH 5/8] exact size tests --- src/array_chunks.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/array_chunks.rs b/src/array_chunks.rs index 380a9288e..507e5c7bf 100644 --- a/src/array_chunks.rs +++ b/src/array_chunks.rs @@ -85,3 +85,34 @@ impl Iterator for ArrayChunks { } impl ExactSizeIterator for ArrayChunks {} + +#[cfg(test)] +mod tests { + use crate::Itertools; + + fn exact_size_helper(it: impl Iterator) { + let (lo, hi) = it.size_hint(); + let count = it.count(); + assert_eq!(lo, count); + assert_eq!(hi, Some(count)); + } + + #[test] + fn exact_size_not_divisible() { + let it = (0..10).array_chunks::<3>(); + exact_size_helper(it); + } + + #[test] + fn exact_size_after_next() { + let mut it = (0..10).array_chunks::<3>(); + _ = it.next(); + exact_size_helper(it); + } + + #[test] + fn exact_size_divisible() { + let it = (0..10).array_chunks::<5>(); + exact_size_helper(it); + } +} From 532758a2b091818ecb1e80df9faf50c1430280b5 Mon Sep 17 00:00:00 2001 From: Ronno Das Date: Mon, 3 Mar 2025 00:39:48 +0100 Subject: [PATCH 6/8] rename to arrays Iterator::array_chunks exists on nightly and it's better to avoid the name collision especially if and when that's stabilized --- src/{array_chunks.rs => arrays.rs} | 12 ++++++------ src/lib.rs | 20 ++++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) rename src/{array_chunks.rs => arrays.rs} (91%) diff --git a/src/array_chunks.rs b/src/arrays.rs similarity index 91% rename from src/array_chunks.rs rename to src/arrays.rs index 507e5c7bf..42be5be7b 100644 --- a/src/array_chunks.rs +++ b/src/arrays.rs @@ -6,12 +6,12 @@ use crate::next_array::ArrayBuilder; /// /// See [`.next_array()`](crate::Itertools::next_array) for details. #[derive(Debug, Clone)] -pub struct ArrayChunks { +pub struct Arrays { iter: I, partial: Vec, } -impl ArrayChunks { +impl Arrays { pub(crate) fn new(iter: I) -> Self { const { assert!(N > 0); @@ -36,13 +36,13 @@ impl ArrayChunks { /// ``` /// use itertools::Itertools; /// - /// let mut it = (1..9).array_chunks(); + /// let mut it = (1..9).arrays(); /// assert_eq!(Some([1, 2, 3]), it.next()); /// assert_eq!(Some([4, 5, 6]), it.next()); /// assert_eq!(None, it.next()); /// itertools::assert_equal(it.remainder(), [7,8]); /// - /// let mut it = (1..9).array_chunks(); + /// let mut it = (1..9).arrays(); /// assert_eq!(Some([1, 2, 3]), it.next()); /// itertools::assert_equal(it.remainder(), 4..9); /// ``` @@ -51,7 +51,7 @@ impl ArrayChunks { } } -impl Iterator for ArrayChunks { +impl Iterator for Arrays { type Item = [I::Item; N]; fn next(&mut self) -> Option { @@ -84,7 +84,7 @@ impl Iterator for ArrayChunks { } } -impl ExactSizeIterator for ArrayChunks {} +impl ExactSizeIterator for Arrays {} #[cfg(test)] mod tests { diff --git a/src/lib.rs b/src/lib.rs index 0c72df429..a28404934 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,7 +97,7 @@ pub mod structs { TakeWhileRef, TupleCombinations, Update, WhileSome, }; #[cfg(feature = "use_alloc")] - pub use crate::array_chunks::ArrayChunks; + pub use crate::arrays::Arrays; #[cfg(feature = "use_alloc")] pub use crate::combinations::{ArrayCombinations, Combinations}; #[cfg(feature = "use_alloc")] @@ -174,7 +174,7 @@ pub use crate::with_position::Position; pub use crate::ziptuple::multizip; mod adaptors; #[cfg(feature = "use_alloc")] -mod array_chunks; +mod arrays; mod either_or_both; pub use crate::either_or_both::EitherOrBoth; #[doc(hidden)] @@ -757,41 +757,41 @@ pub trait Itertools: Iterator { /// ```rust /// use itertools::Itertools; /// let mut v = Vec::new(); - /// for [a, b] in (1..5).array_chunks() { + /// for [a, b] in (1..5).arrays() { /// v.push([a, b]); /// } /// assert_eq!(v, vec![[1, 2], [3, 4]]); /// - /// let mut it = (1..9).array_chunks(); + /// let mut it = (1..9).arrays(); /// assert_eq!(Some([1, 2, 3]), it.next()); /// assert_eq!(Some([4, 5, 6]), it.next()); /// assert_eq!(None, it.next()); /// itertools::assert_equal(it.remainder(), [7,8]); /// /// // this requires a type hint - /// let it = (1..7).array_chunks::<3>(); + /// let it = (1..7).arrays::<3>(); /// itertools::assert_equal(it, vec![[1, 2, 3], [4, 5, 6]]); /// /// // you can also specify the complete type - /// use itertools::ArrayChunks; + /// use itertools::Arrays; /// use std::ops::Range; /// - /// let it: ArrayChunks, 3> = (1..7).array_chunks(); + /// let it: Arrays, 3> = (1..7).arrays(); /// itertools::assert_equal(it, vec![[1, 2, 3], [4, 5, 6]]); /// ``` /// /// ```compile_fail /// use itertools::Itertools; /// - /// let mut it = (1..5).array_chunks::<0>(); + /// let mut it = (1..5).arrays::<0>(); /// assert_eq!(Some([]), it.next()); /// ``` #[cfg(feature = "use_alloc")] - fn array_chunks(self) -> ArrayChunks + fn arrays(self) -> Arrays where Self: Sized, { - ArrayChunks::new(self) + Arrays::new(self) } /// Return an iterator over all contiguous windows producing tuples of From 6bede5583848bfa450761b3af25dcd2b825e8bd8 Mon Sep 17 00:00:00 2001 From: Ronno Das Date: Mon, 3 Mar 2025 00:43:43 +0100 Subject: [PATCH 7/8] avoid const assert to keep msrv --- src/arrays.rs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/arrays.rs b/src/arrays.rs index 42be5be7b..7563ba506 100644 --- a/src/arrays.rs +++ b/src/arrays.rs @@ -13,9 +13,8 @@ pub struct Arrays { impl Arrays { pub(crate) fn new(iter: I) -> Self { - const { - assert!(N > 0); - } + assert_positive::(); + // TODO should we use iter.fuse() instead? Otherwise remainder may behave strangely Self { iter, @@ -86,6 +85,22 @@ impl Iterator for Arrays { impl ExactSizeIterator for Arrays {} +/// Effectively assert!(N > 0) post-monomorphization +fn assert_positive() { + trait StaticAssert { + const ASSERT: bool; + } + + impl StaticAssert for () { + const ASSERT: bool = { + assert!(N > 0); + true + }; + } + + assert!(<() as StaticAssert>::ASSERT); +} + #[cfg(test)] mod tests { use crate::Itertools; From db21b2f9dfbaf4b7f24c8c05e77d8bb2fc2782e7 Mon Sep 17 00:00:00 2001 From: Ronno Das Date: Wed, 5 Mar 2025 16:39:04 +0100 Subject: [PATCH 8/8] make assert_positive a macro --- src/arrays.rs | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/arrays.rs b/src/arrays.rs index 7563ba506..c955c5f86 100644 --- a/src/arrays.rs +++ b/src/arrays.rs @@ -2,6 +2,23 @@ use alloc::vec::Vec; use crate::next_array::ArrayBuilder; +macro_rules! const_assert_positive { + ($N: ty) => { + trait StaticAssert { + const ASSERT: bool; + } + + impl StaticAssert for () { + const ASSERT: bool = { + assert!(N > 0); + true + }; + } + + assert!(<() as StaticAssert>::ASSERT); + }; +} + /// An iterator that groups the items in arrays of const generic size `N`. /// /// See [`.next_array()`](crate::Itertools::next_array) for details. @@ -13,7 +30,7 @@ pub struct Arrays { impl Arrays { pub(crate) fn new(iter: I) -> Self { - assert_positive::(); + const_assert_positive!(N); // TODO should we use iter.fuse() instead? Otherwise remainder may behave strangely Self { @@ -85,22 +102,6 @@ impl Iterator for Arrays { impl ExactSizeIterator for Arrays {} -/// Effectively assert!(N > 0) post-monomorphization -fn assert_positive() { - trait StaticAssert { - const ASSERT: bool; - } - - impl StaticAssert for () { - const ASSERT: bool = { - assert!(N > 0); - true - }; - } - - assert!(<() as StaticAssert>::ASSERT); -} - #[cfg(test)] mod tests { use crate::Itertools;