From fbfa0039347acfdb60dcd4cc82c2b6631273fab8 Mon Sep 17 00:00:00 2001 From: mendelsshop Date: Fri, 13 Jun 2025 02:29:45 -0400 Subject: [PATCH 1/8] Added PoolIndex impl for Box<[usize]> --- src/combinations.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/combinations.rs b/src/combinations.rs index 54a027551..2122707e8 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -52,7 +52,16 @@ pub trait PoolIndex: BorrowMut<[usize]> { self.borrow().len() } } +impl PoolIndex for Box<[usize]> { + type Item = Vec; + fn extract_item>(&self, pool: &LazyBuffer) -> Vec + where + T: Clone, + { + pool.get_at(self) + } +} impl PoolIndex for Vec { type Item = Vec; From 76dcd1e25239f13f1ab4a712165c636915cb9a39 Mon Sep 17 00:00:00 2001 From: mendelsshop Date: Fri, 13 Jun 2025 02:33:47 -0400 Subject: [PATCH 2/8] Generisized combinations_with_replacement underlying list type --- src/combinations_with_replacement.rs | 63 +++++++++++++++++----------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/src/combinations_with_replacement.rs b/src/combinations_with_replacement.rs index c17e75250..a2324fd81 100644 --- a/src/combinations_with_replacement.rs +++ b/src/combinations_with_replacement.rs @@ -1,33 +1,36 @@ use alloc::boxed::Box; -use alloc::vec::Vec; +use core::array; use std::fmt; use std::iter::FusedIterator; use super::lazy_buffer::LazyBuffer; use crate::adaptors::checked_binomial; - +use crate::combinations::PoolIndex; /// An iterator to iterate through all the `n`-length combinations in an iterator, with replacement. /// /// See [`.combinations_with_replacement()`](crate::Itertools::combinations_with_replacement) /// for more information. #[derive(Clone)] #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] -pub struct CombinationsWithReplacement +pub struct CombinationsWithReplacementGeneric where I: Iterator, I::Item: Clone, { - indices: Box<[usize]>, + indices: Idx, pool: LazyBuffer, first: bool, } -impl fmt::Debug for CombinationsWithReplacement +/// Iterator for `Box<[I]>` valued combinations_with_replacement returned by [`.combinations_with_replacement()`](crate::Itertools::combinations_with_replacement) +pub type CombinationsWithReplacement = CombinationsWithReplacementGeneric>; +impl fmt::Debug for CombinationsWithReplacementGeneric where I: Iterator + fmt::Debug, I::Item: fmt::Debug + Clone, + Idx: fmt::Debug, { - debug_fmt_fields!(CombinationsWithReplacement, indices, pool, first); + debug_fmt_fields!(CombinationsWithReplacementGeneric, indices, pool, first); } /// Create a new `CombinationsWithReplacement` from a clonable iterator. @@ -37,16 +40,11 @@ where I::Item: Clone, { let indices = alloc::vec![0; k].into_boxed_slice(); - let pool: LazyBuffer = LazyBuffer::new(iter); - CombinationsWithReplacement { - indices, - pool, - first: true, - } + CombinationsWithReplacementGeneric::new(iter, indices) } -impl CombinationsWithReplacement +impl> CombinationsWithReplacementGeneric where I: Iterator, I::Item: Clone, @@ -62,7 +60,8 @@ where // Work out where we need to update our indices let mut increment = None; - for (i, indices_int) in self.indices.iter().enumerate().rev() { + let indices: &mut [usize] = self.indices.borrow_mut(); + for (i, indices_int) in indices.iter().enumerate().rev() { if *indices_int < self.pool.len() - 1 { increment = Some((i, indices_int + 1)); break; @@ -73,39 +72,52 @@ where Some((increment_from, increment_value)) => { // We need to update the rightmost non-max value // and all those to the right - self.indices[increment_from..].fill(increment_value); + indices[increment_from..].fill(increment_value); false } // Otherwise, we're done None => true, } } + /// Constructor with arguments the inner iterator and the initial state for the indices. + fn new(iter: I, indices: Idx) -> Self { + Self { + indices, + pool: LazyBuffer::new(iter), + first: true, + } + } } -impl Iterator for CombinationsWithReplacement +impl Iterator for CombinationsWithReplacementGeneric where I: Iterator, I::Item: Clone, + Idx: PoolIndex, { - type Item = Vec; + type Item = Idx::Item; fn next(&mut self) -> Option { if self.first { // In empty edge cases, stop iterating immediately - if !(self.indices.is_empty() || self.pool.get_next()) { + if !(core::borrow::Borrow::<[usize]>::borrow(&self.indices).is_empty() + || self.pool.get_next()) + { return None; } self.first = false; } else if self.increment_indices() { return None; } - Some(self.pool.get_at(&self.indices)) + Some(self.indices.extract_item(&self.pool)) } fn nth(&mut self, n: usize) -> Option { if self.first { // In empty edge cases, stop iterating immediately - if !(self.indices.is_empty() || self.pool.get_next()) { + if !(core::borrow::Borrow::<[usize]>::borrow(&self.indices).is_empty() + || self.pool.get_next()) + { return None; } self.first = false; @@ -117,13 +129,13 @@ where return None; } } - Some(self.pool.get_at(&self.indices)) + Some(self.indices.extract_item(&self.pool)) } fn size_hint(&self) -> (usize, Option) { let (mut low, mut upp) = self.pool.size_hint(); - low = remaining_for(low, self.first, &self.indices).unwrap_or(usize::MAX); - upp = upp.and_then(|upp| remaining_for(upp, self.first, &self.indices)); + low = remaining_for(low, self.first, self.indices.borrow()).unwrap_or(usize::MAX); + upp = upp.and_then(|upp| remaining_for(upp, self.first, self.indices.borrow())); (low, upp) } @@ -134,14 +146,15 @@ where first, } = self; let n = pool.count(); - remaining_for(n, first, &indices).unwrap() + remaining_for(n, first, indices.borrow()).unwrap() } } -impl FusedIterator for CombinationsWithReplacement +impl FusedIterator for CombinationsWithReplacementGeneric where I: Iterator, I::Item: Clone, + Idx: PoolIndex, { } From a1dd959f0c7cb9ee31a692b8fb6db9d1241a5b31 Mon Sep 17 00:00:00 2001 From: mendelsshop Date: Fri, 13 Jun 2025 02:34:51 -0400 Subject: [PATCH 3/8] Added const generic verions of combinations_with_replacement (array_combinations_with_replacement) --- src/combinations_with_replacement.rs | 13 +++++++++++ src/lib.rs | 32 +++++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/combinations_with_replacement.rs b/src/combinations_with_replacement.rs index a2324fd81..c7d402df5 100644 --- a/src/combinations_with_replacement.rs +++ b/src/combinations_with_replacement.rs @@ -24,6 +24,10 @@ where /// Iterator for `Box<[I]>` valued combinations_with_replacement returned by [`.combinations_with_replacement()`](crate::Itertools::combinations_with_replacement) pub type CombinationsWithReplacement = CombinationsWithReplacementGeneric>; +/// Iterator for const generic combinations_with_replacement returned by [`.array_combinations_with_replacement()`](crate::Itertools::array_combinations_with_replacement) +pub type ArrayCombinationsWithReplacement = + CombinationsWithReplacementGeneric; + impl fmt::Debug for CombinationsWithReplacementGeneric where I: Iterator + fmt::Debug, @@ -33,6 +37,15 @@ where debug_fmt_fields!(CombinationsWithReplacementGeneric, indices, pool, first); } +/// Create a new `ArrayCombinationsWithReplacement`` from a clonable iterator. +pub fn array_combinations_with_replacement( + iter: I, +) -> ArrayCombinationsWithReplacement +where + I::Item: Clone, +{ + ArrayCombinationsWithReplacement::new(iter, [0; K]) +} /// Create a new `CombinationsWithReplacement` from a clonable iterator. pub fn combinations_with_replacement(iter: I, k: usize) -> CombinationsWithReplacement where diff --git a/src/lib.rs b/src/lib.rs index 9f8d1cd1a..7c541ae3e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -153,6 +153,8 @@ pub mod traits { pub use crate::tuple_impl::HomogeneousTuple; } +#[cfg(feature = "use_alloc")] +use crate::combinations_with_replacement::ArrayCombinationsWithReplacement; pub use crate::concat_impl::concat; pub use crate::cons_tuples_impl::cons_tuples; pub use crate::diff::diff_with; @@ -1804,7 +1806,35 @@ pub trait Itertools: Iterator { { combinations_with_replacement::combinations_with_replacement(self, k) } - + /// Return an iterator that iterates over the `k`-length combinations of + /// the elements from an iterator, with replacement. + /// + /// Iterator element type is [Self::Item; K]. The iterator produces a new + /// array per iteration, and clones the iterator elements. + /// + /// ``` + /// use itertools::Itertools; + /// + /// let it = (1..4).array_combinations_with_replacement::<2>(); + /// itertools::assert_equal(it, vec![ + /// [1, 1], + /// [1, 2], + /// [1, 3], + /// [2, 2], + /// [2, 3], + /// [3, 3], + /// ]); + /// ``` + #[cfg(feature = "use_alloc")] + fn array_combinations_with_replacement( + self, + ) -> ArrayCombinationsWithReplacement + where + Self: Sized, + Self::Item: Clone, + { + combinations_with_replacement::array_combinations_with_replacement(self) + } /// Return an iterator adaptor that iterates over all k-permutations of the /// elements from an iterator. /// From bbbce31747fea310c14dfc2b191d0d5e62420242 Mon Sep 17 00:00:00 2001 From: mendelsshop Date: Fri, 13 Jun 2025 10:38:44 -0400 Subject: [PATCH 4/8] Unqualify `borrow()` in combinations_with_replacement --- src/combinations_with_replacement.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/combinations_with_replacement.rs b/src/combinations_with_replacement.rs index c7d402df5..fc9c3ba0c 100644 --- a/src/combinations_with_replacement.rs +++ b/src/combinations_with_replacement.rs @@ -1,5 +1,4 @@ use alloc::boxed::Box; -use core::array; use std::fmt; use std::iter::FusedIterator; @@ -113,9 +112,7 @@ where fn next(&mut self) -> Option { if self.first { // In empty edge cases, stop iterating immediately - if !(core::borrow::Borrow::<[usize]>::borrow(&self.indices).is_empty() - || self.pool.get_next()) - { + if !((&self.indices).borrow().is_empty() || self.pool.get_next()) { return None; } self.first = false; @@ -128,9 +125,7 @@ where fn nth(&mut self, n: usize) -> Option { if self.first { // In empty edge cases, stop iterating immediately - if !(core::borrow::Borrow::<[usize]>::borrow(&self.indices).is_empty() - || self.pool.get_next()) - { + if !((&self.indices).borrow().is_empty() || self.pool.get_next()) { return None; } self.first = false; From 9ce506819a027db16dca3bc5aef58e70b6df277a Mon Sep 17 00:00:00 2001 From: mendelsshop Date: Fri, 13 Jun 2025 11:51:10 -0400 Subject: [PATCH 5/8] Added tests for `array_combinations_with_replacement` --- benches/combinations_with_replacement.rs | 31 ++++++++++++++++++++++++ benches/specializations.rs | 24 ++++++++++++++++++ tests/adaptors_no_collect.rs | 4 +++ tests/laziness.rs | 5 ++++ tests/quick.rs | 5 ++++ tests/specializations.rs | 10 ++++++++ tests/test_std.rs | 24 ++++++++++++++++++ 7 files changed, 103 insertions(+) diff --git a/benches/combinations_with_replacement.rs b/benches/combinations_with_replacement.rs index 8e4fa3dc3..203532714 100644 --- a/benches/combinations_with_replacement.rs +++ b/benches/combinations_with_replacement.rs @@ -30,11 +30,42 @@ fn comb_replacement_n10_k10(c: &mut Criterion) { }) }); } +fn array_comb_replacement_n10_k5(c: &mut Criterion) { + c.bench_function("array comb replacement n10k5", move |b| { + b.iter(|| { + for i in (0..10).array_combinations_with_replacement::<5>() { + black_box(i); + } + }) + }); +} +fn array_comb_replacement_n5_k10(c: &mut Criterion) { + c.bench_function("array comb replacement n5 k10", move |b| { + b.iter(|| { + for i in (0..5).array_combinations_with_replacement::<10>() { + black_box(i); + } + }) + }); +} + +fn array_comb_replacement_n10_k10(c: &mut Criterion) { + c.bench_function("array comb replacement n10 k10", move |b| { + b.iter(|| { + for i in (0..10).array_combinations_with_replacement::<10>() { + black_box(i); + } + }) + }); +} criterion_group!( benches, comb_replacement_n10_k5, comb_replacement_n5_k10, comb_replacement_n10_k10, + array_comb_replacement_n10_k5, + array_comb_replacement_n5_k10, + array_comb_replacement_n10_k10, ); criterion_main!(benches); diff --git a/benches/specializations.rs b/benches/specializations.rs index e70323f8e..2b9eff6d1 100644 --- a/benches/specializations.rs +++ b/benches/specializations.rs @@ -441,6 +441,30 @@ bench_specializations! { } v.iter().combinations_with_replacement(4) } + array_combinations_with_replacement1 { + { + let v = black_box(vec![0; 4096]); + } + v.iter().array_combinations_with_replacement::<1>() + } + array_combinations_with_replacement2 { + { + let v = black_box(vec![0; 90]); + } + v.iter().array_combinations_with_replacement::<2>() + } + array_combinations_with_replacement3 { + { + let v = black_box(vec![0; 28]); + } + v.iter().array_combinations_with_replacement::<3>() + } + array_combinations_with_replacement4 { + { + let v = black_box(vec![0; 16]); + } + v.iter().array_combinations_with_replacement::<4>() + } permutations1 { { let v = black_box(vec![0; 1024]); diff --git a/tests/adaptors_no_collect.rs b/tests/adaptors_no_collect.rs index 977224af2..28b20a441 100644 --- a/tests/adaptors_no_collect.rs +++ b/tests/adaptors_no_collect.rs @@ -49,3 +49,7 @@ fn combinations_no_collect() { fn combinations_with_replacement_no_collect() { no_collect_test(|iter| iter.combinations_with_replacement(5)) } +#[test] +fn array_combinations_with_replacement_no_collect() { + no_collect_test(|iter| iter.array_combinations_with_replacement::<5>()) +} diff --git a/tests/laziness.rs b/tests/laziness.rs index c559d33ad..dfeee68f8 100644 --- a/tests/laziness.rs +++ b/tests/laziness.rs @@ -217,6 +217,11 @@ must_use_tests! { let _ = Panicking.combinations_with_replacement(1); let _ = Panicking.combinations_with_replacement(2); } + array_combinations_with_replacement { + let _ = Panicking.array_combinations_with_replacement::<0>(); + let _ = Panicking.array_combinations_with_replacement::<1>(); + let _ = Panicking.array_combinations_with_replacement::<2>(); + } permutations { let _ = Panicking.permutations(0); let _ = Panicking.permutations(1); diff --git a/tests/quick.rs b/tests/quick.rs index e0632fa47..0af73778a 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -1838,6 +1838,11 @@ quickcheck! { is_fused(a.combinations_with_replacement(3)) } + fn fused_array_combination_with_replacement(a: Iter) -> bool + { + is_fused(a.clone().array_combinations_with_replacement::<1>()) && + is_fused(a.array_combinations_with_replacement::<3>()) + } fn fused_tuple_combination(a: Iter) -> bool { is_fused(a.clone().fuse().tuple_combinations::<(_,)>()) && diff --git a/tests/specializations.rs b/tests/specializations.rs index 44e3cedec..26d1f5367 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -299,6 +299,16 @@ quickcheck! { TestResult::passed() } + fn array_combinations_with_replacement(a: Vec) -> TestResult { + if a.len() > 10 { + return TestResult::discard(); + } + test_specializations(&a.iter().array_combinations_with_replacement::<1>()); + test_specializations(&a.iter().array_combinations_with_replacement::<2>()); + test_specializations(&a.iter().array_combinations_with_replacement::<3>()); + + TestResult::passed() + } fn permutations(a: Vec, n: u8) -> TestResult { if n > 3 || a.len() > 8 { return TestResult::discard(); diff --git a/tests/test_std.rs b/tests/test_std.rs index ad391faad..c0ee373aa 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -1256,6 +1256,30 @@ fn combinations_with_replacement_range_count() { } } +#[test] +#[cfg(not(miri))] +fn array_combinations_with_replacement() { + // Pool smaller than n + it::assert_equal( + (0..1).array_combinations_with_replacement::<2>(), + vec![[0, 0]], + ); + // Pool larger than n + it::assert_equal( + (0..3).array_combinations_with_replacement::<2>(), + vec![[0, 0], [0, 1], [0, 2], [1, 1], [1, 2], [2, 2]], + ); + // Zero size + it::assert_equal((0..3).array_combinations_with_replacement::<0>(), vec![[]]); + // Zero size on empty pool + it::assert_equal((0..0).array_combinations_with_replacement::<0>(), vec![[]]); + // Empty pool + it::assert_equal( + (0..0).array_combinations_with_replacement::<2>(), + vec![] as Vec<[_; 2]>, + ); +} + #[test] fn powerset() { it::assert_equal((0..0).powerset(), vec![vec![]]); From 189c15e9ee33caea0edc283bd20ee803d417d407 Mon Sep 17 00:00:00 2001 From: mendelsshop Date: Fri, 13 Jun 2025 12:16:36 -0400 Subject: [PATCH 6/8] Added appropiate `cfg(use_alloc)` for `combinations_with_replacement` (and its dependencies) --- src/combinations.rs | 3 +++ src/combinations_with_replacement.rs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/combinations.rs b/src/combinations.rs index 2122707e8..2d259f9d1 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "use_alloc")] +use alloc::boxed::Box; use core::array; use core::borrow::BorrowMut; use std::fmt; @@ -52,6 +54,7 @@ pub trait PoolIndex: BorrowMut<[usize]> { self.borrow().len() } } +#[cfg(feature = "use_alloc")] impl PoolIndex for Box<[usize]> { type Item = Vec; diff --git a/src/combinations_with_replacement.rs b/src/combinations_with_replacement.rs index fc9c3ba0c..532cf1b1a 100644 --- a/src/combinations_with_replacement.rs +++ b/src/combinations_with_replacement.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "use_alloc")] use alloc::boxed::Box; use std::fmt; use std::iter::FusedIterator; @@ -21,6 +22,7 @@ where first: bool, } +#[cfg(feature = "use_alloc")] /// Iterator for `Box<[I]>` valued combinations_with_replacement returned by [`.combinations_with_replacement()`](crate::Itertools::combinations_with_replacement) pub type CombinationsWithReplacement = CombinationsWithReplacementGeneric>; /// Iterator for const generic combinations_with_replacement returned by [`.array_combinations_with_replacement()`](crate::Itertools::array_combinations_with_replacement) @@ -45,6 +47,7 @@ where { ArrayCombinationsWithReplacement::new(iter, [0; K]) } +#[cfg(feature = "use_alloc")] /// Create a new `CombinationsWithReplacement` from a clonable iterator. pub fn combinations_with_replacement(iter: I, k: usize) -> CombinationsWithReplacement where From 6607b1c3f0368ab88bee8f3ff6579a01a70d4ee6 Mon Sep 17 00:00:00 2001 From: mendelsshop Date: Fri, 13 Jun 2025 18:00:58 -0400 Subject: [PATCH 7/8] Listen to clippy in `combinations_with_replacement` --- src/combinations_with_replacement.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/combinations_with_replacement.rs b/src/combinations_with_replacement.rs index 532cf1b1a..9cbb7a7b2 100644 --- a/src/combinations_with_replacement.rs +++ b/src/combinations_with_replacement.rs @@ -115,7 +115,7 @@ where fn next(&mut self) -> Option { if self.first { // In empty edge cases, stop iterating immediately - if !((&self.indices).borrow().is_empty() || self.pool.get_next()) { + if !(self.indices.borrow().is_empty() || self.pool.get_next()) { return None; } self.first = false; @@ -128,7 +128,7 @@ where fn nth(&mut self, n: usize) -> Option { if self.first { // In empty edge cases, stop iterating immediately - if !((&self.indices).borrow().is_empty() || self.pool.get_next()) { + if !(self.indices.borrow().is_empty() || self.pool.get_next()) { return None; } self.first = false; From 74b3157f1fd1e1ad6d2e5df7c6be895c369a136a Mon Sep 17 00:00:00 2001 From: mendelsshop Date: Fri, 13 Jun 2025 18:13:58 -0400 Subject: [PATCH 8/8] Removed `cfg(feature="use_alloc")` from `combinations` and `combinations_with_replacements` as they are done at a module level right now. --- src/combinations.rs | 2 -- src/combinations_with_replacement.rs | 3 --- 2 files changed, 5 deletions(-) diff --git a/src/combinations.rs b/src/combinations.rs index 2d259f9d1..7bf9dfeab 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -1,4 +1,3 @@ -#[cfg(feature = "use_alloc")] use alloc::boxed::Box; use core::array; use core::borrow::BorrowMut; @@ -54,7 +53,6 @@ pub trait PoolIndex: BorrowMut<[usize]> { self.borrow().len() } } -#[cfg(feature = "use_alloc")] impl PoolIndex for Box<[usize]> { type Item = Vec; diff --git a/src/combinations_with_replacement.rs b/src/combinations_with_replacement.rs index 9cbb7a7b2..7142681fe 100644 --- a/src/combinations_with_replacement.rs +++ b/src/combinations_with_replacement.rs @@ -1,4 +1,3 @@ -#[cfg(feature = "use_alloc")] use alloc::boxed::Box; use std::fmt; use std::iter::FusedIterator; @@ -22,7 +21,6 @@ where first: bool, } -#[cfg(feature = "use_alloc")] /// Iterator for `Box<[I]>` valued combinations_with_replacement returned by [`.combinations_with_replacement()`](crate::Itertools::combinations_with_replacement) pub type CombinationsWithReplacement = CombinationsWithReplacementGeneric>; /// Iterator for const generic combinations_with_replacement returned by [`.array_combinations_with_replacement()`](crate::Itertools::array_combinations_with_replacement) @@ -47,7 +45,6 @@ where { ArrayCombinationsWithReplacement::new(iter, [0; K]) } -#[cfg(feature = "use_alloc")] /// Create a new `CombinationsWithReplacement` from a clonable iterator. pub fn combinations_with_replacement(iter: I, k: usize) -> CombinationsWithReplacement where