From fdad6252fff04903e75cf04258cfd3a3913123e4 Mon Sep 17 00:00:00 2001 From: Ronno Das Date: Tue, 10 Sep 2024 15:32:14 +0200 Subject: [PATCH 1/9] array_combinations using array::from_fn --- src/array_combinations.rs | 166 ++++++++++++++++++++++++++++++++++++++ src/combinations.rs | 14 +++- src/lazy_buffer.rs | 4 + src/lib.rs | 47 +++++++++++ 4 files changed, 228 insertions(+), 3 deletions(-) create mode 100644 src/array_combinations.rs diff --git a/src/array_combinations.rs b/src/array_combinations.rs new file mode 100644 index 000000000..9de275234 --- /dev/null +++ b/src/array_combinations.rs @@ -0,0 +1,166 @@ +use core::iter::FusedIterator; +use core::{array, fmt}; + +use crate::combinations::{n_and_count, remaining_for}; +use crate::lazy_buffer::LazyBuffer; + +/// An iterator to iterate through all combinations in an iterator of `Clone`-able items that +/// produces arrays of a specific size. +/// +/// See [`.array_combinations()`](crate::Itertools::array_combinations) for more +/// information. +#[derive(Clone)] +#[must_use = "this iterator adaptor is not lazy but does nearly nothing unless consumed"] +pub struct ArrayCombinations +where + I::Item: Clone, +{ + indices: [usize; K], + pool: LazyBuffer, + first: bool, +} + +/// Create a new `ArrayCombinations` from a clonable iterator. +pub fn array_combinations(iter: I) -> ArrayCombinations +where + I::Item: Clone, +{ + let indices = array::from_fn(|i| i); + let pool = LazyBuffer::new(iter); + + ArrayCombinations { + indices, + pool, + first: true, + } +} + +impl ArrayCombinations +where + I::Item: Clone, +{ + /// Returns the (current) length of the pool from which combination elements are + /// selected. This value can change between invocations of [`next`](Combinations::next). + #[inline] + pub fn n(&self) -> usize { + self.pool.len() + } + + /// Initialises the iterator by filling a buffer with elements from the + /// iterator. Returns true if there are no combinations, false otherwise. + fn init(&mut self) -> bool { + self.pool.prefill(K); + let done = K > self.n(); + if !done { + self.first = false; + } + + done + } + + /// Increments indices representing the combination to advance to the next + /// (in lexicographic order by increasing sequence) combination. For example + /// if we have n=4 & k=2 then `[0, 1] -> [0, 2] -> [0, 3] -> [1, 2] -> ...` + /// + /// Returns true if we've run out of combinations, false otherwise. + fn increment_indices(&mut self) -> bool { + if K == 0 { + return true; // Done + } + + // Scan from the end, looking for an index to increment + let mut i: usize = K - 1; + + // Check if we need to consume more from the iterator + if self.indices[i] == self.pool.len() - 1 { + _ = self.pool.get_next(); // may change pool size + } + + while self.indices[i] == i + self.pool.len() - K { + if i > 0 { + i -= 1; + } else { + // Reached the last combination + return true; + } + } + + // Increment index, and reset the ones to its right + self.indices[i] += 1; + for j in i + 1..K { + self.indices[j] = self.indices[j - 1] + 1; + } + + // If we've made it this far, we haven't run out of combos + false + } + + /// Returns the n-th item or the number of successful steps. + pub(crate) fn try_nth(&mut self, n: usize) -> Result<::Item, usize> + where + I::Item: Clone, + { + let done = if self.first { + self.init() + } else { + self.increment_indices() + }; + if done { + return Err(0); + } + for i in 0..n { + if self.increment_indices() { + return Err(i + 1); + } + } + Ok(self.pool.get_array(self.indices)) + } +} + +impl Iterator for ArrayCombinations +where + I::Item: Clone, +{ + type Item = [I::Item; K]; + + fn next(&mut self) -> Option { + let done = if self.first { + self.init() + } else { + self.increment_indices() + }; + + (!done).then(|| self.pool.get_array(self.indices)) + } + + fn nth(&mut self, n: usize) -> Option { + self.try_nth(n).ok() + } + + 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, upp) + } + + #[inline] + fn count(self) -> usize { + n_and_count(self.pool, self.first, &self.indices).1 + } +} + +impl fmt::Debug for ArrayCombinations +where + I: Iterator + fmt::Debug, + I::Item: Clone + fmt::Debug, +{ + debug_fmt_fields!(ArrayCombinations, indices, pool, first); +} + +impl FusedIterator for ArrayCombinations +where + I: FusedIterator, + I::Item: Clone, +{ +} diff --git a/src/combinations.rs b/src/combinations.rs index 6bb2f3ec6..423ffa85b 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -91,8 +91,7 @@ impl Combinations { pool, first, } = self; - let n = pool.count(); - (n, remaining_for(n, first, &indices).unwrap()) + n_and_count(pool, first, &indices) } /// Initialises the iterator by filling a buffer with elements from the @@ -210,8 +209,17 @@ where { } +pub(crate) fn n_and_count( + pool: LazyBuffer, + first: bool, + indices: &[usize], +) -> (usize, usize) { + let n = pool.count(); + (n, remaining_for(n, first, indices).unwrap()) +} + /// For a given size `n`, return the count of remaining combinations or None if it would overflow. -fn remaining_for(n: usize, first: bool, indices: &[usize]) -> Option { +pub(crate) fn remaining_for(n: usize, first: bool, indices: &[usize]) -> Option { let k = indices.len(); if n < k { Some(0) diff --git a/src/lazy_buffer.rs b/src/lazy_buffer.rs index fefcff8f5..fafa5f726 100644 --- a/src/lazy_buffer.rs +++ b/src/lazy_buffer.rs @@ -59,6 +59,10 @@ where pub fn get_at(&self, indices: &[usize]) -> Vec { indices.iter().map(|i| self.buffer[*i].clone()).collect() } + + pub fn get_array(&self, indices: [usize; K]) -> [I::Item; K] { + indices.map(|i| self.buffer[i].clone()) + } } impl Index for LazyBuffer diff --git a/src/lib.rs b/src/lib.rs index 9670390a9..c2bb0d9b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -96,6 +96,7 @@ pub mod structs { FilterOk, Interleave, InterleaveShortest, MapInto, MapOk, Positions, Product, PutBack, TakeWhileRef, TupleCombinations, Update, WhileSome, }; + pub use crate::array_combinations::ArrayCombinations; #[cfg(feature = "use_alloc")] pub use crate::combinations::Combinations; #[cfg(feature = "use_alloc")] @@ -177,6 +178,7 @@ pub use crate::either_or_both::EitherOrBoth; pub mod free; #[doc(inline)] pub use crate::free::*; +mod array_combinations; #[cfg(feature = "use_alloc")] mod combinations; #[cfg(feature = "use_alloc")] @@ -1674,6 +1676,51 @@ pub trait Itertools: Iterator { adaptors::tuple_combinations(self) } + /// Return an iterator adaptor that iterates over the combinations of the + /// elements from an iterator. + /// + /// Iterator element can be any array of type `Self::Item`. + /// + /// # Guarantees + /// + /// If the adapted iterator is deterministic, + /// this iterator adapter yields items in a reliable order. + /// + /// ``` + /// use itertools::Itertools; + /// + /// let mut v = Vec::new(); + /// for [a, b] in (1..5).array_combinations() { + /// v.push([a, b]); + /// } + /// assert_eq!(v, vec![[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]); + /// + /// let mut it = (1..5).array_combinations(); + /// assert_eq!(Some([1, 2, 3]), it.next()); + /// assert_eq!(Some([1, 2, 4]), it.next()); + /// assert_eq!(Some([1, 3, 4]), it.next()); + /// assert_eq!(Some([2, 3, 4]), it.next()); + /// assert_eq!(None, it.next()); + /// + /// // this requires a type hint + /// let it = (1..5).array_combinations::<3>(); + /// itertools::assert_equal(it, vec![[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]]); + /// + /// // you can also specify the complete type + /// use itertools::ArrayCombinations; + /// use std::ops::Range; + /// + /// let it: ArrayCombinations, 3> = (1..5).array_combinations(); + /// itertools::assert_equal(it, vec![[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]]); + /// ``` + fn array_combinations(self) -> ArrayCombinations + where + Self: Sized + Clone, + Self::Item: Clone, + { + array_combinations::array_combinations(self) + } + /// Return an iterator adaptor that iterates over the `k`-length combinations of /// the elements from an iterator. /// From b54b84e04ec0a289d1fa9261791f46247366cfb0 Mon Sep 17 00:00:00 2001 From: Ronno Das Date: Tue, 10 Sep 2024 16:30:18 +0200 Subject: [PATCH 2/9] added specialization tests --- tests/specializations.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/specializations.rs b/tests/specializations.rs index 3e4831024..e6694c8e7 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -273,6 +273,16 @@ quickcheck! { test_specializations(&v.into_iter().intersperse_with(|| 0)); } + fn array_combinations(v: Vec) -> TestResult { + if v.len() > 10 { + return TestResult::discard(); + } + test_specializations(&v.iter().array_combinations::<1>()); + test_specializations(&v.iter().array_combinations::<2>()); + test_specializations(&v.iter().array_combinations::<3>()); + TestResult::passed() + } + fn combinations(a: Vec, n: u8) -> TestResult { if n > 3 || a.len() > 8 { return TestResult::discard(); From 8c0f6487df187c02de94c4e2b2dacf02a4cd78b6 Mon Sep 17 00:00:00 2001 From: Ronno Das Date: Tue, 10 Sep 2024 16:33:19 +0200 Subject: [PATCH 3/9] Documentation for `n_and_count` --- src/combinations.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/combinations.rs b/src/combinations.rs index 423ffa85b..9e7492e95 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -209,6 +209,7 @@ where { } +/// Return the length of the inner iterator and the count of remaining combinations. pub(crate) fn n_and_count( pool: LazyBuffer, first: bool, From 7c692980a3beee7cff0f84661bdba9371848f78e Mon Sep 17 00:00:00 2001 From: Ronno Das Date: Tue, 10 Sep 2024 18:05:51 +0200 Subject: [PATCH 4/9] fixed copy-paste error --- src/array_combinations.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/array_combinations.rs b/src/array_combinations.rs index 9de275234..bd2a067d3 100644 --- a/src/array_combinations.rs +++ b/src/array_combinations.rs @@ -40,7 +40,7 @@ where I::Item: Clone, { /// Returns the (current) length of the pool from which combination elements are - /// selected. This value can change between invocations of [`next`](Combinations::next). + /// selected. This value can change between invocations of [`next`](ArrayCombinations::next). #[inline] pub fn n(&self) -> usize { self.pool.len() From 06c1e30e78cf3ad8e4f524008b034cc38d31d164 Mon Sep 17 00:00:00 2001 From: Ronno Das Date: Tue, 10 Sep 2024 18:08:14 +0200 Subject: [PATCH 5/9] restricted to `use_alloc` --- src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index c2bb0d9b8..25c94a3ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -96,6 +96,7 @@ pub mod structs { FilterOk, Interleave, InterleaveShortest, MapInto, MapOk, Positions, Product, PutBack, TakeWhileRef, TupleCombinations, Update, WhileSome, }; + #[cfg(feature = "use_alloc")] pub use crate::array_combinations::ArrayCombinations; #[cfg(feature = "use_alloc")] pub use crate::combinations::Combinations; @@ -178,6 +179,7 @@ pub use crate::either_or_both::EitherOrBoth; pub mod free; #[doc(inline)] pub use crate::free::*; +#[cfg(feature = "use_alloc")] mod array_combinations; #[cfg(feature = "use_alloc")] mod combinations; @@ -1713,6 +1715,7 @@ pub trait Itertools: Iterator { /// let it: ArrayCombinations, 3> = (1..5).array_combinations(); /// itertools::assert_equal(it, vec![[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]]); /// ``` + #[cfg(feature = "use_alloc")] fn array_combinations(self) -> ArrayCombinations where Self: Sized + Clone, From ae940b4a31d4db5e17000d761c6e102fd7413ca5 Mon Sep 17 00:00:00 2001 From: Ronno Das Date: Tue, 10 Sep 2024 22:43:58 +0200 Subject: [PATCH 6/9] refactored to share code --- src/array_combinations.rs | 166 ----------------------------------- src/combinations.rs | 180 ++++++++++++++++++++++++++------------ src/lib.rs | 8 +- 3 files changed, 126 insertions(+), 228 deletions(-) delete mode 100644 src/array_combinations.rs diff --git a/src/array_combinations.rs b/src/array_combinations.rs deleted file mode 100644 index bd2a067d3..000000000 --- a/src/array_combinations.rs +++ /dev/null @@ -1,166 +0,0 @@ -use core::iter::FusedIterator; -use core::{array, fmt}; - -use crate::combinations::{n_and_count, remaining_for}; -use crate::lazy_buffer::LazyBuffer; - -/// An iterator to iterate through all combinations in an iterator of `Clone`-able items that -/// produces arrays of a specific size. -/// -/// See [`.array_combinations()`](crate::Itertools::array_combinations) for more -/// information. -#[derive(Clone)] -#[must_use = "this iterator adaptor is not lazy but does nearly nothing unless consumed"] -pub struct ArrayCombinations -where - I::Item: Clone, -{ - indices: [usize; K], - pool: LazyBuffer, - first: bool, -} - -/// Create a new `ArrayCombinations` from a clonable iterator. -pub fn array_combinations(iter: I) -> ArrayCombinations -where - I::Item: Clone, -{ - let indices = array::from_fn(|i| i); - let pool = LazyBuffer::new(iter); - - ArrayCombinations { - indices, - pool, - first: true, - } -} - -impl ArrayCombinations -where - I::Item: Clone, -{ - /// Returns the (current) length of the pool from which combination elements are - /// selected. This value can change between invocations of [`next`](ArrayCombinations::next). - #[inline] - pub fn n(&self) -> usize { - self.pool.len() - } - - /// Initialises the iterator by filling a buffer with elements from the - /// iterator. Returns true if there are no combinations, false otherwise. - fn init(&mut self) -> bool { - self.pool.prefill(K); - let done = K > self.n(); - if !done { - self.first = false; - } - - done - } - - /// Increments indices representing the combination to advance to the next - /// (in lexicographic order by increasing sequence) combination. For example - /// if we have n=4 & k=2 then `[0, 1] -> [0, 2] -> [0, 3] -> [1, 2] -> ...` - /// - /// Returns true if we've run out of combinations, false otherwise. - fn increment_indices(&mut self) -> bool { - if K == 0 { - return true; // Done - } - - // Scan from the end, looking for an index to increment - let mut i: usize = K - 1; - - // Check if we need to consume more from the iterator - if self.indices[i] == self.pool.len() - 1 { - _ = self.pool.get_next(); // may change pool size - } - - while self.indices[i] == i + self.pool.len() - K { - if i > 0 { - i -= 1; - } else { - // Reached the last combination - return true; - } - } - - // Increment index, and reset the ones to its right - self.indices[i] += 1; - for j in i + 1..K { - self.indices[j] = self.indices[j - 1] + 1; - } - - // If we've made it this far, we haven't run out of combos - false - } - - /// Returns the n-th item or the number of successful steps. - pub(crate) fn try_nth(&mut self, n: usize) -> Result<::Item, usize> - where - I::Item: Clone, - { - let done = if self.first { - self.init() - } else { - self.increment_indices() - }; - if done { - return Err(0); - } - for i in 0..n { - if self.increment_indices() { - return Err(i + 1); - } - } - Ok(self.pool.get_array(self.indices)) - } -} - -impl Iterator for ArrayCombinations -where - I::Item: Clone, -{ - type Item = [I::Item; K]; - - fn next(&mut self) -> Option { - let done = if self.first { - self.init() - } else { - self.increment_indices() - }; - - (!done).then(|| self.pool.get_array(self.indices)) - } - - fn nth(&mut self, n: usize) -> Option { - self.try_nth(n).ok() - } - - 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, upp) - } - - #[inline] - fn count(self) -> usize { - n_and_count(self.pool, self.first, &self.indices).1 - } -} - -impl fmt::Debug for ArrayCombinations -where - I: Iterator + fmt::Debug, - I::Item: Clone + fmt::Debug, -{ - debug_fmt_fields!(ArrayCombinations, indices, pool, first); -} - -impl FusedIterator for ArrayCombinations -where - I: FusedIterator, - I::Item: Clone, -{ -} diff --git a/src/combinations.rs b/src/combinations.rs index 9e7492e95..0b2806f68 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -1,3 +1,5 @@ +use core::array; +use core::ops::IndexMut; use std::fmt; use std::iter::FusedIterator; @@ -6,45 +8,112 @@ use alloc::vec::Vec; use crate::adaptors::checked_binomial; +/// Iterator for `Vec` valued combinations returned by [`.combinations()`](crate::Itertools::combinations) +pub type Combinations = CombinationsGeneric>; +/// Iterator for const generic combinations returned by [`.array_combinations()`](crate::Itertools::array_combinations) +pub type ArrayCombinations = CombinationsGeneric; + +/// Create a new `Combinations` from a clonable iterator. +pub fn combinations(iter: I, k: usize) -> Combinations +where + I::Item: Clone, +{ + Combinations::new(iter, (0..k).collect()) +} + +/// Create a new `ArrayCombinations` from a clonable iterator. +pub fn array_combinations(iter: I) -> ArrayCombinations +where + I::Item: Clone, +{ + ArrayCombinations::new(iter, array::from_fn(|i| i)) +} + /// An iterator to iterate through all the `k`-length combinations in an iterator. /// -/// See [`.combinations()`](crate::Itertools::combinations) for more information. +/// See [`.combinations()`](crate::Itertools::combinations) and [`.array_combinations()`](crate::Itertools::array_combinations) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] -pub struct Combinations { - indices: Vec, +pub struct CombinationsGeneric { + indices: Idx, pool: LazyBuffer, first: bool, } -impl Clone for Combinations +pub trait PoolIndex: IndexMut { + type Item; + + fn len(&self) -> usize; + fn extract_item>(&self, pool: &LazyBuffer) -> Self::Item + where + T: Clone; + fn as_slice(&self) -> &[usize]; +} + +impl PoolIndex for Vec { + type Item = Vec; + + fn len(&self) -> usize { + self.len() + } + fn extract_item>(&self, pool: &LazyBuffer) -> Vec + where + T: Clone, + { + pool.get_at(self) + } + + fn as_slice(&self) -> &[usize] { + self + } +} + +impl PoolIndex for [usize; K] { + type Item = [T; K]; + + fn len(&self) -> usize { + K + } + + fn extract_item>(&self, pool: &LazyBuffer) -> [T; K] + where + T: Clone, + { + pool.get_array(*self) + } + + fn as_slice(&self) -> &[usize] { + self + } +} + +impl Clone for CombinationsGeneric where - I: Clone + Iterator, + I: Iterator + Clone, I::Item: Clone, + Idx: Clone, { clone_fields!(indices, pool, first); } -impl fmt::Debug for Combinations +impl fmt::Debug for CombinationsGeneric where I: Iterator + fmt::Debug, I::Item: fmt::Debug, + Idx: fmt::Debug, { debug_fmt_fields!(Combinations, indices, pool, first); } -/// Create a new `Combinations` from a clonable iterator. -pub fn combinations(iter: I, k: usize) -> Combinations -where - I: Iterator, -{ - Combinations { - indices: (0..k).collect(), - pool: LazyBuffer::new(iter), - first: true, +impl> CombinationsGeneric { + /// 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 Combinations { /// Returns the length of a combination produced by this iterator. #[inline] pub fn k(&self) -> usize { @@ -64,34 +133,17 @@ impl Combinations { &self.pool } - /// Resets this `Combinations` back to an initial state for combinations of length - /// `k` over the same pool data source. If `k` is larger than the current length - /// of the data pool an attempt is made to prefill the pool so that it holds `k` - /// elements. - pub(crate) fn reset(&mut self, k: usize) { - self.first = true; - - if k < self.indices.len() { - self.indices.truncate(k); - for i in 0..k { - self.indices[i] = i; - } - } else { - for i in 0..self.indices.len() { - self.indices[i] = i; - } - self.indices.extend(self.indices.len()..k); - self.pool.prefill(k); - } - } - + /// Return the length of the inner iterator and the count of remaining combinations. pub(crate) fn n_and_count(self) -> (usize, usize) { let Self { indices, pool, first, } = self; - n_and_count(pool, first, &indices) + { + let n = pool.count(); + (n, remaining_for(n, first, indices.as_slice()).unwrap()) + } } /// Initialises the iterator by filling a buffer with elements from the @@ -112,7 +164,7 @@ impl Combinations { /// /// Returns true if we've run out of combinations, false otherwise. fn increment_indices(&mut self) -> bool { - if self.indices.is_empty() { + if self.indices.len() == 0 { return true; // Done } @@ -144,8 +196,9 @@ impl Combinations { } /// Returns the n-th item or the number of successful steps. - pub(crate) fn try_nth(&mut self, n: usize) -> Result<::Item, usize> + pub(crate) fn try_nth(&mut self, n: usize) -> Result where + I: Iterator, I::Item: Clone, { let done = if self.first { @@ -161,16 +214,17 @@ impl Combinations { return Err(i + 1); } } - Ok(self.pool.get_at(&self.indices)) + Ok(self.indices.extract_item(&self.pool)) } } -impl Iterator for Combinations +impl Iterator for CombinationsGeneric where I: Iterator, I::Item: Clone, + Idx: PoolIndex, { - type Item = Vec; + type Item = Idx::Item; fn next(&mut self) -> Option { let done = if self.first { self.init() @@ -182,7 +236,7 @@ where return None; } - Some(self.pool.get_at(&self.indices)) + Some(self.indices.extract_item(&self.pool)) } fn nth(&mut self, n: usize) -> Option { @@ -191,8 +245,8 @@ where 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.as_slice()).unwrap_or(usize::MAX); + upp = upp.and_then(|upp| remaining_for(upp, self.first, self.indices.as_slice())); (low, upp) } @@ -202,21 +256,35 @@ where } } -impl FusedIterator for Combinations +impl FusedIterator for CombinationsGeneric where I: Iterator, I::Item: Clone, + Idx: PoolIndex, { } -/// Return the length of the inner iterator and the count of remaining combinations. -pub(crate) fn n_and_count( - pool: LazyBuffer, - first: bool, - indices: &[usize], -) -> (usize, usize) { - let n = pool.count(); - (n, remaining_for(n, first, indices).unwrap()) +impl Combinations { + /// Resets this `Combinations` back to an initial state for combinations of length + /// `k` over the same pool data source. If `k` is larger than the current length + /// of the data pool an attempt is made to prefill the pool so that it holds `k` + /// elements. + pub(crate) fn reset(&mut self, k: usize) { + self.first = true; + + if k < self.indices.len() { + self.indices.truncate(k); + for i in 0..k { + self.indices[i] = i; + } + } else { + for i in 0..self.indices.len() { + self.indices[i] = i; + } + self.indices.extend(self.indices.len()..k); + self.pool.prefill(k); + } + } } /// For a given size `n`, return the count of remaining combinations or None if it would overflow. diff --git a/src/lib.rs b/src/lib.rs index 25c94a3ca..ccc4bd7fa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,9 +97,7 @@ pub mod structs { TakeWhileRef, TupleCombinations, Update, WhileSome, }; #[cfg(feature = "use_alloc")] - pub use crate::array_combinations::ArrayCombinations; - #[cfg(feature = "use_alloc")] - pub use crate::combinations::Combinations; + pub use crate::combinations::{ArrayCombinations, Combinations}; #[cfg(feature = "use_alloc")] pub use crate::combinations_with_replacement::CombinationsWithReplacement; pub use crate::cons_tuples_impl::ConsTuples; @@ -180,8 +178,6 @@ pub mod free; #[doc(inline)] pub use crate::free::*; #[cfg(feature = "use_alloc")] -mod array_combinations; -#[cfg(feature = "use_alloc")] mod combinations; #[cfg(feature = "use_alloc")] mod combinations_with_replacement; @@ -1721,7 +1717,7 @@ pub trait Itertools: Iterator { Self: Sized + Clone, Self::Item: Clone, { - array_combinations::array_combinations(self) + combinations::array_combinations(self) } /// Return an iterator adaptor that iterates over the `k`-length combinations of From 96c43dccf089eb1cf5cf8fa1efbcf50cc2e77d9c Mon Sep 17 00:00:00 2001 From: Ronno Das Date: Wed, 11 Sep 2024 21:13:22 +0200 Subject: [PATCH 7/9] IndexMut -> BorrowMut --- src/combinations.rs | 51 +++++++++++++++++++-------------------------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/src/combinations.rs b/src/combinations.rs index 0b2806f68..c8ec14142 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -1,5 +1,5 @@ use core::array; -use core::ops::IndexMut; +use core::borrow::BorrowMut; use std::fmt; use std::iter::FusedIterator; @@ -39,41 +39,33 @@ pub struct CombinationsGeneric { first: bool, } -pub trait PoolIndex: IndexMut { +pub trait PoolIndex: BorrowMut<[usize]> { type Item; - fn len(&self) -> usize; fn extract_item>(&self, pool: &LazyBuffer) -> Self::Item where T: Clone; - fn as_slice(&self) -> &[usize]; + + #[inline] + fn len(&self) -> usize { + self.borrow().len() + } } impl PoolIndex for Vec { type Item = Vec; - fn len(&self) -> usize { - self.len() - } fn extract_item>(&self, pool: &LazyBuffer) -> Vec where T: Clone, { pool.get_at(self) } - - fn as_slice(&self) -> &[usize] { - self - } } impl PoolIndex for [usize; K] { type Item = [T; K]; - fn len(&self) -> usize { - K - } - fn extract_item>(&self, pool: &LazyBuffer) -> [T; K] where T: Clone, @@ -81,8 +73,8 @@ impl PoolIndex for [usize; K] { pool.get_array(*self) } - fn as_slice(&self) -> &[usize] { - self + fn len(&self) -> usize { + K } } @@ -142,7 +134,7 @@ impl> CombinationsGeneric { } = self; { let n = pool.count(); - (n, remaining_for(n, first, indices.as_slice()).unwrap()) + (n, remaining_for(n, first, indices.borrow()).unwrap()) } } @@ -164,19 +156,21 @@ impl> CombinationsGeneric { /// /// Returns true if we've run out of combinations, false otherwise. fn increment_indices(&mut self) -> bool { - if self.indices.len() == 0 { + // Borrow once instead of noise each time it's indexed + let indices = self.indices.borrow_mut(); + + if indices.is_empty() { return true; // Done } - // Scan from the end, looking for an index to increment - let mut i: usize = self.indices.len() - 1; + let mut i: usize = indices.len() - 1; // Check if we need to consume more from the iterator - if self.indices[i] == self.pool.len() - 1 { + if indices[i] == self.pool.len() - 1 { self.pool.get_next(); // may change pool size } - while self.indices[i] == i + self.pool.len() - self.indices.len() { + while indices[i] == i + self.pool.len() - indices.len() { if i > 0 { i -= 1; } else { @@ -186,11 +180,10 @@ impl> CombinationsGeneric { } // Increment index, and reset the ones to its right - self.indices[i] += 1; - for j in i + 1..self.indices.len() { - self.indices[j] = self.indices[j - 1] + 1; + indices[i] += 1; + for j in i + 1..indices.len() { + indices[j] = indices[j - 1] + 1; } - // If we've made it this far, we haven't run out of combos false } @@ -245,8 +238,8 @@ where fn size_hint(&self) -> (usize, Option) { let (mut low, mut upp) = self.pool.size_hint(); - low = remaining_for(low, self.first, self.indices.as_slice()).unwrap_or(usize::MAX); - upp = upp.and_then(|upp| remaining_for(upp, self.first, self.indices.as_slice())); + 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) } From 25a64132d76231ba6e397fa3f64994e05624a9a1 Mon Sep 17 00:00:00 2001 From: Ronno Das Date: Wed, 11 Sep 2024 22:24:41 +0200 Subject: [PATCH 8/9] "nitpicks" --- src/combinations.rs | 15 ++++----------- src/lib.rs | 3 ++- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/combinations.rs b/src/combinations.rs index c8ec14142..f5aa4630e 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -46,7 +46,6 @@ pub trait PoolIndex: BorrowMut<[usize]> { where T: Clone; - #[inline] fn len(&self) -> usize { self.borrow().len() } @@ -72,10 +71,6 @@ impl PoolIndex for [usize; K] { { pool.get_array(*self) } - - fn len(&self) -> usize { - K - } } impl Clone for CombinationsGeneric @@ -132,10 +127,8 @@ impl> CombinationsGeneric { pool, first, } = self; - { - let n = pool.count(); - (n, remaining_for(n, first, indices.borrow()).unwrap()) - } + let n = pool.count(); + (n, remaining_for(n, first, indices.borrow()).unwrap()) } /// Initialises the iterator by filling a buffer with elements from the @@ -189,7 +182,7 @@ impl> CombinationsGeneric { } /// Returns the n-th item or the number of successful steps. - pub(crate) fn try_nth(&mut self, n: usize) -> Result + pub(crate) fn try_nth(&mut self, n: usize) -> Result<::Item, usize> where I: Iterator, I::Item: Clone, @@ -281,7 +274,7 @@ impl Combinations { } /// For a given size `n`, return the count of remaining combinations or None if it would overflow. -pub(crate) fn remaining_for(n: usize, first: bool, indices: &[usize]) -> Option { +fn remaining_for(n: usize, first: bool, indices: &[usize]) -> Option { let k = indices.len(); if n < k { Some(0) diff --git a/src/lib.rs b/src/lib.rs index ccc4bd7fa..dc316d847 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1677,7 +1677,8 @@ pub trait Itertools: Iterator { /// Return an iterator adaptor that iterates over the combinations of the /// elements from an iterator. /// - /// Iterator element can be any array of type `Self::Item`. + /// Iterator element type is [Self::Item; K]. The iterator produces a new + /// array per iteration, and clones the iterator elements. /// /// # Guarantees /// From b5e5cd1262b5ab0ac3f1cf8de02c7377a6f578a3 Mon Sep 17 00:00:00 2001 From: Ronno Das Date: Fri, 20 Sep 2024 07:45:34 +0200 Subject: [PATCH 9/9] doc for added trait --- src/combinations.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/combinations.rs b/src/combinations.rs index f5aa4630e..54a027551 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -39,6 +39,8 @@ pub struct CombinationsGeneric { first: bool, } +/// A type holding indices of elements in a pool or buffer of items from an inner iterator +/// and used to pick out different combinations in a generic way. pub trait PoolIndex: BorrowMut<[usize]> { type Item;