From ff85f2032fa9c8c3184c96faea9c2ccf6d4e1fa0 Mon Sep 17 00:00:00 2001 From: Iago-lito Date: Mon, 20 May 2024 19:24:38 +0200 Subject: [PATCH 1/6] =?UTF-8?q?=F0=9F=9A=A7=20Implementing=20cartesian=20p?= =?UTF-8?q?ower.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cartesian_power.rs | 356 +++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 20 +++ 2 files changed, 376 insertions(+) create mode 100644 src/cartesian_power.rs diff --git a/src/cartesian_power.rs b/src/cartesian_power.rs new file mode 100644 index 000000000..3cf3562ab --- /dev/null +++ b/src/cartesian_power.rs @@ -0,0 +1,356 @@ +use alloc::vec::Vec; +use std::fmt; +use std::iter::FusedIterator; + +/// An adaptor iterating through all the ordered `n`-length lists of items +/// yielded by the underlying iterator, including repetitions. +/// +/// See [`.cartesian_power()`](crate::Itertools::cartesian_power) +/// for more information. +#[derive(Clone)] +#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] +pub struct CartesianPower +where + I: Iterator, + I::Item: Clone, +{ + pow: usize, + iter: Option, // Inner iterator. Forget once consumed after 'base' iterations. + items: Vec, // Fill from iter. Clear once adaptor is exhausted. Final length is 'base'. + indices: Vec, // Indices just yielded. Clear once adaptor is exhausted. Length is 'pow'. +} + +/// Create a new `CartesianPower` from an iterator of clonables. +pub fn cartesian_power(iter: I, pow: usize) -> CartesianPower +where + I: Iterator, + I::Item: Clone, +{ + CartesianPower { + pow, + iter: Some(iter), + items: Vec::new(), + indices: Vec::new(), + } +} + +impl CartesianPower +where + I: Iterator, + I::Item: Clone, +{ + /// Increments internal indices to advance to the next list to be yielded. + /// This collects new items from the underlying iterator + /// if they were not all already collected. + /// + /// Returns None if we've run out of possible lists, + /// otherwise return refs to the indices to yield next, + /// valid within the collected items slice also returned. + fn increment_indices(&mut self) -> Option<(&[usize], &[I::Item])> { + let Self { + pow, + iter, + items, + indices, + } = self; + match (*pow, iter, items.len()) { + // Final stable state: underlying iterator and items forgotten. + (_, None, 0) => None, + + // Degenerated 0th power iteration. + (0, Some(_), _) => { + self.iter = None; // Forget without even consuming. + Some((indices, items)) + } + + (pow, Some(it), 0) => { + // Check whether there is at least one element in the iterator. + if let Some(first) = it.next() { + // Allocate buffer to hold items about to be yielded. + items.reserve_exact(it.size_hint().0); + items.push(first); + // Same for indices to be yielded. + indices.reserve_exact(pow); + for _ in 0..pow { + indices.push(0); + } + return Some((indices, items)); + } + // Degenerated iteration over an empty set, yet with non-null power. + self.iter = None; + None + } + + (pow, Some(it), base) => { + // We are still unsure whether all items have been collected. + // As a consequence, 'base' is still uncertain, + // but then we know that indices haven't started wrapping around yet. + if let Some(next) = it.next() { + items.push(next); + indices[pow - 1] += 1; + return Some((indices, items)); + } + + // All items have just been collected. + self.iter = None; + if base == 1 || pow == 1 { + // End of iteration. + items.clear(); + indices.clear(); + return None; + } + + // First wrap around. + indices[pow - 1] = 0; + indices[pow - 2] += 1; + Some((indices, items)) + } + + (_, None, b) => { + // Keep yielding items list, incrementing indices rightmost first. + for index in indices.iter_mut().rev() { + *index += 1; + if *index < b { + return Some((indices, items)); + } + *index = 0; // Wrap and increment left. + } + items.clear(); + indices.clear(); + None + } + } + } + + /// Same as [`increment_indices`], but does n increments at once. + fn increment_indices_by_n(&mut self, n: usize) -> Option<(&[usize], &[I::Item])> { + todo!() + } +} + +impl Iterator for CartesianPower +where + I: Iterator, + I::Item: Clone, +{ + type Item = Vec; + + fn next(&mut self) -> Option { + // If anything to yield, + // clone the correct 'pow' instances of collected items + // into a freshly allocated vector. + self.increment_indices().map(|(indices, items)| { + indices + .iter() + .map(|&i| items[i].clone()) + .collect::>() + }) + } + + fn nth(&mut self, n: usize) -> Option { + self.increment_indices_by_n(n).map(|(indices, items)| { + indices + .iter() + .map(|&i| items[i].clone()) + .collect::>() + }) + } + + fn size_hint(&self) -> (usize, Option) { + todo!() + } + + fn count(self) -> usize { + todo!() + } +} + +// Elide underlying iterator from the debug display. +impl fmt::Debug for CartesianPower +where + I: Iterator + fmt::Debug, + I::Item: fmt::Debug + Clone, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + pow, + iter, + items, + indices, + } = self; + f.debug_struct("CartesianPower") + .field("pow", pow) + .field("iter", &iter.is_some()) + .field("items", items) + .field("indices", indices) + .finish() + } +} + +impl FusedIterator for CartesianPower +where + I: Iterator, + I::Item: Clone, +{ +} + +#[cfg(test)] +mod tests { + //! Use chars and string to ease testing of every yielded iterator values. + + use super::CartesianPower; + use crate::Itertools; + use core::str::Chars; + + fn check_fused(mut exhausted_it: CartesianPower, context: String) { + for i in 0..100 { + let act = exhausted_it.next(); + assert!( + act.is_none(), + "Iteration {} after expected exhaustion of {} \ + yielded {:?} instead of None. ", + i, + context, + act, + ); + } + } + + #[test] + fn basic() { + fn check(origin: &str, pow: usize, expected: &[&str]) { + let mut it = origin.chars().cartesian_power(pow); + let mut i = 0; + for exp in expected { + let act = it.next(); + if act != Some(exp.chars().collect()) { + panic!( + "Failed iteration {} for {:?}^{}. \ + Expected {:?}, got {:?} instead.", + i, origin, pow, exp, act, + ); + } + i += 1; + } + check_fused(it, format!("iteration {} or {:?}^{}", i, origin, pow)); + } + + // Empty underlying iterator. + check("", 0, &[""]); + check("", 1, &[]); + check("", 2, &[]); + check("", 3, &[]); + + // Singleton underlying iterator. + check("a", 0, &[""]); + check("a", 1, &["a"]); + check("a", 2, &["aa"]); + check("a", 3, &["aaa"]); + + // Underlying pair. + check("ab", 0, &[""]); + check("ab", 1, &["a", "b"]); + check("ab", 2, &["aa", "ab", "ba", "bb"]); + check( + "ab", + 3, + &["aaa", "aab", "aba", "abb", "baa", "bab", "bba", "bbb"], + ); + + // Underlying triplet. + check("abc", 0, &[""]); + check("abc", 1, &["a", "b", "c"]); + check( + "abc", + 2, + &["aa", "ab", "ac", "ba", "bb", "bc", "ca", "cb", "cc"], + ); + check( + "abc", + 3, + &[ + "aaa", "aab", "aac", "aba", "abb", "abc", "aca", "acb", "acc", "baa", "bab", "bac", + "bba", "bbb", "bbc", "bca", "bcb", "bcc", "caa", "cab", "cac", "cba", "cbb", "cbc", + "cca", "ccb", "ccc", + ], + ); + } + + #[test] + fn nth() { + fn check(origin: &str, pow: usize, expected: &[(usize, Option<&str>)]) { + let mut it = origin.chars().cartesian_power(pow); + let mut total_n = Vec::new(); + for &(n, exp) in expected { + let act = it.nth(n); + total_n.push(n); + if act != exp.map(|s| s.chars().collect::>()) { + panic!( + "Failed nth({}) iteration for {:?}^{}. \ + Expected {:?}, got {:?} instead.", + total_n + .iter() + .map(ToString::to_string) + .collect::>() + .join(", "), + origin, + pow, + exp, + act, + ); + } + } + check_fused( + it, + format!( + "nth({}) iteration of {:?}^{}", + total_n + .iter() + .map(ToString::to_string) + .collect::>() + .join(", "), + origin, + pow + ), + ); + } + + // HERE: make it work with the new implementation. + + // Check degenerated cases. + check("", 0, &[(0, Some("")), (0, None)]); + check("", 0, &[(0, Some("")), (1, None)]); + check("", 0, &[(0, Some("")), (2, None)]); + check("", 0, &[(1, None), (0, None)]); + check("", 0, &[(1, None), (1, None)]); + check("", 0, &[(1, None), (2, None)]); + check("", 0, &[(2, None), (0, None)]); + check("", 0, &[(2, None), (1, None)]); + check("", 0, &[(2, None), (2, None)]); + + check("a", 0, &[(0, Some("")), (0, None)]); + check("a", 0, &[(0, Some("")), (1, None)]); + check("a", 0, &[(0, Some("")), (2, None)]); + check("a", 0, &[(1, None), (0, None)]); + check("a", 0, &[(1, None), (1, None)]); + check("a", 0, &[(1, None), (2, None)]); + check("a", 0, &[(2, None), (0, None)]); + check("a", 0, &[(2, None), (1, None)]); + check("a", 0, &[(2, None), (2, None)]); + + // Unit power. + check("a", 1, &[(0, Some("a")), (0, None)]); + check("a", 1, &[(0, Some("a")), (1, None)]); + check("a", 1, &[(0, Some("a")), (2, None)]); + check("a", 1, &[(1, None), (0, None)]); + check("a", 1, &[(1, None), (1, None)]); + check("a", 1, &[(1, None), (2, None)]); + check("a", 1, &[(2, None), (0, None)]); + check("a", 1, &[(2, None), (1, None)]); + check("a", 1, &[(2, None), (2, None)]); + + check("ab", 1, &[(0, Some("a")), (0, Some("b")), (0, None)]); + check("ab", 1, &[(1, Some("b")), (0, None), (0, None)]); + check("ab", 1, &[(2, None), (0, None), (0, None)]); + } +} diff --git a/src/lib.rs b/src/lib.rs index 834a48dea..8af602df0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,6 +60,7 @@ extern crate alloc; #[cfg(feature = "use_alloc")] use alloc::{collections::VecDeque, string::String, vec::Vec}; +use cartesian_power::CartesianPower; pub use either::Either; use core::borrow::Borrow; @@ -178,6 +179,8 @@ pub mod free; #[doc(inline)] pub use crate::free::*; #[cfg(feature = "use_alloc")] +mod cartesian_power; +#[cfg(feature = "use_alloc")] mod combinations; #[cfg(feature = "use_alloc")] mod combinations_with_replacement; @@ -1792,6 +1795,23 @@ pub trait Itertools: Iterator { combinations_with_replacement::combinations_with_replacement(self, k) } + /// Returns an iterator yielding the successive elements + /// of the cartesian power of the set described by the original iterator. + /// + /// ``` + /// use itertools::Itertools; + /// + /// TODO: illustrative example. + /// ``` + #[cfg(feature = "use_alloc")] + fn cartesian_power(self, pow: usize) -> CartesianPower + where + Self: Sized, + Self::Item: Clone, + { + cartesian_power::cartesian_power(self, pow) + } + /// Return an iterator adaptor that iterates over all k-permutations of the /// elements from an iterator. /// From 1c47a74c5a08430c4ab4db5e720867388556115d Mon Sep 17 00:00:00 2001 From: Iago-lito Date: Tue, 10 Sep 2024 22:36:18 +0200 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=9A=A7=20Test=20cycling=20iterator=20?= =?UTF-8?q?instead.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cartesian_power.rs | 224 +++++++++++++++++++++++------------------ 1 file changed, 126 insertions(+), 98 deletions(-) diff --git a/src/cartesian_power.rs b/src/cartesian_power.rs index 3cf3562ab..3954a3e4c 100644 --- a/src/cartesian_power.rs +++ b/src/cartesian_power.rs @@ -1,6 +1,5 @@ use alloc::vec::Vec; use std::fmt; -use std::iter::FusedIterator; /// An adaptor iterating through all the ordered `n`-length lists of items /// yielded by the underlying iterator, including repetitions. @@ -15,9 +14,19 @@ where I::Item: Clone, { pow: usize, - iter: Option, // Inner iterator. Forget once consumed after 'base' iterations. - items: Vec, // Fill from iter. Clear once adaptor is exhausted. Final length is 'base'. - indices: Vec, // Indices just yielded. Clear once adaptor is exhausted. Length is 'pow'. + iter: Option, // Inner iterator. Forget once consumed after 'base' iterations. + items: Option>, // Fill from iter. Final length is 'base'. + // None means that collection has not started yet. + // Some(empty) means that collection would have started but pow = 0. + + // Indices just yielded. Length is 'pow'. + // 0 0 .. 0 0 means that the first combination has been yielded. + // 0 0 .. 0 1 means that the second combination has been yielded. + // m m .. m m means that the last combination has just been yielded (m = base - 1). + // b 0 .. 0 0 means that 'None' has just been yielded (b = base). + // The latter is a special value marking the renewal of the iterator, + // which can cycle again through another full round, ad libitum. + indices: Vec, } /// Create a new `CartesianPower` from an iterator of clonables. @@ -29,7 +38,7 @@ where CartesianPower { pow, iter: Some(iter), - items: Vec::new(), + items: None, indices: Vec::new(), } } @@ -53,37 +62,57 @@ where items, indices, } = self; - match (*pow, iter, items.len()) { - // Final stable state: underlying iterator and items forgotten. - (_, None, 0) => None, + println!( + "^{pow}: {} {indices:?}\t\t{:?}", + if iter.is_some() { 'S' } else { 'N' }, + items.as_ref().map(Vec::len) + ); - // Degenerated 0th power iteration. - (0, Some(_), _) => { - self.iter = None; // Forget without even consuming. - Some((indices, items)) + // (weird 'items @' bindings circumvent NLL limitations, unneeded with polonius) + match (*pow, iter, &mut *items) { + // First iteration with degenerated 0th power. + (0, Some(_), items @ None) => { + self.iter = None; // Forget about underlying iteration immediately. + let empty = items.insert(Vec::new()); // Raise this value as a boolean flag. + Some((indices, empty)) // Yield empty list. } - (pow, Some(it), 0) => { + // Subsequent degenerated 0th power iteration. + // Use the Some<(empty)Vec> as a flag to alternate between yielding [] or None. + (0, None, items @ Some(_)) => { + *items = None; + None + } + (0, None, items @ None) => Some((indices, items.insert(Vec::new()))), + + // First iteration in the general case. + (pow, Some(it), items @ None) => { // Check whether there is at least one element in the iterator. if let Some(first) = it.next() { - // Allocate buffer to hold items about to be yielded. - items.reserve_exact(it.size_hint().0); - items.push(first); - // Same for indices to be yielded. + items // Collect it. + .insert(Vec::with_capacity(it.size_hint().0)) + .push(first); + // Prepare indices to be yielded. indices.reserve_exact(pow); for _ in 0..pow { indices.push(0); } - return Some((indices, items)); + Some((indices, items.as_ref().unwrap())) + } else { + // Degenerated iteration over an empty set: + // 'base = 0', yet with non-null power. + self.iter = None; + None } - // Degenerated iteration over an empty set, yet with non-null power. - self.iter = None; - None } - (pow, Some(it), base) => { + // Stable iteration in the degenerated case 'base = 0'. + (_, None, None) => None, + + // Subsequent iteration in the general case. + (pow, Some(it), Some(items)) => { // We are still unsure whether all items have been collected. - // As a consequence, 'base' is still uncertain, + // As a consequence, the exact value of 'base' is still uncertain, // but then we know that indices haven't started wrapping around yet. if let Some(next) = it.next() { items.push(next); @@ -91,32 +120,41 @@ where return Some((indices, items)); } - // All items have just been collected. + // The item collected on previous call was the last one. self.iter = None; + let base = items.len(); // Definitive 'base' value. if base == 1 || pow == 1 { - // End of iteration. - items.clear(); - indices.clear(); + // Early end of singleton iteration. + indices[0] = base; // Mark to cycle again on next iteration. return None; } // First wrap around. indices[pow - 1] = 0; - indices[pow - 2] += 1; + indices[pow - 2] = 1; Some((indices, items)) } - (_, None, b) => { + // Subsequent iteration in the general case after all items have been collected. + (_, None, Some(items)) => { + let base = items.len(); + if indices[0] == base { + // Special marker that iteration can start over for a new round. + indices[0] = 0; + return Some((indices, items)); + } // Keep yielding items list, incrementing indices rightmost first. for index in indices.iter_mut().rev() { *index += 1; - if *index < b { + if *index < base { return Some((indices, items)); } *index = 0; // Wrap and increment left. } - items.clear(); - indices.clear(); + // Iteration is over. + // Mark a special index value to not fuse the iterator + // and make it possible to cycle through all results again. + indices[0] = base; None } } @@ -187,13 +225,6 @@ where } } -impl FusedIterator for CartesianPower -where - I: Iterator, - I::Item: Clone, -{ -} - #[cfg(test)] mod tests { //! Use chars and string to ease testing of every yielded iterator values. @@ -202,41 +233,50 @@ mod tests { use crate::Itertools; use core::str::Chars; - fn check_fused(mut exhausted_it: CartesianPower, context: String) { - for i in 0..100 { - let act = exhausted_it.next(); - assert!( - act.is_none(), - "Iteration {} after expected exhaustion of {} \ - yielded {:?} instead of None. ", - i, - context, - act, - ); - } - } - #[test] fn basic() { fn check(origin: &str, pow: usize, expected: &[&str]) { - let mut it = origin.chars().cartesian_power(pow); - let mut i = 0; - for exp in expected { - let act = it.next(); - if act != Some(exp.chars().collect()) { - panic!( - "Failed iteration {} for {:?}^{}. \ - Expected {:?}, got {:?} instead.", - i, origin, pow, exp, act, - ); + println!("================== ({origin:?}^{pow})"); + let mut it_act = origin.chars().cartesian_power(pow); + // Check thrice that it's cycling. + for r in 1..=3 { + println!("- - {r} - - - - - -"); + let mut it_exp = expected.iter(); + let mut i = 0; + loop { + match (it_exp.next(), it_act.next()) { + (Some(exp), Some(act)) => { + if act != exp.chars().collect::>() { + panic!( + "Failed iteration {} (repetition {}) for {:?}^{}. \ + Expected {:?}, got {:?} instead.", + i, r, origin, pow, exp, act, + ); + } + i += 1; + } + (None, Some(act)) => { + panic!( + "Failed iteration {} (repetition {}) for {:?}^{}. \ + Expected None, got {:?} instead.", + i, r, origin, pow, act, + ); + } + (Some(exp), None) => { + panic!( + "Failed iteration {} (repetition {}) for {:?}^{}. \ + Expected {:?}, got None instead.", + i, r, origin, pow, exp, + ); + } + (None, None) => break, + } } - i += 1; } - check_fused(it, format!("iteration {} or {:?}^{}", i, origin, pow)); } // Empty underlying iterator. - check("", 0, &[""]); + check("", 0, &[""]); // 0^0 = 1. check("", 1, &[]); check("", 2, &[]); check("", 3, &[]); @@ -281,42 +321,30 @@ mod tests { fn check(origin: &str, pow: usize, expected: &[(usize, Option<&str>)]) { let mut it = origin.chars().cartesian_power(pow); let mut total_n = Vec::new(); - for &(n, exp) in expected { - let act = it.nth(n); - total_n.push(n); - if act != exp.map(|s| s.chars().collect::>()) { - panic!( - "Failed nth({}) iteration for {:?}^{}. \ - Expected {:?}, got {:?} instead.", - total_n - .iter() - .map(ToString::to_string) - .collect::>() - .join(", "), - origin, - pow, - exp, - act, - ); + for r in 1..=3 { + for &(n, exp) in expected { + let act = it.nth(n); + total_n.push(n); + if act != exp.map(|s| s.chars().collect::>()) { + panic!( + "Failed nth({}) iteration (repetition {}) for {:?}^{}. \ + Expected {:?}, got {:?} instead.", + total_n + .iter() + .map(ToString::to_string) + .collect::>() + .join(", "), + r, + origin, + pow, + exp, + act + ); + } } } - check_fused( - it, - format!( - "nth({}) iteration of {:?}^{}", - total_n - .iter() - .map(ToString::to_string) - .collect::>() - .join(", "), - origin, - pow - ), - ); } - // HERE: make it work with the new implementation. - // Check degenerated cases. check("", 0, &[(0, Some("")), (0, None)]); check("", 0, &[(0, Some("")), (1, None)]); From 5dfab7cd7aac5846c03c09320e6c14b5a8e6e39b Mon Sep 17 00:00:00 2001 From: Iago-lito Date: Tue, 24 Sep 2024 00:21:17 +0200 Subject: [PATCH 3/6] =?UTF-8?q?=F0=9F=9A=A7=20Implement=20.nth()=20with=20?= =?UTF-8?q?cycling.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cartesian_power.rs | 521 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 461 insertions(+), 60 deletions(-) diff --git a/src/cartesian_power.rs b/src/cartesian_power.rs index 3954a3e4c..0f5d53e60 100644 --- a/src/cartesian_power.rs +++ b/src/cartesian_power.rs @@ -144,12 +144,8 @@ where return Some((indices, items)); } // Keep yielding items list, incrementing indices rightmost first. - for index in indices.iter_mut().rev() { - *index += 1; - if *index < base { - return Some((indices, items)); - } - *index = 0; // Wrap and increment left. + if Self::inbounds_increment(indices, base) { + return Some((indices, items)); } // Iteration is over. // Mark a special index value to not fuse the iterator @@ -160,9 +156,166 @@ where } } + /// Increment indices, returning false in case of overflow. + fn inbounds_increment(indices: &mut [usize], base: usize) -> bool { + for index in indices.iter_mut().rev() { + *index += 1; + if *index < base { + return true; + } + *index = 0; // Wrap and increment left. + } + false + } + + /// Increment indices by n, returning false in case of (saturating) overflow. + fn inbounds_increment_by(n: usize, indices: &mut [usize], base: usize) -> bool { + let mut q = n; + for index in indices.iter_mut().rev() { + let s = *index + q; + q = s / base; + *index = s % base; + if q == 0 { + return true; + } + } + // Saturation requires a second pass to reset all indices. + for index in indices.iter_mut() { + *index = 0; + } + false + } + /// Same as [`increment_indices`], but does n increments at once. + /// The iterator is cycling, but `.nth()` does not 'wrap' + /// and 'saturates' to None instead. fn increment_indices_by_n(&mut self, n: usize) -> Option<(&[usize], &[I::Item])> { - todo!() + let Self { + pow, + iter, + items, + indices, + } = self; + + match (*pow, iter, &mut *items, n) { + // First iteration with degenerated 0th power. + (0, Some(_), items @ None, 0) => { + // Same as .next(). + self.iter = None; + let empty = items.insert(Vec::new()); + Some((indices, empty)) + } + (0, Some(_), None, _) => { + // Saturate. + self.iter = None; + None + } + + // Subsequent degenerated 0th power iteration. + // Same as `.next()`. + (0, None, items @ None, 0) => { + Some((indices, items.insert(Vec::new()))) + } + // Saturate. + (0, None, items, _) => { + *items = None; + None + } + + // First iterations in the general case. + // Possibly this will consume the entire underlying iterator, + // but we need to consume to check. + (pow, Some(it), items @ None, mut remaining) => { + if let Some(first) = it.next() { + // There is at least one element in the iterator, prepare collection + indices. + let items = items.insert(Vec::with_capacity(it.size_hint().0)); + items.push(first); + indices.reserve_exact(pow); + for _ in 0..pow { + indices.push(0); + } + // Collect more. + loop { + if remaining == 0 { + // Stop before collection completion. + indices[pow - 1] = n; // Hasn't wrapped yet. + return Some((indices, items)); + } + if let Some(next) = it.next() { + items.push(next); + remaining -= 1; + continue; + } + // Collection completed, but we need to go further. + self.iter = None; + let base = items.len(); + if Self::inbounds_increment_by(n, indices, base) { + return Some((indices, items)); + } + // Immediate saturation. + indices[0] = base; + return None; + } + } else { + // Degenerated iteration over an empty set. + self.iter = None; + None + } + } + + // Stable iteration in the degenerated case 'base = 0'. + (_, None, None, _) => { + None + } + + // Subsequent iteration in the general case. + // Again, immediate saturation is an option. + (pow, Some(it), Some(items), mut remaining) => { + if let Some(next) = it.next() { + items.push(next); + loop { + if remaining == 0 { + indices[pow - 1] += n + 1; // Hasn't wrapped yet. + return Some((indices, items)); + } + if let Some(next) = it.next() { + items.push(next); + remaining -= 1; + continue; + } + break; + } + } + // Collection completed. + self.iter = None; + let base = items.len(); + if Self::inbounds_increment_by(n + 1, indices, base) { + return Some((indices, items)); + } + // Saturate. + indices[0] = base; + None + } + + // Subsequent iteration in the general case + // after all items have been collected. + (_, None, Some(items), n) => { + let base = items.len(); + let shift = if indices[0] == base { + // Start over for a new round (already counted then). + indices[0] = 0; + 0 + } else { + 1 + }; + if Self::inbounds_increment_by(n + shift, indices, base) { + return Some((indices, items)); + } + // Immediate re-saturation. + indices[0] = base; + None + } + } } } @@ -244,7 +397,9 @@ mod tests { let mut it_exp = expected.iter(); let mut i = 0; loop { - match (it_exp.next(), it_act.next()) { + let act = it_act.next(); + println!(" {:?}", act); + match (it_exp.next(), act) { (Some(exp), Some(act)) => { if act != exp.chars().collect::>() { panic!( @@ -319,66 +474,312 @@ mod tests { #[test] fn nth() { fn check(origin: &str, pow: usize, expected: &[(usize, Option<&str>)]) { + println!("================== ({origin:?}^{pow})"); let mut it = origin.chars().cartesian_power(pow); let mut total_n = Vec::new(); - for r in 1..=3 { - for &(n, exp) in expected { - let act = it.nth(n); - total_n.push(n); - if act != exp.map(|s| s.chars().collect::>()) { - panic!( - "Failed nth({}) iteration (repetition {}) for {:?}^{}. \ - Expected {:?}, got {:?} instead.", - total_n - .iter() - .map(ToString::to_string) - .collect::>() - .join(", "), - r, - origin, - pow, - exp, - act - ); + for &(n, exp) in expected { + let act = it.nth(n); + let act = act.map(|v| v.into_iter().collect::()); + println!( + " → {}", + if let Some(act) = act.as_ref() { + act + } else { + "🗙" } + ); + total_n.push(n); + if act.as_ref().map(String::as_str) != exp { + panic!( + "Failed nth({}) iteration for {:?}^{}. \ + Expected {:?}, got {:?} instead.", + total_n + .iter() + .map(ToString::to_string) + .collect::>() + .join(", "), + origin, + pow, + exp, + act + ); } } } - // Check degenerated cases. - check("", 0, &[(0, Some("")), (0, None)]); - check("", 0, &[(0, Some("")), (1, None)]); - check("", 0, &[(0, Some("")), (2, None)]); - check("", 0, &[(1, None), (0, None)]); - check("", 0, &[(1, None), (1, None)]); - check("", 0, &[(1, None), (2, None)]); - check("", 0, &[(2, None), (0, None)]); - check("", 0, &[(2, None), (1, None)]); - check("", 0, &[(2, None), (2, None)]); - - check("a", 0, &[(0, Some("")), (0, None)]); - check("a", 0, &[(0, Some("")), (1, None)]); - check("a", 0, &[(0, Some("")), (2, None)]); - check("a", 0, &[(1, None), (0, None)]); - check("a", 0, &[(1, None), (1, None)]); - check("a", 0, &[(1, None), (2, None)]); - check("a", 0, &[(2, None), (0, None)]); - check("a", 0, &[(2, None), (1, None)]); - check("a", 0, &[(2, None), (2, None)]); + // Ease test read/write. + // Accept a sequence of ' ' yielded by a call to `.nth(n)`. + macro_rules! check { + ($base:expr, $pow:expr => $( $n:literal $expected:expr )+ ) => { + check($base, $pow, &[$(($n, $expected)),+]); + }; + } + + // Degenerated 0th power. + let o = None; + let e = Some(""); // "e"mpty. + + for base in ["", "a", "ab"] { + check!(base, 0 => 0 e 0 o 0 e 0 o); + check!(base, 0 => 0 e 1 o 0 e 1 o); + check!(base, 0 => 0 e 2 o 1 o 0 e); + check!(base, 0 => 1 o 0 e 0 o 1 o); + check!(base, 0 => 1 o 1 o 0 e 0 o); + check!(base, 0 => 1 o 2 o 0 e 1 o); + check!(base, 0 => 2 o 0 e 1 o 0 e); + check!(base, 0 => 2 o 1 o 2 o 0 e); + check!(base, 0 => 2 o 2 o 0 e 2 o); + } + + // Degenerated 0-base. + for pow in [1, 2, 3] { + check!("", pow => 0 o 0 o 0 o 0 o); + check!("", pow => 1 o 1 o 1 o 1 o); + check!("", pow => 2 o 2 o 2 o 2 o); + check!("", pow => 0 o 1 o 2 o 0 o); + check!("", pow => 2 o 1 o 0 o 1 o); + } // Unit power. - check("a", 1, &[(0, Some("a")), (0, None)]); - check("a", 1, &[(0, Some("a")), (1, None)]); - check("a", 1, &[(0, Some("a")), (2, None)]); - check("a", 1, &[(1, None), (0, None)]); - check("a", 1, &[(1, None), (1, None)]); - check("a", 1, &[(1, None), (2, None)]); - check("a", 1, &[(2, None), (0, None)]); - check("a", 1, &[(2, None), (1, None)]); - check("a", 1, &[(2, None), (2, None)]); - - check("ab", 1, &[(0, Some("a")), (0, Some("b")), (0, None)]); - check("ab", 1, &[(1, Some("b")), (0, None), (0, None)]); - check("ab", 1, &[(2, None), (0, None), (0, None)]); + let a = Some("a"); + let b = Some("b"); + let c = Some("c"); + + check!("a", 1 => 0 a 0 o 0 a 0 o 0 a 0 o); + check!("a", 1 => 1 o 1 o 1 o 1 o 1 o 1 o); + check!("a", 1 => 2 o 2 o 2 o 2 o 2 o 2 o); + check!("a", 1 => 0 a 1 o 0 a 1 o 0 a 1 o); + check!("a", 1 => 1 o 0 a 1 o 0 a 1 o 0 a); + check!("a", 1 => 0 a 2 o 0 a 2 o 0 a 2 o); + check!("a", 1 => 2 o 0 a 2 o 0 a 2 o 0 a); + check!("a", 1 => 1 o 2 o 1 o 2 o 1 o 2 o); + check!("a", 1 => 2 o 1 o 2 o 1 o 2 o 1 o); + check!("a", 1 => 0 a 1 o 2 o 0 a 1 o 2 o); + check!("a", 1 => 0 a 2 o 1 o 0 a 2 o 1 o); + check!("a", 1 => 1 o 0 a 2 o 1 o 0 a 2 o); + check!("a", 1 => 1 o 2 o 0 a 1 o 2 o 0 a 1 o 2 o 0 a); + check!("a", 1 => 2 o 0 a 1 o 2 o 0 a 1 o 2 o 0 a 1 o); + check!("a", 1 => 2 o 1 o 0 a 2 o 1 o 0 a 2 o 1 o 0 a); + check!("a", 1 => 1 o 0 a 0 o 1 o 0 a 0 o 1 o 0 a 0 o); + check!("a", 1 => 1 o 1 o 0 a 0 o 1 o 1 o 0 a 0 o 1 o 1 o); + + check!("ab", 1 => 0 a 0 b 0 o 0 a 0 b 0 o); + check!("ab", 1 => 1 b 1 o 1 b 1 o 1 b 1 o); + check!("ab", 1 => 2 o 2 o 2 o 2 o 2 o 2 o); + check!("ab", 1 => 0 a 1 o 0 a 1 o 0 a 1 o); + check!("ab", 1 => 1 b 0 o 1 b 0 o 1 b 0 o); + check!("ab", 1 => 0 a 2 o 0 a 2 o 0 a 2 o); + check!("ab", 1 => 2 o 0 a 2 o 0 a 2 o 0 a); + check!("ab", 1 => 1 b 2 o 1 b 2 o 1 b 2 o); + check!("ab", 1 => 2 o 1 b 2 o 1 b 2 o 1 b); + check!("ab", 1 => 0 a 1 o 2 o 0 a 1 o 2 o); + check!("ab", 1 => 0 a 2 o 1 b 0 o 2 o 1 b); + check!("ab", 1 => 1 b 0 o 2 o 1 b 0 o 2 o); + check!("ab", 1 => 1 b 2 o 0 a 1 o 2 o 0 a 1 o 2 o 0 a); + check!("ab", 1 => 2 o 0 a 1 o 2 o 0 a 1 o); + check!("ab", 1 => 2 o 1 b 0 o 2 o 1 b 0 o); + check!("ab", 1 => 1 b 0 o 0 a 1 o 0 a 0 b 1 o 0 a 0 b); + check!("ab", 1 => 1 b 1 o 0 a 0 b 1 o 1 b 0 o 0 a 1 o 1 b); + + check!("abc", 1 => 0 a 0 b 0 c 0 o 0 a 0 b 0 c 0 o); + check!("abc", 1 => 1 b 1 o 1 b 1 o 1 b 1 o); + check!("abc", 1 => 2 c 2 o 2 c 2 o 2 c 2 o); + check!("abc", 1 => 0 a 1 c 0 o 1 b 0 c 1 o 0 a 1 c); + check!("abc", 1 => 1 b 0 c 1 o 0 a 1 c 0 o 1 b 0 c); + check!("abc", 1 => 0 a 2 o 0 a 2 o 0 a 2 o); + check!("abc", 1 => 2 c 0 o 2 c 0 o 2 c 0 o); + check!("abc", 1 => 1 b 2 o 1 b 2 o 1 b 2 o); + check!("abc", 1 => 2 c 1 o 2 c 1 o 2 c 1 o); + check!("abc", 1 => 0 a 1 c 2 o 0 a 1 c 2 o); + check!("abc", 1 => 0 a 2 o 1 b 0 c 2 o 1 b); + check!("abc", 1 => 1 b 0 c 2 o 1 b 0 c 2 o); + check!("abc", 1 => 1 b 2 o 0 a 1 c 2 o 0 a 1 c 2 o 0 a); + check!("abc", 1 => 2 c 0 o 1 b 2 o 0 a 1 c 2 o 0 a 1 c); + check!("abc", 1 => 2 c 1 o 0 a 2 o 1 b 0 c 2 o 1 b 0 c); + check!("abc", 1 => 1 b 0 c 0 o 1 b 0 c 0 o 1 b 0 c 0 o); + check!("abc", 1 => 1 b 1 o 0 a 0 b 1 o 1 b 0 c 0 o 1 b 1 o); + + // Higher power. + let aa = Some("aa"); + let ab = Some("ab"); + let ac = Some("ac"); + let ba = Some("ba"); + let bb = Some("bb"); + let bc = Some("bc"); + let ca = Some("ca"); + let cb = Some("cb"); + let cc = Some("cc"); + + check!("a", 2 => 0 aa 0 o 0 aa 0 o 0 aa 0 o); + check!("a", 2 => 1 o 1 o 1 o 1 o 1 o 1 o); + check!("a", 2 => 2 o 2 o 2 o 2 o 2 o 2 o); + check!("a", 2 => 0 aa 1 o 0 aa 1 o 0 aa 1 o); + check!("a", 2 => 1 o 0 aa 1 o 0 aa 1 o 0 aa 1 o); + check!("a", 2 => 0 aa 2 o 0 aa 2 o 0 aa 2 o); + check!("a", 2 => 2 o 0 aa 2 o 0 aa 2 o 0 aa); + check!("a", 2 => 1 o 2 o 1 o 2 o 1 o 2 o); + check!("a", 2 => 2 o 1 o 2 o 1 o 2 o 1 o); + check!("a", 2 => 0 aa 1 o 2 o 0 aa 1 o 2 o); + check!("a", 2 => 0 aa 2 o 1 o 0 aa 2 o 1 o); + check!("a", 2 => 1 o 0 aa 2 o 1 o 0 aa 2 o); + check!("a", 2 => 1 o 2 o 0 aa 1 o 2 o 0 aa 1 o 2 o 0 aa); + check!("a", 2 => 2 o 0 aa 1 o 2 o 0 aa 1 o 2 o 0 aa 1 o); + check!("a", 2 => 2 o 1 o 0 aa 2 o 1 o 0 aa 2 o 1 o 0 aa); + check!("a", 2 => 1 o 0 aa 0 o 1 o 0 aa 0 o 1 o 0 aa 0 o); + check!("a", 2 => 1 o 1 o 0 aa 0 o 1 o 1 o 0 aa 0 o 1 o 1 o); + + check!("ab", 2 => 0 aa 0 ab 0 ba 0 bb 0 o 0 aa 0 ab); + check!("ab", 2 => 1 ab 1 bb 1 o 1 ab 1 bb 1 o); + check!("ab", 2 => 2 ba 2 o 2 ba 2 o 2 ba 2 o); + check!("ab", 2 => 0 aa 1 ba 0 bb 1 o 0 aa 1 ba); + check!("ab", 2 => 1 ab 0 ba 1 o 0 aa 1 ba 0 bb 1 o 0 aa 1 ba 0 bb); + check!("ab", 2 => 0 aa 2 bb 0 o 2 ba 0 bb 2 o 0 aa 2 bb); + check!("ab", 2 => 2 ba 0 bb 2 o 0 aa 2 bb 0 o 2 ba 0 bb); + check!("ab", 2 => 1 ab 2 o 1 ab 2 o 1 ab 2 o); + check!("ab", 2 => 2 ba 1 o 2 ba 1 o 2 ba 1 o); + check!("ab", 2 => 0 aa 1 ba 2 o 0 aa 1 ba 2 o); + check!("ab", 2 => 0 aa 2 bb 1 o 0 aa 2 bb 1 o); + check!("ab", 2 => 1 ab 0 ba 2 o 1 ab 0 ba 2 o); + check!("ab", 2 => 1 ab 2 o 0 aa 1 ba 2 o 0 aa 1 ba 2 o 0 aa); + check!("ab", 2 => 2 ba 0 bb 1 o 2 ba 0 bb 1 o 2 ba 0 bb 1 o); + check!("ab", 2 => 2 ba 1 o 0 aa 2 bb 1 o 0 aa 2 bb 1 o 0 aa); + check!("ab", 2 => 1 ab 0 ba 0 bb 1 o 0 aa 0 ab 1 bb 0 o 0 aa 1 ba 0 bb 0 o 1 ab 0 ba 0 bb); + check!("ab", 2 => 1 ab 1 bb 0 o 0 aa 1 ba 1 o 0 aa 0 ab 1 bb 1 o 0 aa 0 ab 1 bb 1 o); + + check!("abc", 2 => 0 aa 0 ab 0 ac 0 ba 0 bb 0 bc 0 ca 0 cb 0 cc 0 o 0 aa 0 ab 0 ac 0 ba); + check!("abc", 2 => 1 ab 1 ba 1 bc 1 cb 1 o 1 ab 1 ba 1 bc 1 cb 1 o 1 ab 1 ba 1 bc 1 cb 1 o); + check!("abc", 2 => 2 ac 2 bc 2 cc 2 o 2 ac 2 bc 2 cc 2 o 2 ac 2 bc 2 cc 2 o 2 ac 2 bc 2 cc); + check!("abc", 2 => 0 aa 1 ac 0 ba 1 bc 0 ca 1 cc 0 o 1 ab 0 ac 1 bb 0 bc 1 cb 0 cc 1 o); + check!("abc", 2 => 1 ab 0 ac 1 bb 0 bc 1 cb 0 cc 1 o 0 aa 1 ac 0 ba 1 bc 0 ca 1 cc 0 o); + check!("abc", 2 => 0 aa 2 ba 0 bb 2 cb 0 cc 2 o 0 aa 2 ba 0 bb 2 cb 0 cc 2 o 0 aa 2 ba); + check!("abc", 2 => 2 ac 0 ba 2 ca 0 cb 2 o 0 aa 2 ba 0 bb 2 cb 0 cc 2 o 0 aa 2 ba 0 bb); + check!("abc", 2 => 1 ab 2 bb 1 ca 2 o 1 ab 2 bb 1 ca 2 o 1 ab 2 bb 1 ca 2 o 1 ab 2 bb 1 ca); + check!("abc", 2 => 2 ac 1 bb 2 cb 1 o 2 ac 1 bb 2 cb 1 o 2 ac 1 bb 2 cb 1 o 2 ac 1 bb 2 cb); + check!("abc", 2 => 0 aa 1 ac 2 bc 0 ca 1 cc 2 o 0 aa 1 ac 2 bc 0 ca 1 cc 2 o 0 aa 1 ac); + check!("abc", 2 => 0 aa 2 ba 1 bc 0 ca 2 o 1 ab 0 ac 2 bc 1 cb 0 cc 2 o 1 ab 0 ac 2 bc); + check!("abc", 2 => 1 ab 0 ac 2 bc 1 cb 0 cc 2 o 1 ab 0 ac 2 bc 1 cb 0 cc 2 o 1 ab 0 ac); + check!("abc", 2 => 1 ab 2 bb 0 bc 1 cb 2 o 0 aa 1 ac 2 bc 0 ca 1 cc 2 o 0 aa 1 ac 2 bc); + check!("abc", 2 => 2 ac 0 ba 1 bc 2 cc 0 o 1 ab 2 bb 0 bc 1 cb 2 o 0 aa 1 ac 2 bc 0 ca); + check!("abc", 2 => 2 ac 1 bb 0 bc 2 cc 1 o 0 aa 2 ba 1 bc 0 ca 2 o 1 ab 0 ac 2 bc 1 cb); + check!("abc", 2 => 1 ab 0 ac 0 ba 1 bc 0 ca 0 cb 1 o 0 aa 0 ab 1 ba 0 bb 0 bc 1 cb 0 cc); + check!("abc", 2 => 1 ab 1 ba 0 bb 0 bc 1 cb 1 o 0 aa 0 ab 1 ba 1 bc 0 ca 0 cb 1 o 1 ab); + + let s = |s| Some(s); + + check!( + "abc", 3 => + 0 s("aaa") + 0 s("aab") + 0 s("aac") + 0 s("aba") + 0 s("abb") + 0 s("abc") + 0 s("aca") + 0 s("acb") + 0 s("acc") + 0 s("baa") + 0 s("bab") + 0 s("bac") + 0 s("bba") + 0 s("bbb") + 0 s("bbc") + 0 s("bca") + 0 s("bcb") + 0 s("bcc") + 0 s("caa") + 0 s("cab") + 0 s("cac") + 0 s("cba") + 0 s("cbb") + 0 s("cbc") + 0 s("cca") + 0 s("ccb") + 0 s("ccc") + 0 o + 0 s("aaa") + 0 s("aab") + 0 s("aac") + ); + + check!( + "abc", 3 => + 1 s("aab") + 1 s("aba") + 1 s("abc") + 1 s("acb") + 1 s("baa") + 1 s("bac") + 1 s("bbb") + 1 s("bca") + 1 s("bcc") + 1 s("cab") + 1 s("cba") + 1 s("cbc") + 1 s("ccb") + 1 o + 1 s("aab") + 1 s("aba") + ); + + check!( + "abc", 3 => + 2 s("aac") + 2 s("abc") + 2 s("acc") + 2 s("bac") + 2 s("bbc") + 2 s("bcc") + 2 s("cac") + 2 s("cbc") + 2 s("ccc") + 2 o + 2 s("aac") + 2 s("abc") + ); + + check!( + "abc", 3 => + 3 s("aba") 3 s("acb") 3 s("bac") 3 s("bca") 3 s("cab") 3 s("cbc") 3 o + 3 s("aba") 3 s("acb") 3 s("bac") 3 s("bca") 3 s("cab") 3 s("cbc") 3 o + ); + + check!( + "abc", 3 => + 4 s("abb") 4 s("baa") 4 s("bbc") 4 s("cab") 4 s("cca") 4 o + 4 s("abb") 4 s("baa") 4 s("bbc") 4 s("cab") 4 s("cca") 4 o + ); + + check!( + "abc", 3 => + 5 s("abc") 5 s("bac") 5 s("bcc") 5 s("cbc") 5 o + 5 s("abc") 5 s("bac") 5 s("bcc") 5 s("cbc") 5 o + ); + + check!("abc", 3 => + 6 s("aca") 6 s("bbb") 6 s("cac") 6 o + 6 s("aca") 6 s("bbb") 6 s("cac") 6 o + ); + + check!("abc", 3 => + 7 s("acb") 7 s("bca") 7 s("cbc") 7 o + 7 s("acb") 7 s("bca") 7 s("cbc") 7 o + ); + + check!( + "abc", 3 => + 8 s("acc") 8 s("bcc") 8 s("ccc") 8 o + 8 s("acc") 8 s("bcc") 8 s("ccc") 8 o + ); + + check!("abc", 3 => 9 s("baa") 9 s("cab") 9 o 9 s("baa") 9 s("cab") 9 o); + check!("abc", 3 => 10 s("bab") 10 s("cba") 10 o 10 s("bab") 10 s("cba") 10 o); + check!("abc", 3 => 11 s("bac") 11 s("cbc") 11 o 11 s("bac") 11 s("cbc") 11 o); + check!("abc", 3 => 12 s("bba") 12 s("ccb") 12 o 12 s("bba") 12 s("ccb") 12 o); + check!("abc", 3 => 13 s("bbb") 13 o 13 s("bbb") 13 o); + check!("abc", 3 => 14 s("bbc") 14 o 14 s("bbc") 14 o); + check!("abc", 3 => 25 s("ccb") 25 o 25 s("ccb") 25 o); + check!("abc", 3 => 26 s("ccc") 26 o 26 s("ccc") 26 o); + check!("abc", 3 => 27 o 27 o 27 o 27 o); + check!("abc", 3 => 28 o 28 o 28 o 28 o); } } From c4b9892b40c88c91d997ab0b1a3c14cfa4567454 Mon Sep 17 00:00:00 2001 From: Iago-lito Date: Thu, 3 Oct 2024 23:19:32 +0200 Subject: [PATCH 4/6] =?UTF-8?q?=F0=9F=9A=A7=20Implement=20`.size=5Fhint()`?= =?UTF-8?q?.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cartesian_power.rs | 448 +++++++++++++++++++++++++++-------------- src/lib.rs | 2 +- 2 files changed, 299 insertions(+), 151 deletions(-) diff --git a/src/cartesian_power.rs b/src/cartesian_power.rs index 0f5d53e60..ec05d00a5 100644 --- a/src/cartesian_power.rs +++ b/src/cartesian_power.rs @@ -13,7 +13,7 @@ where I: Iterator, I::Item: Clone, { - pow: usize, + pow: u32, iter: Option, // Inner iterator. Forget once consumed after 'base' iterations. items: Option>, // Fill from iter. Final length is 'base'. // None means that collection has not started yet. @@ -30,7 +30,7 @@ where } /// Create a new `CartesianPower` from an iterator of clonables. -pub fn cartesian_power(iter: I, pow: usize) -> CartesianPower +pub fn cartesian_power(iter: I, pow: u32) -> CartesianPower where I: Iterator, I::Item: Clone, @@ -62,14 +62,11 @@ where items, indices, } = self; - println!( - "^{pow}: {} {indices:?}\t\t{:?}", - if iter.is_some() { 'S' } else { 'N' }, - items.as_ref().map(Vec::len) - ); + + let pow = *pow as usize; // (weird 'items @' bindings circumvent NLL limitations, unneeded with polonius) - match (*pow, iter, &mut *items) { + match (pow, iter, &mut *items) { // First iteration with degenerated 0th power. (0, Some(_), items @ None) => { self.iter = None; // Forget about underlying iteration immediately. @@ -197,7 +194,9 @@ where indices, } = self; - match (*pow, iter, &mut *items, n) { + let pow = *pow as usize; + + match (pow, iter, &mut *items, n) { // First iteration with degenerated 0th power. (0, Some(_), items @ None, 0) => { // Same as .next(). @@ -213,9 +212,7 @@ where // Subsequent degenerated 0th power iteration. // Same as `.next()`. - (0, None, items @ None, 0) => { - Some((indices, items.insert(Vec::new()))) - } + (0, None, items @ None, 0) => Some((indices, items.insert(Vec::new()))), // Saturate. (0, None, items, _) => { *items = None; @@ -264,9 +261,7 @@ where } // Stable iteration in the degenerated case 'base = 0'. - (_, None, None, _) => { - None - } + (_, None, None, _) => None, // Subsequent iteration in the general case. // Again, immediate saturation is an option. @@ -348,11 +343,80 @@ where } fn size_hint(&self) -> (usize, Option) { - todo!() + let Self { + pow, + iter, + items, + indices, + } = self; + + // The following case analysis matches implementation of `.next()`. + #[allow(clippy::match_same_arms)] + match (*pow, iter, items) { + // First iteration with degenerated 0th power. + (0, Some(_), None) => (1, Some(1)), + + // Subsequent degenerated 0th power iteration. + // Alternating for cycling behaviour. + (0, None, Some(_)) => (0, Some(0)), + (0, None, None) => (1, Some(1)), + + // First iteration in the general case. + (pow, Some(it), None) => { + let (a, b) = it.size_hint(); + ( + a.checked_pow(pow).unwrap_or(usize::MAX), + b.and_then(|b| b.checked_pow(pow)), + ) + } + + // Stable iteration in the degenerated case 'base = 0'. + (_, None, None) => (0, Some(0)), + + // Subsequent iteration in the general case. + (pow, Some(it), Some(items)) => { + let already = items.len(); + let minus_already = |total| total - already; + let (a, b) = it.size_hint(); + ( + (a + already) + .checked_pow(pow) + .map_or(usize::MAX, minus_already), + b.and_then(|b| (b + already).checked_pow(pow).map(minus_already)), + ) + } + + // Subsequent iteration in the general case after all items have been collected. + (pow, None, Some(items)) => { + let base = items.len(); + if indices[0] == base { + // Fresh re-start. + let r = base.checked_pow(pow); + return (r.unwrap_or(usize::MAX), r); + } + // Count what's left from current indices. + // This is subtracting the current iteration number base^pow, + // using the complement method. + let calc = || -> Option { + // (closure-wrap to ease interruption on overflow with ?-operator) + let mut r = 0usize; + for (&i, rank) in indices.iter().rev().zip(0u32..) { + let complement = base - 1 - i; + let increment = complement.checked_mul(base.checked_pow(rank)?)?; + r = r.checked_add(increment)?; + } + Some(r) + }; + let Some(r) = calc() else { + return (usize::MAX, None); + }; + (r, Some(r)) + } + } } fn count(self) -> usize { - todo!() + self.size_hint().0 } } @@ -378,54 +442,82 @@ where } } +/// Use chars and string to ease testing of every yielded iterator values. #[cfg(test)] mod tests { - //! Use chars and string to ease testing of every yielded iterator values. - use super::CartesianPower; use crate::Itertools; - use core::str::Chars; #[test] fn basic() { - fn check(origin: &str, pow: usize, expected: &[&str]) { + fn check(origin: &str, pow: u32, expected: &[&str]) { println!("================== ({origin:?}^{pow})"); - let mut it_act = origin.chars().cartesian_power(pow); + let mut it_act = origin + .chars() + .collect::>() // Collect to get exact size hint upper bound. + .into_iter() + .cartesian_power(pow); + // Check size_hint on the fly. + let e_hint = expected.len(); // Check thrice that it's cycling. for r in 1..=3 { println!("- - {r} - - - - - -"); let mut it_exp = expected.iter(); let mut i = 0; + let mut e_remaining = e_hint; loop { + // Common context to emit in case of test failure. + let ctx = || { + format!( + "Failed iteration {} (repetition {}) for {:?}^{}.", + i, r, origin, pow, + ) + }; + // Check size hints. + let a_remaining = it_act.size_hint(); + assert!( + if let (la, Some(ha)) = a_remaining { + la == e_remaining && ha == e_remaining + } else { + false + }, + "{} Expected size hint: ({e}, Some({e})), got instead: {a:?}.", + ctx(), + e = e_remaining, + a = a_remaining, + ); + // Actual/expected iterators steps. let act = it_act.next(); + let exp = it_exp.next().map(|e| e.chars().collect::>()); println!(" {:?}", act); - match (it_exp.next(), act) { + // Possible failure report. + let fail = |e, a| { + let f = |o| { + if let Some(v) = o { + format!("{v:?}") + } else { + "None".into() + } + }; + panic!("{} Expected {:?}, got {:?} instead.", ctx(), f(e), f(a)); + }; + // Comparison. + match (exp, act) { (Some(exp), Some(act)) => { - if act != exp.chars().collect::>() { - panic!( - "Failed iteration {} (repetition {}) for {:?}^{}. \ - Expected {:?}, got {:?} instead.", - i, r, origin, pow, exp, act, - ); + if act != exp { + fail(Some(exp), Some(act)); } i += 1; } (None, Some(act)) => { - panic!( - "Failed iteration {} (repetition {}) for {:?}^{}. \ - Expected None, got {:?} instead.", - i, r, origin, pow, act, - ); + fail(None, Some(act)); } (Some(exp), None) => { - panic!( - "Failed iteration {} (repetition {}) for {:?}^{}. \ - Expected {:?}, got None instead.", - i, r, origin, pow, exp, - ); + fail(Some(exp), None); } (None, None) => break, } + e_remaining -= 1; } } } @@ -473,12 +565,30 @@ mod tests { #[test] fn nth() { - fn check(origin: &str, pow: usize, expected: &[(usize, Option<&str>)]) { + fn check(origin: &str, pow: u32, expected: &[(usize, (Option<&str>, usize))]) { println!("================== ({origin:?}^{pow})"); - let mut it = origin.chars().cartesian_power(pow); + let mut it = origin + .chars() + .collect::>() + .into_iter() + .cartesian_power(pow); let mut total_n = Vec::new(); - for &(n, exp) in expected { + for &(n, (exp, e_hint)) in expected { + total_n.push(n); + let ctx = || { + format!( + "Failed nth({}) iteration for {:?}^{}.", + total_n + .iter() + .map(ToString::to_string) + .collect::>() + .join(", "), + origin, + pow, + ) + }; let act = it.nth(n); + let a_hint = it.size_hint(); let act = act.map(|v| v.into_iter().collect::()); println!( " → {}", @@ -488,22 +598,21 @@ mod tests { "🗙" } ); - total_n.push(n); if act.as_ref().map(String::as_str) != exp { - panic!( - "Failed nth({}) iteration for {:?}^{}. \ - Expected {:?}, got {:?} instead.", - total_n - .iter() - .map(ToString::to_string) - .collect::>() - .join(", "), - origin, - pow, - exp, - act - ); + panic!("{} Expected {:?}, got {:?} instead.", ctx(), exp, act); } + // Check size hint after stepping. + assert!( + if let (la, Some(ha)) = a_hint { + la == e_hint && ha == e_hint + } else { + false + }, + "{} Expected size hint: ({e}, Some({e})), got instead: {a:?}.", + ctx(), + e = e_hint, + a = a_hint, + ); } } @@ -516,9 +625,8 @@ mod tests { } // Degenerated 0th power. - let o = None; - let e = Some(""); // "e"mpty. - + let o = (None, 1); + let e = (Some(""), 0); // "e"mpty. for base in ["", "a", "ab"] { check!(base, 0 => 0 e 0 o 0 e 0 o); check!(base, 0 => 0 e 1 o 0 e 1 o); @@ -532,6 +640,7 @@ mod tests { } // Degenerated 0-base. + let o = (None, 0); for pow in [1, 2, 3] { check!("", pow => 0 o 0 o 0 o 0 o); check!("", pow => 1 o 1 o 1 o 1 o); @@ -541,10 +650,8 @@ mod tests { } // Unit power. - let a = Some("a"); - let b = Some("b"); - let c = Some("c"); - + let o = (None, 1); + let a = (Some("a"), 0); check!("a", 1 => 0 a 0 o 0 a 0 o 0 a 0 o); check!("a", 1 => 1 o 1 o 1 o 1 o 1 o 1 o); check!("a", 1 => 2 o 2 o 2 o 2 o 2 o 2 o); @@ -563,6 +670,9 @@ mod tests { check!("a", 1 => 1 o 0 a 0 o 1 o 0 a 0 o 1 o 0 a 0 o); check!("a", 1 => 1 o 1 o 0 a 0 o 1 o 1 o 0 a 0 o 1 o 1 o); + let o = (None, 2); + let a = (Some("a"), 1); + let b = (Some("b"), 0); check!("ab", 1 => 0 a 0 b 0 o 0 a 0 b 0 o); check!("ab", 1 => 1 b 1 o 1 b 1 o 1 b 1 o); check!("ab", 1 => 2 o 2 o 2 o 2 o 2 o 2 o); @@ -581,6 +691,10 @@ mod tests { check!("ab", 1 => 1 b 0 o 0 a 1 o 0 a 0 b 1 o 0 a 0 b); check!("ab", 1 => 1 b 1 o 0 a 0 b 1 o 1 b 0 o 0 a 1 o 1 b); + let o = (None, 3); + let a = (Some("a"), 2); + let b = (Some("b"), 1); + let c = (Some("c"), 0); check!("abc", 1 => 0 a 0 b 0 c 0 o 0 a 0 b 0 c 0 o); check!("abc", 1 => 1 b 1 o 1 b 1 o 1 b 1 o); check!("abc", 1 => 2 c 2 o 2 c 2 o 2 c 2 o); @@ -600,16 +714,8 @@ mod tests { check!("abc", 1 => 1 b 1 o 0 a 0 b 1 o 1 b 0 c 0 o 1 b 1 o); // Higher power. - let aa = Some("aa"); - let ab = Some("ab"); - let ac = Some("ac"); - let ba = Some("ba"); - let bb = Some("bb"); - let bc = Some("bc"); - let ca = Some("ca"); - let cb = Some("cb"); - let cc = Some("cc"); - + let o = (None, 1); + let aa = (Some("aa"), 0); check!("a", 2 => 0 aa 0 o 0 aa 0 o 0 aa 0 o); check!("a", 2 => 1 o 1 o 1 o 1 o 1 o 1 o); check!("a", 2 => 2 o 2 o 2 o 2 o 2 o 2 o); @@ -628,6 +734,11 @@ mod tests { check!("a", 2 => 1 o 0 aa 0 o 1 o 0 aa 0 o 1 o 0 aa 0 o); check!("a", 2 => 1 o 1 o 0 aa 0 o 1 o 1 o 0 aa 0 o 1 o 1 o); + let o = (None, 4); + let aa = (Some("aa"), 3); + let ab = (Some("ab"), 2); + let ba = (Some("ba"), 1); + let bb = (Some("bb"), 0); check!("ab", 2 => 0 aa 0 ab 0 ba 0 bb 0 o 0 aa 0 ab); check!("ab", 2 => 1 ab 1 bb 1 o 1 ab 1 bb 1 o); check!("ab", 2 => 2 ba 2 o 2 ba 2 o 2 ba 2 o); @@ -646,6 +757,16 @@ mod tests { check!("ab", 2 => 1 ab 0 ba 0 bb 1 o 0 aa 0 ab 1 bb 0 o 0 aa 1 ba 0 bb 0 o 1 ab 0 ba 0 bb); check!("ab", 2 => 1 ab 1 bb 0 o 0 aa 1 ba 1 o 0 aa 0 ab 1 bb 1 o 0 aa 0 ab 1 bb 1 o); + let o = (None, 9); + let aa = (Some("aa"), 8); + let ab = (Some("ab"), 7); + let ac = (Some("ac"), 6); + let ba = (Some("ba"), 5); + let bb = (Some("bb"), 4); + let bc = (Some("bc"), 3); + let ca = (Some("ca"), 2); + let cb = (Some("cb"), 1); + let cc = (Some("cc"), 0); check!("abc", 2 => 0 aa 0 ab 0 ac 0 ba 0 bb 0 bc 0 ca 0 cb 0 cc 0 o 0 aa 0 ab 0 ac 0 ba); check!("abc", 2 => 1 ab 1 ba 1 bc 1 cb 1 o 1 ab 1 ba 1 bc 1 cb 1 o 1 ab 1 ba 1 bc 1 cb 1 o); check!("abc", 2 => 2 ac 2 bc 2 cc 2 o 2 ac 2 bc 2 cc 2 o 2 ac 2 bc 2 cc 2 o 2 ac 2 bc 2 cc); @@ -664,121 +785,148 @@ mod tests { check!("abc", 2 => 1 ab 0 ac 0 ba 1 bc 0 ca 0 cb 1 o 0 aa 0 ab 1 ba 0 bb 0 bc 1 cb 0 cc); check!("abc", 2 => 1 ab 1 ba 0 bb 0 bc 1 cb 1 o 0 aa 0 ab 1 ba 1 bc 0 ca 0 cb 1 o 1 ab); - let s = |s| Some(s); + let o = (None, 27); + let aaa = (Some("aaa"), 26); + let aab = (Some("aab"), 25); + let aac = (Some("aac"), 24); + let aba = (Some("aba"), 23); + let abb = (Some("abb"), 22); + let abc = (Some("abc"), 21); + let aca = (Some("aca"), 20); + let acb = (Some("acb"), 19); + let acc = (Some("acc"), 18); + let baa = (Some("baa"), 17); + let bab = (Some("bab"), 16); + let bac = (Some("bac"), 15); + let bba = (Some("bba"), 14); + let bbb = (Some("bbb"), 13); + let bbc = (Some("bbc"), 12); + let bca = (Some("bca"), 11); + let bcb = (Some("bcb"), 10); + let bcc = (Some("bcc"), 9); + let caa = (Some("caa"), 8); + let cab = (Some("cab"), 7); + let cac = (Some("cac"), 6); + let cba = (Some("cba"), 5); + let cbb = (Some("cbb"), 4); + let cbc = (Some("cbc"), 3); + let cca = (Some("cca"), 2); + let ccb = (Some("ccb"), 1); + let ccc = (Some("ccc"), 0); check!( "abc", 3 => - 0 s("aaa") - 0 s("aab") - 0 s("aac") - 0 s("aba") - 0 s("abb") - 0 s("abc") - 0 s("aca") - 0 s("acb") - 0 s("acc") - 0 s("baa") - 0 s("bab") - 0 s("bac") - 0 s("bba") - 0 s("bbb") - 0 s("bbc") - 0 s("bca") - 0 s("bcb") - 0 s("bcc") - 0 s("caa") - 0 s("cab") - 0 s("cac") - 0 s("cba") - 0 s("cbb") - 0 s("cbc") - 0 s("cca") - 0 s("ccb") - 0 s("ccc") + 0 aaa + 0 aab + 0 aac + 0 aba + 0 abb + 0 abc + 0 aca + 0 acb + 0 acc + 0 baa + 0 bab + 0 bac + 0 bba + 0 bbb + 0 bbc + 0 bca + 0 bcb + 0 bcc + 0 caa + 0 cab + 0 cac + 0 cba + 0 cbb + 0 cbc + 0 cca + 0 ccb + 0 ccc 0 o - 0 s("aaa") - 0 s("aab") - 0 s("aac") + 0 aaa + 0 aab + 0 aac ); check!( "abc", 3 => - 1 s("aab") - 1 s("aba") - 1 s("abc") - 1 s("acb") - 1 s("baa") - 1 s("bac") - 1 s("bbb") - 1 s("bca") - 1 s("bcc") - 1 s("cab") - 1 s("cba") - 1 s("cbc") - 1 s("ccb") + 1 aab + 1 aba + 1 abc + 1 acb + 1 baa + 1 bac + 1 bbb + 1 bca + 1 bcc + 1 cab + 1 cba + 1 cbc + 1 ccb 1 o - 1 s("aab") - 1 s("aba") + 1 aab + 1 aba ); check!( "abc", 3 => - 2 s("aac") - 2 s("abc") - 2 s("acc") - 2 s("bac") - 2 s("bbc") - 2 s("bcc") - 2 s("cac") - 2 s("cbc") - 2 s("ccc") + 2 aac + 2 abc + 2 acc + 2 bac + 2 bbc + 2 bcc + 2 cac + 2 cbc + 2 ccc 2 o - 2 s("aac") - 2 s("abc") + 2 aac + 2 abc ); check!( "abc", 3 => - 3 s("aba") 3 s("acb") 3 s("bac") 3 s("bca") 3 s("cab") 3 s("cbc") 3 o - 3 s("aba") 3 s("acb") 3 s("bac") 3 s("bca") 3 s("cab") 3 s("cbc") 3 o + 3 aba 3 acb 3 bac 3 bca 3 cab 3 cbc 3 o + 3 aba 3 acb 3 bac 3 bca 3 cab 3 cbc 3 o ); check!( "abc", 3 => - 4 s("abb") 4 s("baa") 4 s("bbc") 4 s("cab") 4 s("cca") 4 o - 4 s("abb") 4 s("baa") 4 s("bbc") 4 s("cab") 4 s("cca") 4 o + 4 abb 4 baa 4 bbc 4 cab 4 cca 4 o + 4 abb 4 baa 4 bbc 4 cab 4 cca 4 o ); check!( "abc", 3 => - 5 s("abc") 5 s("bac") 5 s("bcc") 5 s("cbc") 5 o - 5 s("abc") 5 s("bac") 5 s("bcc") 5 s("cbc") 5 o + 5 abc 5 bac 5 bcc 5 cbc 5 o + 5 abc 5 bac 5 bcc 5 cbc 5 o ); check!("abc", 3 => - 6 s("aca") 6 s("bbb") 6 s("cac") 6 o - 6 s("aca") 6 s("bbb") 6 s("cac") 6 o + 6 aca 6 bbb 6 cac 6 o + 6 aca 6 bbb 6 cac 6 o ); check!("abc", 3 => - 7 s("acb") 7 s("bca") 7 s("cbc") 7 o - 7 s("acb") 7 s("bca") 7 s("cbc") 7 o + 7 acb 7 bca 7 cbc 7 o + 7 acb 7 bca 7 cbc 7 o ); check!( "abc", 3 => - 8 s("acc") 8 s("bcc") 8 s("ccc") 8 o - 8 s("acc") 8 s("bcc") 8 s("ccc") 8 o + 8 acc 8 bcc 8 ccc 8 o + 8 acc 8 bcc 8 ccc 8 o ); - check!("abc", 3 => 9 s("baa") 9 s("cab") 9 o 9 s("baa") 9 s("cab") 9 o); - check!("abc", 3 => 10 s("bab") 10 s("cba") 10 o 10 s("bab") 10 s("cba") 10 o); - check!("abc", 3 => 11 s("bac") 11 s("cbc") 11 o 11 s("bac") 11 s("cbc") 11 o); - check!("abc", 3 => 12 s("bba") 12 s("ccb") 12 o 12 s("bba") 12 s("ccb") 12 o); - check!("abc", 3 => 13 s("bbb") 13 o 13 s("bbb") 13 o); - check!("abc", 3 => 14 s("bbc") 14 o 14 s("bbc") 14 o); - check!("abc", 3 => 25 s("ccb") 25 o 25 s("ccb") 25 o); - check!("abc", 3 => 26 s("ccc") 26 o 26 s("ccc") 26 o); + check!("abc", 3 => 9 baa 9 cab 9 o 9 baa 9 cab 9 o); + check!("abc", 3 => 10 bab 10 cba 10 o 10 bab 10 cba 10 o); + check!("abc", 3 => 11 bac 11 cbc 11 o 11 bac 11 cbc 11 o); + check!("abc", 3 => 12 bba 12 ccb 12 o 12 bba 12 ccb 12 o); + check!("abc", 3 => 13 bbb 13 o 13 bbb 13 o); + check!("abc", 3 => 14 bbc 14 o 14 bbc 14 o); + check!("abc", 3 => 25 ccb 25 o 25 ccb 25 o); + check!("abc", 3 => 26 ccc 26 o 26 ccc 26 o); check!("abc", 3 => 27 o 27 o 27 o 27 o); check!("abc", 3 => 28 o 28 o 28 o 28 o); } diff --git a/src/lib.rs b/src/lib.rs index 8af602df0..66c8a892f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1804,7 +1804,7 @@ pub trait Itertools: Iterator { /// TODO: illustrative example. /// ``` #[cfg(feature = "use_alloc")] - fn cartesian_power(self, pow: usize) -> CartesianPower + fn cartesian_power(self, pow: u32) -> CartesianPower where Self: Sized, Self::Item: Clone, From aa75f6962493245d7bf510a238323f4dc225f73a Mon Sep 17 00:00:00 2001 From: Iago-lito Date: Fri, 18 Oct 2024 20:10:50 +0200 Subject: [PATCH 5/6] =?UTF-8?q?=F0=9F=9A=A7=20Refactor=20in=20terms=20of?= =?UTF-8?q?=20underlying=20pure-indices=20indices=20streaming=20iterator.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cartesian_power.rs | 553 ++++++++++++++++++----------------------- 1 file changed, 236 insertions(+), 317 deletions(-) diff --git a/src/cartesian_power.rs b/src/cartesian_power.rs index ec05d00a5..cfff98058 100644 --- a/src/cartesian_power.rs +++ b/src/cartesian_power.rs @@ -1,8 +1,205 @@ use alloc::vec::Vec; use std::fmt; +/// Pseudo-iterator owned by [`CartesianPower`], +/// yielding underlying indices by references to itself, +/// the [streaming iterator](https://docs.rs/streaming-iterator/latest/streaming_iterator/) way. +#[derive(Debug, Clone)] +pub struct Indices { + // May be incremented by owner on first pass as long as exact value is unknown. + base: usize, + pow: u32, + + // Indices just yielded. Length is 'pow'. + // 0 0 .. 0 0 means that the first combination has been yielded. + // 0 0 .. 0 1 means that the second combination has been yielded. + // m m .. m m means that the last combination has just been yielded (m = base - 1). + // b 0 .. 0 0 means that 'None' has just been yielded (b = base). + // The latter is a special value marking the renewal of the iterator, + // which can cycle again through another full round, ad libitum. + values: Option>, +} + +impl Indices { + pub fn new(base: usize, pow: u32) -> Self { + Self { + base, + pow, + values: None, + } + } + pub fn next(&mut self) -> Option<&[usize]> { + let Self { base, pow, values } = self; + + match (base, pow, values) { + // First iteration with degenerated 0th power. + (_, 0, values @ None) => Some(values.insert(Vec::new())), + + // Last degenerated 0th power iteration. + // Use the Some<(empty)Vec> as a flag to alternate between yielding [] or None. + (_, 0, values @ Some(_)) => { + *values = None; + None + } + + // Stable iteration in 0-base. + (0, _, _) => None, + + // First iteration in the general case. + (_, pow, values @ None) => Some(values.insert(vec![0; *pow as usize])), + + // Subsequent iteration in the general case. + (&mut base, _, Some(values)) => { + if values[0] == base { + // Special marker that iteration can start over for a new round. + values[0] = 0; + return Some(values); + } + if inbounds_increment(values, base) { + return Some(values); + } + // Iteration is over. + // Mark a special index value to not fuse the iterator + // and make it possible to cycle through all results again. + values[0] = base; + None + } + } + } + + pub fn nth(&mut self, n: usize) -> Option<&[usize]> { + let Self { base, pow, values } = self; + match (base, pow, values, n) { + // First iteration with degenerated 0th power. + (_, 0, values @ None, 0) => { + Some(values.insert(Vec::new())) // Same as .next() + } + // Saturate. + (_, 0, values, _) => { + *values = None; + None + } + // Stable iteration in 0-base. + (0, _, _, _) => None, + // First iteration in the general case. + (&mut base, pow, values @ None, n) => { + let values = values.insert(vec![0; *pow as usize]); + if inbounds_increment_by(n, values, base) { + return Some(values); + } + // Immediate saturation. + values[0] = base; + None + } + // Subsequent iteration in the general case. + (&mut base, _, Some(values), n) => { + let shift = if values[0] == base { + // Start over for a new round (already counted then). + values[0] = 0; + 0 + } else { + 1 + }; + if inbounds_increment_by(n + shift, values, base) { + return Some(values); + } + // Immediate re-saturation. + values[0] = base; + None + } + } + } + + fn size_hint(&self) -> (usize, Option) { + self.size_hint_with_base(self.base) + } + + fn size_hint_with_base(&self, base: usize) -> (usize, Option) { + let Self { + base: _, + pow, + values, + } = self; + + // The following case analysis matches implementation of `.next()`. + match (base, pow, values) { + // First iteration with degenerated 0th power. + (_, 0, None) => (1, Some(1)), + + // Last degenerated 0th power iteration | Stable iteration in 0-base. + // Use the Some<(empty)Vec> as a flag to alternate between yielding [] or None. + (0, _, _) | (_, 0, Some(_)) => (0, Some(0)), + + // First iteration in the general case. + (base, &pow, None) => { + let c = base.checked_pow(pow); + (c.unwrap_or(usize::MAX), c) + } + + // Subsequent iteration in the general case. + (base, &pow, Some(values)) => { + if values[0] == base { + // Fresh re-start. + let r = base.checked_pow(pow); + return (r.unwrap_or(usize::MAX), r); + } + // Count what's left from current indices. + // This is subtracting the current iteration number base^pow, + // using the complement method. + let calc = || -> Option { + // (closure-wrap to ease interruption on overflow with ?-operator) + let mut r = 0usize; + for (&i, rank) in values.iter().rev().zip(0u32..) { + let complement = base - 1 - i; + let increment = complement.checked_mul(base.checked_pow(rank)?)?; + r = r.checked_add(increment)?; + } + Some(r) + }; + let Some(r) = calc() else { + return (usize::MAX, None); + }; + (r, Some(r)) + } + } + } +} + +/// Increment indices, returning false in case of overflow. +fn inbounds_increment(indices: &mut [usize], base: usize) -> bool { + for index in indices.iter_mut().rev() { + *index += 1; + if *index < base { + return true; + } + *index = 0; // Wrap and increment left. + } + false +} + +/// Increment indices by n, returning false in case of (saturating) overflow. +fn inbounds_increment_by(n: usize, indices: &mut [usize], base: usize) -> bool { + let mut q = n; + for index in indices.iter_mut().rev() { + let s = *index + q; + q = s / base; + *index = s % base; + if q == 0 { + return true; + } + } + // Saturation requires a second pass to reset all indices. + for index in indices.iter_mut() { + *index = 0; + } + false +} + /// An adaptor iterating through all the ordered `n`-length lists of items /// yielded by the underlying iterator, including repetitions. +/// Works by cloning the items into a fresh Vector on every `.next()`. +/// If this is too much allocation for you, +/// consider using the underlying lending [`Indices`] iterator instead. /// /// See [`.cartesian_power()`](crate::Itertools::cartesian_power) /// for more information. @@ -13,20 +210,11 @@ where I: Iterator, I::Item: Clone, { - pow: u32, iter: Option, // Inner iterator. Forget once consumed after 'base' iterations. - items: Option>, // Fill from iter. Final length is 'base'. - // None means that collection has not started yet. - // Some(empty) means that collection would have started but pow = 0. - - // Indices just yielded. Length is 'pow'. - // 0 0 .. 0 0 means that the first combination has been yielded. - // 0 0 .. 0 1 means that the second combination has been yielded. - // m m .. m m means that the last combination has just been yielded (m = base - 1). - // b 0 .. 0 0 means that 'None' has just been yielded (b = base). - // The latter is a special value marking the renewal of the iterator, - // which can cycle again through another full round, ad libitum. - indices: Vec, + items: Option>, // Fill from 'iter'. Final length is 'base'. + // Keep track of the items to yield, + // updating 'base' as the inner iterator is consumed. + indices: Indices, } /// Create a new `CartesianPower` from an iterator of clonables. @@ -36,10 +224,9 @@ where I::Item: Clone, { CartesianPower { - pow, iter: Some(iter), items: None, - indices: Vec::new(), + indices: Indices::new(0, pow), } } @@ -57,259 +244,53 @@ where /// valid within the collected items slice also returned. fn increment_indices(&mut self) -> Option<(&[usize], &[I::Item])> { let Self { - pow, iter, items, indices, } = self; - let pow = *pow as usize; - - // (weird 'items @' bindings circumvent NLL limitations, unneeded with polonius) - match (pow, iter, &mut *items) { - // First iteration with degenerated 0th power. - (0, Some(_), items @ None) => { - self.iter = None; // Forget about underlying iteration immediately. - let empty = items.insert(Vec::new()); // Raise this value as a boolean flag. - Some((indices, empty)) // Yield empty list. - } - - // Subsequent degenerated 0th power iteration. - // Use the Some<(empty)Vec> as a flag to alternate between yielding [] or None. - (0, None, items @ Some(_)) => { - *items = None; - None - } - (0, None, items @ None) => Some((indices, items.insert(Vec::new()))), - - // First iteration in the general case. - (pow, Some(it), items @ None) => { - // Check whether there is at least one element in the iterator. - if let Some(first) = it.next() { - items // Collect it. - .insert(Vec::with_capacity(it.size_hint().0)) - .push(first); - // Prepare indices to be yielded. - indices.reserve_exact(pow); - for _ in 0..pow { - indices.push(0); - } - Some((indices, items.as_ref().unwrap())) - } else { - // Degenerated iteration over an empty set: - // 'base = 0', yet with non-null power. - self.iter = None; - None - } - } - - // Stable iteration in the degenerated case 'base = 0'. - (_, None, None) => None, - - // Subsequent iteration in the general case. - (pow, Some(it), Some(items)) => { - // We are still unsure whether all items have been collected. - // As a consequence, the exact value of 'base' is still uncertain, - // but then we know that indices haven't started wrapping around yet. - if let Some(next) = it.next() { - items.push(next); - indices[pow - 1] += 1; - return Some((indices, items)); - } - - // The item collected on previous call was the last one. + if let Some(iter) = iter { + let items = items.get_or_insert_with(|| Vec::with_capacity(iter.size_hint().0)); + if let Some(new) = iter.next() { + indices.base += 1; + items.push(new); + } else { self.iter = None; - let base = items.len(); // Definitive 'base' value. - if base == 1 || pow == 1 { - // Early end of singleton iteration. - indices[0] = base; // Mark to cycle again on next iteration. - return None; - } - - // First wrap around. - indices[pow - 1] = 0; - indices[pow - 2] = 1; - Some((indices, items)) - } - - // Subsequent iteration in the general case after all items have been collected. - (_, None, Some(items)) => { - let base = items.len(); - if indices[0] == base { - // Special marker that iteration can start over for a new round. - indices[0] = 0; - return Some((indices, items)); - } - // Keep yielding items list, incrementing indices rightmost first. - if Self::inbounds_increment(indices, base) { - return Some((indices, items)); - } - // Iteration is over. - // Mark a special index value to not fuse the iterator - // and make it possible to cycle through all results again. - indices[0] = base; - None } + indices.next().map(move |i| (i, items.as_slice())) + } else if let Some(items) = items { + indices.next().map(move |i| (i, items.as_slice())) + } else { + indices.next().map(move |i| (i, [].as_slice())) } } - /// Increment indices, returning false in case of overflow. - fn inbounds_increment(indices: &mut [usize], base: usize) -> bool { - for index in indices.iter_mut().rev() { - *index += 1; - if *index < base { - return true; - } - *index = 0; // Wrap and increment left. - } - false - } - - /// Increment indices by n, returning false in case of (saturating) overflow. - fn inbounds_increment_by(n: usize, indices: &mut [usize], base: usize) -> bool { - let mut q = n; - for index in indices.iter_mut().rev() { - let s = *index + q; - q = s / base; - *index = s % base; - if q == 0 { - return true; - } - } - // Saturation requires a second pass to reset all indices. - for index in indices.iter_mut() { - *index = 0; - } - false - } - /// Same as [`increment_indices`], but does n increments at once. /// The iterator is cycling, but `.nth()` does not 'wrap' /// and 'saturates' to None instead. fn increment_indices_by_n(&mut self, n: usize) -> Option<(&[usize], &[I::Item])> { let Self { - pow, iter, items, indices, } = self; - let pow = *pow as usize; - - match (pow, iter, &mut *items, n) { - // First iteration with degenerated 0th power. - (0, Some(_), items @ None, 0) => { - // Same as .next(). - self.iter = None; - let empty = items.insert(Vec::new()); - Some((indices, empty)) - } - (0, Some(_), None, _) => { - // Saturate. - self.iter = None; - None - } - - // Subsequent degenerated 0th power iteration. - // Same as `.next()`. - (0, None, items @ None, 0) => Some((indices, items.insert(Vec::new()))), - // Saturate. - (0, None, items, _) => { - *items = None; - None - } - - // First iterations in the general case. - // Possibly this will consume the entire underlying iterator, - // but we need to consume to check. - (pow, Some(it), items @ None, mut remaining) => { - if let Some(first) = it.next() { - // There is at least one element in the iterator, prepare collection + indices. - let items = items.insert(Vec::with_capacity(it.size_hint().0)); - items.push(first); - indices.reserve_exact(pow); - for _ in 0..pow { - indices.push(0); - } - // Collect more. - loop { - if remaining == 0 { - // Stop before collection completion. - indices[pow - 1] = n; // Hasn't wrapped yet. - return Some((indices, items)); - } - if let Some(next) = it.next() { - items.push(next); - remaining -= 1; - continue; - } - // Collection completed, but we need to go further. - self.iter = None; - let base = items.len(); - if Self::inbounds_increment_by(n, indices, base) { - return Some((indices, items)); - } - // Immediate saturation. - indices[0] = base; - return None; - } + if let Some(iter) = iter { + let items = items.get_or_insert_with(|| Vec::with_capacity(iter.size_hint().0)); + for _ in 0..=n { + if let Some(new) = iter.next() { + indices.base += 1; + items.push(new); } else { - // Degenerated iteration over an empty set. self.iter = None; - None - } - } - - // Stable iteration in the degenerated case 'base = 0'. - (_, None, None, _) => None, - - // Subsequent iteration in the general case. - // Again, immediate saturation is an option. - (pow, Some(it), Some(items), mut remaining) => { - if let Some(next) = it.next() { - items.push(next); - loop { - if remaining == 0 { - indices[pow - 1] += n + 1; // Hasn't wrapped yet. - return Some((indices, items)); - } - if let Some(next) = it.next() { - items.push(next); - remaining -= 1; - continue; - } - break; - } - } - // Collection completed. - self.iter = None; - let base = items.len(); - if Self::inbounds_increment_by(n + 1, indices, base) { - return Some((indices, items)); - } - // Saturate. - indices[0] = base; - None - } - - // Subsequent iteration in the general case - // after all items have been collected. - (_, None, Some(items), n) => { - let base = items.len(); - let shift = if indices[0] == base { - // Start over for a new round (already counted then). - indices[0] = 0; - 0 - } else { - 1 - }; - if Self::inbounds_increment_by(n + shift, indices, base) { - return Some((indices, items)); + break; } - // Immediate re-saturation. - indices[0] = base; - None } + indices.nth(n).map(move |i| (i, items.as_slice())) + } else if let Some(items) = items { + indices.nth(n).map(move |i| (i, items.as_slice())) + } else { + indices.nth(n).map(move |i| (i, [].as_slice())) } } } @@ -343,75 +324,15 @@ where } fn size_hint(&self) -> (usize, Option) { - let Self { - pow, - iter, - items, - indices, - } = self; - - // The following case analysis matches implementation of `.next()`. - #[allow(clippy::match_same_arms)] - match (*pow, iter, items) { - // First iteration with degenerated 0th power. - (0, Some(_), None) => (1, Some(1)), - - // Subsequent degenerated 0th power iteration. - // Alternating for cycling behaviour. - (0, None, Some(_)) => (0, Some(0)), - (0, None, None) => (1, Some(1)), - - // First iteration in the general case. - (pow, Some(it), None) => { - let (a, b) = it.size_hint(); - ( - a.checked_pow(pow).unwrap_or(usize::MAX), - b.and_then(|b| b.checked_pow(pow)), - ) - } - - // Stable iteration in the degenerated case 'base = 0'. - (_, None, None) => (0, Some(0)), - - // Subsequent iteration in the general case. - (pow, Some(it), Some(items)) => { - let already = items.len(); - let minus_already = |total| total - already; - let (a, b) = it.size_hint(); - ( - (a + already) - .checked_pow(pow) - .map_or(usize::MAX, minus_already), - b.and_then(|b| (b + already).checked_pow(pow).map(minus_already)), - ) - } - - // Subsequent iteration in the general case after all items have been collected. - (pow, None, Some(items)) => { - let base = items.len(); - if indices[0] == base { - // Fresh re-start. - let r = base.checked_pow(pow); - return (r.unwrap_or(usize::MAX), r); - } - // Count what's left from current indices. - // This is subtracting the current iteration number base^pow, - // using the complement method. - let calc = || -> Option { - // (closure-wrap to ease interruption on overflow with ?-operator) - let mut r = 0usize; - for (&i, rank) in indices.iter().rev().zip(0u32..) { - let complement = base - 1 - i; - let increment = complement.checked_mul(base.checked_pow(rank)?)?; - r = r.checked_add(increment)?; - } - Some(r) - }; - let Some(r) = calc() else { - return (usize::MAX, None); - }; - (r, Some(r)) - } + let Self { iter, indices, .. } = self; + if let Some(iter) = iter { + let yet = indices.base; // <- The number of items yielded so far. + let (a, b) = iter.size_hint(); // <- The estimated number of remaining items. + let a = indices.size_hint_with_base(yet + a).0; + let b = b.and_then(|b| indices.size_hint_with_base(yet + b).1); + (a, b) + } else { + indices.size_hint() } } @@ -428,13 +349,11 @@ where { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { - pow, iter, items, indices, } = self; f.debug_struct("CartesianPower") - .field("pow", pow) .field("iter", &iter.is_some()) .field("items", items) .field("indices", indices) @@ -446,10 +365,10 @@ where #[cfg(test)] mod tests { - use crate::Itertools; + use crate::{cartesian_power::Indices, Itertools}; #[test] - fn basic() { + fn next() { fn check(origin: &str, pow: u32, expected: &[&str]) { println!("================== ({origin:?}^{pow})"); let mut it_act = origin From 1cfbd3ac937ee326a4429708bd8934fde7ed56b2 Mon Sep 17 00:00:00 2001 From: Iago-lito Date: Sun, 8 Dec 2024 18:19:58 +0100 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=9A=80=20Rebase,=20document:=20ready?= =?UTF-8?q?=20for=20review!?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cartesian_power.rs | 593 ++++++--------------------------------- src/lib.rs | 83 ++++-- tests/cartesian_power.rs | 486 ++++++++++++++++++++++++++++++++ 3 files changed, 632 insertions(+), 530 deletions(-) create mode 100644 tests/cartesian_power.rs diff --git a/src/cartesian_power.rs b/src/cartesian_power.rs index cfff98058..28217ab01 100644 --- a/src/cartesian_power.rs +++ b/src/cartesian_power.rs @@ -1,9 +1,49 @@ use alloc::vec::Vec; use std::fmt; -/// Pseudo-iterator owned by [`CartesianPower`], -/// yielding underlying indices by references to itself, -/// the [streaming iterator](https://docs.rs/streaming-iterator/latest/streaming_iterator/) way. +/// Yield all indices combinations of size `pow` from `0..base`. +/// This "lending" iterator only allocates once, +/// and yields references to its internal owned slice of updated indices. +/// See [streaming iterator](https://docs.rs/streaming-iterator/latest/streaming_iterator/). +/// +/// The resulting iterator is "cycling", +/// meaning that, after the last `.next()` call has yielded `None`, +/// you can call `.next()` again to resume iteration from the start, +/// as many times as needed. +/// +/// This is the iterator internally used by [`CartesianPower`], +/// ``` +/// use itertools::CartesianPowerIndices; +/// +/// let mut it = CartesianPowerIndices::new(3, 2); +/// assert_eq!(it.next(), Some(&[0, 0][..])); +/// assert_eq!(it.next(), Some(&[0, 1][..])); +/// assert_eq!(it.next(), Some(&[0, 2][..])); +/// assert_eq!(it.next(), Some(&[1, 0][..])); +/// assert_eq!(it.next(), Some(&[1, 1][..])); +/// assert_eq!(it.next(), Some(&[1, 2][..])); +/// assert_eq!(it.next(), Some(&[2, 0][..])); +/// assert_eq!(it.next(), Some(&[2, 1][..])); +/// assert_eq!(it.next(), Some(&[2, 2][..])); +/// assert_eq!(it.next(), None); // End of iteration. +/// assert_eq!(it.next(), Some(&[0, 0][..])); // Cycle: start over. +/// assert_eq!(it.next(), Some(&[0, 1][..])); +/// // ... +/// +/// let mut it = CartesianPowerIndices::new(2, 3); +/// assert_eq!(it.next(), Some(&[0, 0, 0][..])); +/// assert_eq!(it.next(), Some(&[0, 0, 1][..])); +/// assert_eq!(it.next(), Some(&[0, 1, 0][..])); +/// assert_eq!(it.next(), Some(&[0, 1, 1][..])); +/// assert_eq!(it.next(), Some(&[1, 0, 0][..])); +/// assert_eq!(it.next(), Some(&[1, 0, 1][..])); +/// assert_eq!(it.next(), Some(&[1, 1, 0][..])); +/// assert_eq!(it.next(), Some(&[1, 1, 1][..])); +/// assert_eq!(it.next(), None); +/// assert_eq!(it.next(), Some(&[0, 0, 0][..])); +/// assert_eq!(it.next(), Some(&[0, 0, 1][..])); +/// // ... +/// ``` #[derive(Debug, Clone)] pub struct Indices { // May be incremented by owner on first pass as long as exact value is unknown. @@ -21,6 +61,7 @@ pub struct Indices { } impl Indices { + /// Create a new `base^pow` lending iterator. pub fn new(base: usize, pow: u32) -> Self { Self { base, @@ -28,6 +69,10 @@ impl Indices { values: None, } } + + /// Step the iterator, yielding a reference to internal updated indices, + /// or `None` if the iteration is exhausted. + #[allow(clippy::should_implement_trait)] // <- Intended `.next` name "like Iterator::next". pub fn next(&mut self) -> Option<&[usize]> { let Self { base, pow, values } = self; @@ -67,6 +112,25 @@ impl Indices { } } + /// Same as [`next`][crate::CartesianPowerIndices::next], + /// but skip `n` steps. + /// Return `None` if this would lead to iterator exhaustion. + /// Saturates in case of overflow: + /// the iterator is cycling, + /// but if you skip past the last iteration, + /// you'll obtain `None` no matter how far you skip. + /// Iteration will only resume on further calls to `.next()` and `.nth()`. + /// + /// ``` + /// use itertools::CartesianPowerIndices; + /// + /// let mut it = CartesianPowerIndices::new(3, 2); + /// assert_eq!(it.nth(0), Some(&[0, 0][..])); + /// assert_eq!(it.nth(1), Some(&[0, 2][..])); + /// assert_eq!(it.nth(2), Some(&[1, 2][..])); + /// assert_eq!(it.nth(9), None); // Overshoot, but don't resume cycling yet. + /// assert_eq!(it.nth(2), Some(&[0, 2][..])); // Only resume cycling now. + /// ``` pub fn nth(&mut self, n: usize) -> Option<&[usize]> { let Self { base, pow, values } = self; match (base, pow, values, n) { @@ -110,7 +174,8 @@ impl Indices { } } - fn size_hint(&self) -> (usize, Option) { + /// Akin to [`Iterator::size_hint`]. + pub fn size_hint(&self) -> (usize, Option) { self.size_hint_with_base(self.base) } @@ -195,11 +260,12 @@ fn inbounds_increment_by(n: usize, indices: &mut [usize], base: usize) -> bool { false } -/// An adaptor iterating through all the ordered `n`-length lists of items -/// yielded by the underlying iterator, including repetitions. -/// Works by cloning the items into a fresh Vector on every `.next()`. -/// If this is too much allocation for you, -/// consider using the underlying lending [`Indices`] iterator instead. +/// The adaptor adaptor yielded by +/// [`.cartesian_power()`](crate::Itertools::cartesian_power). +/// +/// This iterator is *cycling*, +/// meaning that, once consumed after `.next()` returns `None`, +/// you can call `.next()` again to resume iteration from the start. /// /// See [`.cartesian_power()`](crate::Itertools::cartesian_power) /// for more information. @@ -217,24 +283,19 @@ where indices: Indices, } -/// Create a new `CartesianPower` from an iterator of clonables. -pub fn cartesian_power(iter: I, pow: u32) -> CartesianPower +impl CartesianPower where I: Iterator, I::Item: Clone, { - CartesianPower { - iter: Some(iter), - items: None, - indices: Indices::new(0, pow), + pub(crate) fn new(iter: I, pow: u32) -> CartesianPower { + CartesianPower { + iter: Some(iter), + items: None, + indices: Indices::new(0, pow), + } } -} -impl CartesianPower -where - I: Iterator, - I::Item: Clone, -{ /// Increments internal indices to advance to the next list to be yielded. /// This collects new items from the underlying iterator /// if they were not all already collected. @@ -360,493 +421,3 @@ where .finish() } } - -/// Use chars and string to ease testing of every yielded iterator values. -#[cfg(test)] -mod tests { - - use crate::{cartesian_power::Indices, Itertools}; - - #[test] - fn next() { - fn check(origin: &str, pow: u32, expected: &[&str]) { - println!("================== ({origin:?}^{pow})"); - let mut it_act = origin - .chars() - .collect::>() // Collect to get exact size hint upper bound. - .into_iter() - .cartesian_power(pow); - // Check size_hint on the fly. - let e_hint = expected.len(); - // Check thrice that it's cycling. - for r in 1..=3 { - println!("- - {r} - - - - - -"); - let mut it_exp = expected.iter(); - let mut i = 0; - let mut e_remaining = e_hint; - loop { - // Common context to emit in case of test failure. - let ctx = || { - format!( - "Failed iteration {} (repetition {}) for {:?}^{}.", - i, r, origin, pow, - ) - }; - // Check size hints. - let a_remaining = it_act.size_hint(); - assert!( - if let (la, Some(ha)) = a_remaining { - la == e_remaining && ha == e_remaining - } else { - false - }, - "{} Expected size hint: ({e}, Some({e})), got instead: {a:?}.", - ctx(), - e = e_remaining, - a = a_remaining, - ); - // Actual/expected iterators steps. - let act = it_act.next(); - let exp = it_exp.next().map(|e| e.chars().collect::>()); - println!(" {:?}", act); - // Possible failure report. - let fail = |e, a| { - let f = |o| { - if let Some(v) = o { - format!("{v:?}") - } else { - "None".into() - } - }; - panic!("{} Expected {:?}, got {:?} instead.", ctx(), f(e), f(a)); - }; - // Comparison. - match (exp, act) { - (Some(exp), Some(act)) => { - if act != exp { - fail(Some(exp), Some(act)); - } - i += 1; - } - (None, Some(act)) => { - fail(None, Some(act)); - } - (Some(exp), None) => { - fail(Some(exp), None); - } - (None, None) => break, - } - e_remaining -= 1; - } - } - } - - // Empty underlying iterator. - check("", 0, &[""]); // 0^0 = 1. - check("", 1, &[]); - check("", 2, &[]); - check("", 3, &[]); - - // Singleton underlying iterator. - check("a", 0, &[""]); - check("a", 1, &["a"]); - check("a", 2, &["aa"]); - check("a", 3, &["aaa"]); - - // Underlying pair. - check("ab", 0, &[""]); - check("ab", 1, &["a", "b"]); - check("ab", 2, &["aa", "ab", "ba", "bb"]); - check( - "ab", - 3, - &["aaa", "aab", "aba", "abb", "baa", "bab", "bba", "bbb"], - ); - - // Underlying triplet. - check("abc", 0, &[""]); - check("abc", 1, &["a", "b", "c"]); - check( - "abc", - 2, - &["aa", "ab", "ac", "ba", "bb", "bc", "ca", "cb", "cc"], - ); - check( - "abc", - 3, - &[ - "aaa", "aab", "aac", "aba", "abb", "abc", "aca", "acb", "acc", "baa", "bab", "bac", - "bba", "bbb", "bbc", "bca", "bcb", "bcc", "caa", "cab", "cac", "cba", "cbb", "cbc", - "cca", "ccb", "ccc", - ], - ); - } - - #[test] - fn nth() { - fn check(origin: &str, pow: u32, expected: &[(usize, (Option<&str>, usize))]) { - println!("================== ({origin:?}^{pow})"); - let mut it = origin - .chars() - .collect::>() - .into_iter() - .cartesian_power(pow); - let mut total_n = Vec::new(); - for &(n, (exp, e_hint)) in expected { - total_n.push(n); - let ctx = || { - format!( - "Failed nth({}) iteration for {:?}^{}.", - total_n - .iter() - .map(ToString::to_string) - .collect::>() - .join(", "), - origin, - pow, - ) - }; - let act = it.nth(n); - let a_hint = it.size_hint(); - let act = act.map(|v| v.into_iter().collect::()); - println!( - " → {}", - if let Some(act) = act.as_ref() { - act - } else { - "🗙" - } - ); - if act.as_ref().map(String::as_str) != exp { - panic!("{} Expected {:?}, got {:?} instead.", ctx(), exp, act); - } - // Check size hint after stepping. - assert!( - if let (la, Some(ha)) = a_hint { - la == e_hint && ha == e_hint - } else { - false - }, - "{} Expected size hint: ({e}, Some({e})), got instead: {a:?}.", - ctx(), - e = e_hint, - a = a_hint, - ); - } - } - - // Ease test read/write. - // Accept a sequence of ' ' yielded by a call to `.nth(n)`. - macro_rules! check { - ($base:expr, $pow:expr => $( $n:literal $expected:expr )+ ) => { - check($base, $pow, &[$(($n, $expected)),+]); - }; - } - - // Degenerated 0th power. - let o = (None, 1); - let e = (Some(""), 0); // "e"mpty. - for base in ["", "a", "ab"] { - check!(base, 0 => 0 e 0 o 0 e 0 o); - check!(base, 0 => 0 e 1 o 0 e 1 o); - check!(base, 0 => 0 e 2 o 1 o 0 e); - check!(base, 0 => 1 o 0 e 0 o 1 o); - check!(base, 0 => 1 o 1 o 0 e 0 o); - check!(base, 0 => 1 o 2 o 0 e 1 o); - check!(base, 0 => 2 o 0 e 1 o 0 e); - check!(base, 0 => 2 o 1 o 2 o 0 e); - check!(base, 0 => 2 o 2 o 0 e 2 o); - } - - // Degenerated 0-base. - let o = (None, 0); - for pow in [1, 2, 3] { - check!("", pow => 0 o 0 o 0 o 0 o); - check!("", pow => 1 o 1 o 1 o 1 o); - check!("", pow => 2 o 2 o 2 o 2 o); - check!("", pow => 0 o 1 o 2 o 0 o); - check!("", pow => 2 o 1 o 0 o 1 o); - } - - // Unit power. - let o = (None, 1); - let a = (Some("a"), 0); - check!("a", 1 => 0 a 0 o 0 a 0 o 0 a 0 o); - check!("a", 1 => 1 o 1 o 1 o 1 o 1 o 1 o); - check!("a", 1 => 2 o 2 o 2 o 2 o 2 o 2 o); - check!("a", 1 => 0 a 1 o 0 a 1 o 0 a 1 o); - check!("a", 1 => 1 o 0 a 1 o 0 a 1 o 0 a); - check!("a", 1 => 0 a 2 o 0 a 2 o 0 a 2 o); - check!("a", 1 => 2 o 0 a 2 o 0 a 2 o 0 a); - check!("a", 1 => 1 o 2 o 1 o 2 o 1 o 2 o); - check!("a", 1 => 2 o 1 o 2 o 1 o 2 o 1 o); - check!("a", 1 => 0 a 1 o 2 o 0 a 1 o 2 o); - check!("a", 1 => 0 a 2 o 1 o 0 a 2 o 1 o); - check!("a", 1 => 1 o 0 a 2 o 1 o 0 a 2 o); - check!("a", 1 => 1 o 2 o 0 a 1 o 2 o 0 a 1 o 2 o 0 a); - check!("a", 1 => 2 o 0 a 1 o 2 o 0 a 1 o 2 o 0 a 1 o); - check!("a", 1 => 2 o 1 o 0 a 2 o 1 o 0 a 2 o 1 o 0 a); - check!("a", 1 => 1 o 0 a 0 o 1 o 0 a 0 o 1 o 0 a 0 o); - check!("a", 1 => 1 o 1 o 0 a 0 o 1 o 1 o 0 a 0 o 1 o 1 o); - - let o = (None, 2); - let a = (Some("a"), 1); - let b = (Some("b"), 0); - check!("ab", 1 => 0 a 0 b 0 o 0 a 0 b 0 o); - check!("ab", 1 => 1 b 1 o 1 b 1 o 1 b 1 o); - check!("ab", 1 => 2 o 2 o 2 o 2 o 2 o 2 o); - check!("ab", 1 => 0 a 1 o 0 a 1 o 0 a 1 o); - check!("ab", 1 => 1 b 0 o 1 b 0 o 1 b 0 o); - check!("ab", 1 => 0 a 2 o 0 a 2 o 0 a 2 o); - check!("ab", 1 => 2 o 0 a 2 o 0 a 2 o 0 a); - check!("ab", 1 => 1 b 2 o 1 b 2 o 1 b 2 o); - check!("ab", 1 => 2 o 1 b 2 o 1 b 2 o 1 b); - check!("ab", 1 => 0 a 1 o 2 o 0 a 1 o 2 o); - check!("ab", 1 => 0 a 2 o 1 b 0 o 2 o 1 b); - check!("ab", 1 => 1 b 0 o 2 o 1 b 0 o 2 o); - check!("ab", 1 => 1 b 2 o 0 a 1 o 2 o 0 a 1 o 2 o 0 a); - check!("ab", 1 => 2 o 0 a 1 o 2 o 0 a 1 o); - check!("ab", 1 => 2 o 1 b 0 o 2 o 1 b 0 o); - check!("ab", 1 => 1 b 0 o 0 a 1 o 0 a 0 b 1 o 0 a 0 b); - check!("ab", 1 => 1 b 1 o 0 a 0 b 1 o 1 b 0 o 0 a 1 o 1 b); - - let o = (None, 3); - let a = (Some("a"), 2); - let b = (Some("b"), 1); - let c = (Some("c"), 0); - check!("abc", 1 => 0 a 0 b 0 c 0 o 0 a 0 b 0 c 0 o); - check!("abc", 1 => 1 b 1 o 1 b 1 o 1 b 1 o); - check!("abc", 1 => 2 c 2 o 2 c 2 o 2 c 2 o); - check!("abc", 1 => 0 a 1 c 0 o 1 b 0 c 1 o 0 a 1 c); - check!("abc", 1 => 1 b 0 c 1 o 0 a 1 c 0 o 1 b 0 c); - check!("abc", 1 => 0 a 2 o 0 a 2 o 0 a 2 o); - check!("abc", 1 => 2 c 0 o 2 c 0 o 2 c 0 o); - check!("abc", 1 => 1 b 2 o 1 b 2 o 1 b 2 o); - check!("abc", 1 => 2 c 1 o 2 c 1 o 2 c 1 o); - check!("abc", 1 => 0 a 1 c 2 o 0 a 1 c 2 o); - check!("abc", 1 => 0 a 2 o 1 b 0 c 2 o 1 b); - check!("abc", 1 => 1 b 0 c 2 o 1 b 0 c 2 o); - check!("abc", 1 => 1 b 2 o 0 a 1 c 2 o 0 a 1 c 2 o 0 a); - check!("abc", 1 => 2 c 0 o 1 b 2 o 0 a 1 c 2 o 0 a 1 c); - check!("abc", 1 => 2 c 1 o 0 a 2 o 1 b 0 c 2 o 1 b 0 c); - check!("abc", 1 => 1 b 0 c 0 o 1 b 0 c 0 o 1 b 0 c 0 o); - check!("abc", 1 => 1 b 1 o 0 a 0 b 1 o 1 b 0 c 0 o 1 b 1 o); - - // Higher power. - let o = (None, 1); - let aa = (Some("aa"), 0); - check!("a", 2 => 0 aa 0 o 0 aa 0 o 0 aa 0 o); - check!("a", 2 => 1 o 1 o 1 o 1 o 1 o 1 o); - check!("a", 2 => 2 o 2 o 2 o 2 o 2 o 2 o); - check!("a", 2 => 0 aa 1 o 0 aa 1 o 0 aa 1 o); - check!("a", 2 => 1 o 0 aa 1 o 0 aa 1 o 0 aa 1 o); - check!("a", 2 => 0 aa 2 o 0 aa 2 o 0 aa 2 o); - check!("a", 2 => 2 o 0 aa 2 o 0 aa 2 o 0 aa); - check!("a", 2 => 1 o 2 o 1 o 2 o 1 o 2 o); - check!("a", 2 => 2 o 1 o 2 o 1 o 2 o 1 o); - check!("a", 2 => 0 aa 1 o 2 o 0 aa 1 o 2 o); - check!("a", 2 => 0 aa 2 o 1 o 0 aa 2 o 1 o); - check!("a", 2 => 1 o 0 aa 2 o 1 o 0 aa 2 o); - check!("a", 2 => 1 o 2 o 0 aa 1 o 2 o 0 aa 1 o 2 o 0 aa); - check!("a", 2 => 2 o 0 aa 1 o 2 o 0 aa 1 o 2 o 0 aa 1 o); - check!("a", 2 => 2 o 1 o 0 aa 2 o 1 o 0 aa 2 o 1 o 0 aa); - check!("a", 2 => 1 o 0 aa 0 o 1 o 0 aa 0 o 1 o 0 aa 0 o); - check!("a", 2 => 1 o 1 o 0 aa 0 o 1 o 1 o 0 aa 0 o 1 o 1 o); - - let o = (None, 4); - let aa = (Some("aa"), 3); - let ab = (Some("ab"), 2); - let ba = (Some("ba"), 1); - let bb = (Some("bb"), 0); - check!("ab", 2 => 0 aa 0 ab 0 ba 0 bb 0 o 0 aa 0 ab); - check!("ab", 2 => 1 ab 1 bb 1 o 1 ab 1 bb 1 o); - check!("ab", 2 => 2 ba 2 o 2 ba 2 o 2 ba 2 o); - check!("ab", 2 => 0 aa 1 ba 0 bb 1 o 0 aa 1 ba); - check!("ab", 2 => 1 ab 0 ba 1 o 0 aa 1 ba 0 bb 1 o 0 aa 1 ba 0 bb); - check!("ab", 2 => 0 aa 2 bb 0 o 2 ba 0 bb 2 o 0 aa 2 bb); - check!("ab", 2 => 2 ba 0 bb 2 o 0 aa 2 bb 0 o 2 ba 0 bb); - check!("ab", 2 => 1 ab 2 o 1 ab 2 o 1 ab 2 o); - check!("ab", 2 => 2 ba 1 o 2 ba 1 o 2 ba 1 o); - check!("ab", 2 => 0 aa 1 ba 2 o 0 aa 1 ba 2 o); - check!("ab", 2 => 0 aa 2 bb 1 o 0 aa 2 bb 1 o); - check!("ab", 2 => 1 ab 0 ba 2 o 1 ab 0 ba 2 o); - check!("ab", 2 => 1 ab 2 o 0 aa 1 ba 2 o 0 aa 1 ba 2 o 0 aa); - check!("ab", 2 => 2 ba 0 bb 1 o 2 ba 0 bb 1 o 2 ba 0 bb 1 o); - check!("ab", 2 => 2 ba 1 o 0 aa 2 bb 1 o 0 aa 2 bb 1 o 0 aa); - check!("ab", 2 => 1 ab 0 ba 0 bb 1 o 0 aa 0 ab 1 bb 0 o 0 aa 1 ba 0 bb 0 o 1 ab 0 ba 0 bb); - check!("ab", 2 => 1 ab 1 bb 0 o 0 aa 1 ba 1 o 0 aa 0 ab 1 bb 1 o 0 aa 0 ab 1 bb 1 o); - - let o = (None, 9); - let aa = (Some("aa"), 8); - let ab = (Some("ab"), 7); - let ac = (Some("ac"), 6); - let ba = (Some("ba"), 5); - let bb = (Some("bb"), 4); - let bc = (Some("bc"), 3); - let ca = (Some("ca"), 2); - let cb = (Some("cb"), 1); - let cc = (Some("cc"), 0); - check!("abc", 2 => 0 aa 0 ab 0 ac 0 ba 0 bb 0 bc 0 ca 0 cb 0 cc 0 o 0 aa 0 ab 0 ac 0 ba); - check!("abc", 2 => 1 ab 1 ba 1 bc 1 cb 1 o 1 ab 1 ba 1 bc 1 cb 1 o 1 ab 1 ba 1 bc 1 cb 1 o); - check!("abc", 2 => 2 ac 2 bc 2 cc 2 o 2 ac 2 bc 2 cc 2 o 2 ac 2 bc 2 cc 2 o 2 ac 2 bc 2 cc); - check!("abc", 2 => 0 aa 1 ac 0 ba 1 bc 0 ca 1 cc 0 o 1 ab 0 ac 1 bb 0 bc 1 cb 0 cc 1 o); - check!("abc", 2 => 1 ab 0 ac 1 bb 0 bc 1 cb 0 cc 1 o 0 aa 1 ac 0 ba 1 bc 0 ca 1 cc 0 o); - check!("abc", 2 => 0 aa 2 ba 0 bb 2 cb 0 cc 2 o 0 aa 2 ba 0 bb 2 cb 0 cc 2 o 0 aa 2 ba); - check!("abc", 2 => 2 ac 0 ba 2 ca 0 cb 2 o 0 aa 2 ba 0 bb 2 cb 0 cc 2 o 0 aa 2 ba 0 bb); - check!("abc", 2 => 1 ab 2 bb 1 ca 2 o 1 ab 2 bb 1 ca 2 o 1 ab 2 bb 1 ca 2 o 1 ab 2 bb 1 ca); - check!("abc", 2 => 2 ac 1 bb 2 cb 1 o 2 ac 1 bb 2 cb 1 o 2 ac 1 bb 2 cb 1 o 2 ac 1 bb 2 cb); - check!("abc", 2 => 0 aa 1 ac 2 bc 0 ca 1 cc 2 o 0 aa 1 ac 2 bc 0 ca 1 cc 2 o 0 aa 1 ac); - check!("abc", 2 => 0 aa 2 ba 1 bc 0 ca 2 o 1 ab 0 ac 2 bc 1 cb 0 cc 2 o 1 ab 0 ac 2 bc); - check!("abc", 2 => 1 ab 0 ac 2 bc 1 cb 0 cc 2 o 1 ab 0 ac 2 bc 1 cb 0 cc 2 o 1 ab 0 ac); - check!("abc", 2 => 1 ab 2 bb 0 bc 1 cb 2 o 0 aa 1 ac 2 bc 0 ca 1 cc 2 o 0 aa 1 ac 2 bc); - check!("abc", 2 => 2 ac 0 ba 1 bc 2 cc 0 o 1 ab 2 bb 0 bc 1 cb 2 o 0 aa 1 ac 2 bc 0 ca); - check!("abc", 2 => 2 ac 1 bb 0 bc 2 cc 1 o 0 aa 2 ba 1 bc 0 ca 2 o 1 ab 0 ac 2 bc 1 cb); - check!("abc", 2 => 1 ab 0 ac 0 ba 1 bc 0 ca 0 cb 1 o 0 aa 0 ab 1 ba 0 bb 0 bc 1 cb 0 cc); - check!("abc", 2 => 1 ab 1 ba 0 bb 0 bc 1 cb 1 o 0 aa 0 ab 1 ba 1 bc 0 ca 0 cb 1 o 1 ab); - - let o = (None, 27); - let aaa = (Some("aaa"), 26); - let aab = (Some("aab"), 25); - let aac = (Some("aac"), 24); - let aba = (Some("aba"), 23); - let abb = (Some("abb"), 22); - let abc = (Some("abc"), 21); - let aca = (Some("aca"), 20); - let acb = (Some("acb"), 19); - let acc = (Some("acc"), 18); - let baa = (Some("baa"), 17); - let bab = (Some("bab"), 16); - let bac = (Some("bac"), 15); - let bba = (Some("bba"), 14); - let bbb = (Some("bbb"), 13); - let bbc = (Some("bbc"), 12); - let bca = (Some("bca"), 11); - let bcb = (Some("bcb"), 10); - let bcc = (Some("bcc"), 9); - let caa = (Some("caa"), 8); - let cab = (Some("cab"), 7); - let cac = (Some("cac"), 6); - let cba = (Some("cba"), 5); - let cbb = (Some("cbb"), 4); - let cbc = (Some("cbc"), 3); - let cca = (Some("cca"), 2); - let ccb = (Some("ccb"), 1); - let ccc = (Some("ccc"), 0); - - check!( - "abc", 3 => - 0 aaa - 0 aab - 0 aac - 0 aba - 0 abb - 0 abc - 0 aca - 0 acb - 0 acc - 0 baa - 0 bab - 0 bac - 0 bba - 0 bbb - 0 bbc - 0 bca - 0 bcb - 0 bcc - 0 caa - 0 cab - 0 cac - 0 cba - 0 cbb - 0 cbc - 0 cca - 0 ccb - 0 ccc - 0 o - 0 aaa - 0 aab - 0 aac - ); - - check!( - "abc", 3 => - 1 aab - 1 aba - 1 abc - 1 acb - 1 baa - 1 bac - 1 bbb - 1 bca - 1 bcc - 1 cab - 1 cba - 1 cbc - 1 ccb - 1 o - 1 aab - 1 aba - ); - - check!( - "abc", 3 => - 2 aac - 2 abc - 2 acc - 2 bac - 2 bbc - 2 bcc - 2 cac - 2 cbc - 2 ccc - 2 o - 2 aac - 2 abc - ); - - check!( - "abc", 3 => - 3 aba 3 acb 3 bac 3 bca 3 cab 3 cbc 3 o - 3 aba 3 acb 3 bac 3 bca 3 cab 3 cbc 3 o - ); - - check!( - "abc", 3 => - 4 abb 4 baa 4 bbc 4 cab 4 cca 4 o - 4 abb 4 baa 4 bbc 4 cab 4 cca 4 o - ); - - check!( - "abc", 3 => - 5 abc 5 bac 5 bcc 5 cbc 5 o - 5 abc 5 bac 5 bcc 5 cbc 5 o - ); - - check!("abc", 3 => - 6 aca 6 bbb 6 cac 6 o - 6 aca 6 bbb 6 cac 6 o - ); - - check!("abc", 3 => - 7 acb 7 bca 7 cbc 7 o - 7 acb 7 bca 7 cbc 7 o - ); - - check!( - "abc", 3 => - 8 acc 8 bcc 8 ccc 8 o - 8 acc 8 bcc 8 ccc 8 o - ); - - check!("abc", 3 => 9 baa 9 cab 9 o 9 baa 9 cab 9 o); - check!("abc", 3 => 10 bab 10 cba 10 o 10 bab 10 cba 10 o); - check!("abc", 3 => 11 bac 11 cbc 11 o 11 bac 11 cbc 11 o); - check!("abc", 3 => 12 bba 12 ccb 12 o 12 bba 12 ccb 12 o); - check!("abc", 3 => 13 bbb 13 o 13 bbb 13 o); - check!("abc", 3 => 14 bbc 14 o 14 bbc 14 o); - check!("abc", 3 => 25 ccb 25 o 25 ccb 25 o); - check!("abc", 3 => 26 ccc 26 o 26 ccc 26 o); - check!("abc", 3 => 27 o 27 o 27 o 27 o); - check!("abc", 3 => 28 o 28 o 28 o 28 o); - } -} diff --git a/src/lib.rs b/src/lib.rs index 66c8a892f..9b27644db 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,7 +60,6 @@ extern crate alloc; #[cfg(feature = "use_alloc")] use alloc::{collections::VecDeque, string::String, vec::Vec}; -use cartesian_power::CartesianPower; pub use either::Either; use core::borrow::Borrow; @@ -91,7 +90,10 @@ pub use std::iter as __std_iter; /// The concrete iterator types. pub mod structs { #[cfg(feature = "use_alloc")] - pub use crate::adaptors::MultiProduct; + pub use crate::{ + cartesian_power::{CartesianPower, Indices as CartesianPowerIndices}, + adaptors::MultiProduct + }; pub use crate::adaptors::{ Batching, Coalesce, Dedup, DedupBy, DedupByWithCount, DedupWithCount, FilterMapOk, FilterOk, Interleave, InterleaveShortest, MapInto, MapOk, Positions, Product, PutBack, @@ -1215,6 +1217,66 @@ pub trait Itertools: Iterator { adaptors::cartesian_product(self, other.into_iter()) } + /// Return an iterator adaptor that iterates over the given cartesian power of + /// the element yielded by `self`. + /// + /// Iterator element type is a collection `Vec` of size `pow`. + /// The collection is a fresh vector + /// allocated on every `.next()` call + /// and populated with `Self::Item::clone` + /// until the cartesian power is exhausted. + /// The underlying iterator is only consumed once, + /// and all its yielded items are stored within the adaptor for future cloning. + /// + /// If this is too much allocation for you, + /// you may try the underlying streaming + /// [`CartesianPowerIndices`] instead. + /// + /// The resulting iterator is "cycling", + /// meaning that, after the last `.next()` call has yielded `None`, + /// you can call `.next()` again to resume iteration from the start, + /// as many times as needed. + /// + /// ``` + /// use itertools::Itertools; + /// + /// let mut it = "abc".chars().cartesian_power(2); + /// assert_eq!(it.next(), Some(vec!['a', 'a'])); + /// assert_eq!(it.next(), Some(vec!['a', 'b'])); + /// assert_eq!(it.next(), Some(vec!['a', 'c'])); // Underlying a"abc".chars()` consumed. + /// assert_eq!(it.next(), Some(vec!['b', 'a'])); + /// assert_eq!(it.next(), Some(vec!['b', 'b'])); + /// assert_eq!(it.next(), Some(vec!['b', 'c'])); + /// assert_eq!(it.next(), Some(vec!['c', 'a'])); + /// assert_eq!(it.next(), Some(vec!['c', 'b'])); + /// assert_eq!(it.next(), Some(vec!['c', 'c'])); + /// assert_eq!(it.next(), None); // Cartesian product exhausted. + /// assert_eq!(it.next(), Some(vec!['a', 'a'])); // Cycle: start over, *ad libitum*. + /// assert_eq!(it.next(), Some(vec!['a', 'b'])); + /// // ... + /// + /// let mut it = "ab".chars().cartesian_power(3); + /// assert_eq!(it.next(), Some(vec!['a', 'a', 'a'])); + /// assert_eq!(it.next(), Some(vec!['a', 'a', 'b'])); + /// assert_eq!(it.next(), Some(vec!['a', 'b', 'a'])); + /// assert_eq!(it.next(), Some(vec!['a', 'b', 'b'])); + /// assert_eq!(it.next(), Some(vec!['b', 'a', 'a'])); + /// assert_eq!(it.next(), Some(vec!['b', 'a', 'b'])); + /// assert_eq!(it.next(), Some(vec!['b', 'b', 'a'])); + /// assert_eq!(it.next(), Some(vec!['b', 'b', 'b'])); + /// assert_eq!(it.next(), None); + /// assert_eq!(it.next(), Some(vec!['a', 'a', 'a'])); + /// assert_eq!(it.next(), Some(vec!['a', 'a', 'b'])); + /// // ... + /// ``` + fn cartesian_power(self, pow: u32) -> CartesianPower + where + Self: Sized, + Self::Item: Clone, + { + CartesianPower::new(self, pow) + } + /// Return an iterator adaptor that iterates over the cartesian product of /// all subiterators returned by meta-iterator `self`. /// @@ -1795,23 +1857,6 @@ pub trait Itertools: Iterator { combinations_with_replacement::combinations_with_replacement(self, k) } - /// Returns an iterator yielding the successive elements - /// of the cartesian power of the set described by the original iterator. - /// - /// ``` - /// use itertools::Itertools; - /// - /// TODO: illustrative example. - /// ``` - #[cfg(feature = "use_alloc")] - fn cartesian_power(self, pow: u32) -> CartesianPower - where - Self: Sized, - Self::Item: Clone, - { - cartesian_power::cartesian_power(self, pow) - } - /// Return an iterator adaptor that iterates over all k-permutations of the /// elements from an iterator. /// diff --git a/tests/cartesian_power.rs b/tests/cartesian_power.rs new file mode 100644 index 000000000..5670f5065 --- /dev/null +++ b/tests/cartesian_power.rs @@ -0,0 +1,486 @@ +//! Use chars and string to ease testing of every yielded iterator values. +use itertools::Itertools; + +#[test] +fn next() { + fn check(origin: &str, pow: u32, expected: &[&str]) { + println!("================== ({origin:?}^{pow})"); + let mut it_act = origin + .chars() + .collect::>() // Collect to get exact size hint upper bound. + .into_iter() + .cartesian_power(pow); + // Check size_hint on the fly. + let e_hint = expected.len(); + // Check thrice that it's cycling. + for r in 1..=3 { + println!("- - {r} - - - - - -"); + let mut it_exp = expected.iter(); + let mut i = 0; + let mut e_remaining = e_hint; + loop { + // Common context to emit in case of test failure. + let ctx = + || format!("Failed iteration {i} (repetition {r}) for {origin:?}^{pow}.",); + // Check size hints. + let a_remaining = it_act.size_hint(); + assert!( + if let (la, Some(ha)) = a_remaining { + la == e_remaining && ha == e_remaining + } else { + false + }, + "{} Expected size hint: ({e}, Some({e})), got instead: {a:?}.", + ctx(), + e = e_remaining, + a = a_remaining, + ); + // Actual/expected iterators steps. + let act = it_act.next(); + let exp = it_exp.next().map(|e| e.chars().collect::>()); + println!(" {act:?}"); + // Possible failure report. + let fail = |e, a| { + let f = |o| { + if let Some(v) = o { + format!("{v:?}") + } else { + "None".into() + } + }; + panic!("{} Expected {:?}, got {:?} instead.", ctx(), f(e), f(a)); + }; + // Comparison. + match (exp, act) { + (Some(exp), Some(act)) => { + if act != exp { + fail(Some(exp), Some(act)); + } + i += 1; + } + (None, Some(act)) => { + fail(None, Some(act)); + } + (Some(exp), None) => { + fail(Some(exp), None); + } + (None, None) => break, + } + e_remaining -= 1; + } + } + } + + // Empty underlying iterator. + check("", 0, &[""]); // 0^0 = 1. + check("", 1, &[]); + check("", 2, &[]); + check("", 3, &[]); + + // Singleton underlying iterator. + check("a", 0, &[""]); + check("a", 1, &["a"]); + check("a", 2, &["aa"]); + check("a", 3, &["aaa"]); + + // Underlying pair. + check("ab", 0, &[""]); + check("ab", 1, &["a", "b"]); + check("ab", 2, &["aa", "ab", "ba", "bb"]); + check( + "ab", + 3, + &["aaa", "aab", "aba", "abb", "baa", "bab", "bba", "bbb"], + ); + + // Underlying triplet. + check("abc", 0, &[""]); + check("abc", 1, &["a", "b", "c"]); + check( + "abc", + 2, + &["aa", "ab", "ac", "ba", "bb", "bc", "ca", "cb", "cc"], + ); + check( + "abc", + 3, + &[ + "aaa", "aab", "aac", "aba", "abb", "abc", "aca", "acb", "acc", "baa", "bab", "bac", + "bba", "bbb", "bbc", "bca", "bcb", "bcc", "caa", "cab", "cac", "cba", "cbb", "cbc", + "cca", "ccb", "ccc", + ], + ); +} + +#[test] +#[allow(clippy::too_many_lines)] // Numerous detailed tests, especially corner-cases. +#[allow(clippy::similar_names)] // On-purpose `aab`, `aba`, `aac`, etc. +#[allow(clippy::many_single_char_names)] // On-purpose short `o`, `e`, `a`: ease test readability. +fn nth() { + fn check(origin: &str, pow: u32, expected: &[(usize, (Option<&str>, usize))]) { + println!("================== ({origin:?}^{pow})"); + let mut it = origin + .chars() + .collect::>() + .into_iter() + .cartesian_power(pow); + let mut total_n = Vec::new(); + for &(n, (exp, e_hint)) in expected { + total_n.push(n); + let ctx = || { + format!( + "Failed nth({}) iteration for {:?}^{}.", + total_n + .iter() + .map(ToString::to_string) + .collect::>() + .join(", "), + origin, + pow, + ) + }; + let act = it.nth(n); + let a_hint = it.size_hint(); + let act = act.map(|v| v.into_iter().collect::()); + println!( + " → {}", + if let Some(act) = act.as_ref() { + act + } else { + "🗙" + } + ); + assert!( + act.as_deref() == exp, + "{} Expected {exp:?}, got {act:?} instead.", + ctx(), + ); + // Check size hint after stepping. + assert!( + if let (la, Some(ha)) = a_hint { + la == e_hint && ha == e_hint + } else { + false + }, + "{} Expected size hint: ({e}, Some({e})), got instead: {a:?}.", + ctx(), + e = e_hint, + a = a_hint, + ); + } + } + + // Ease test read/write. + // Accept a sequence of ' ' yielded by a call to `.nth(n)`. + macro_rules! check { + ($base:expr, $pow:expr => $( $n:literal $expected:expr )+ ) => { + check($base, $pow, &[$(($n, $expected)),+]); + }; + } + + // Degenerated 0th power. + let o = (None, 1); + let e = (Some(""), 0); // "e"mpty. + for base in ["", "a", "ab"] { + check!(base, 0 => 0 e 0 o 0 e 0 o); + check!(base, 0 => 0 e 1 o 0 e 1 o); + check!(base, 0 => 0 e 2 o 1 o 0 e); + check!(base, 0 => 1 o 0 e 0 o 1 o); + check!(base, 0 => 1 o 1 o 0 e 0 o); + check!(base, 0 => 1 o 2 o 0 e 1 o); + check!(base, 0 => 2 o 0 e 1 o 0 e); + check!(base, 0 => 2 o 1 o 2 o 0 e); + check!(base, 0 => 2 o 2 o 0 e 2 o); + } + + // Degenerated 0-base. + let o = (None, 0); + for pow in [1, 2, 3] { + check!("", pow => 0 o 0 o 0 o 0 o); + check!("", pow => 1 o 1 o 1 o 1 o); + check!("", pow => 2 o 2 o 2 o 2 o); + check!("", pow => 0 o 1 o 2 o 0 o); + check!("", pow => 2 o 1 o 0 o 1 o); + } + + // Unit power. + let o = (None, 1); + let a = (Some("a"), 0); + check!("a", 1 => 0 a 0 o 0 a 0 o 0 a 0 o); + check!("a", 1 => 1 o 1 o 1 o 1 o 1 o 1 o); + check!("a", 1 => 2 o 2 o 2 o 2 o 2 o 2 o); + check!("a", 1 => 0 a 1 o 0 a 1 o 0 a 1 o); + check!("a", 1 => 1 o 0 a 1 o 0 a 1 o 0 a); + check!("a", 1 => 0 a 2 o 0 a 2 o 0 a 2 o); + check!("a", 1 => 2 o 0 a 2 o 0 a 2 o 0 a); + check!("a", 1 => 1 o 2 o 1 o 2 o 1 o 2 o); + check!("a", 1 => 2 o 1 o 2 o 1 o 2 o 1 o); + check!("a", 1 => 0 a 1 o 2 o 0 a 1 o 2 o); + check!("a", 1 => 0 a 2 o 1 o 0 a 2 o 1 o); + check!("a", 1 => 1 o 0 a 2 o 1 o 0 a 2 o); + check!("a", 1 => 1 o 2 o 0 a 1 o 2 o 0 a 1 o 2 o 0 a); + check!("a", 1 => 2 o 0 a 1 o 2 o 0 a 1 o 2 o 0 a 1 o); + check!("a", 1 => 2 o 1 o 0 a 2 o 1 o 0 a 2 o 1 o 0 a); + check!("a", 1 => 1 o 0 a 0 o 1 o 0 a 0 o 1 o 0 a 0 o); + check!("a", 1 => 1 o 1 o 0 a 0 o 1 o 1 o 0 a 0 o 1 o 1 o); + + let o = (None, 2); + let a = (Some("a"), 1); + let b = (Some("b"), 0); + check!("ab", 1 => 0 a 0 b 0 o 0 a 0 b 0 o); + check!("ab", 1 => 1 b 1 o 1 b 1 o 1 b 1 o); + check!("ab", 1 => 2 o 2 o 2 o 2 o 2 o 2 o); + check!("ab", 1 => 0 a 1 o 0 a 1 o 0 a 1 o); + check!("ab", 1 => 1 b 0 o 1 b 0 o 1 b 0 o); + check!("ab", 1 => 0 a 2 o 0 a 2 o 0 a 2 o); + check!("ab", 1 => 2 o 0 a 2 o 0 a 2 o 0 a); + check!("ab", 1 => 1 b 2 o 1 b 2 o 1 b 2 o); + check!("ab", 1 => 2 o 1 b 2 o 1 b 2 o 1 b); + check!("ab", 1 => 0 a 1 o 2 o 0 a 1 o 2 o); + check!("ab", 1 => 0 a 2 o 1 b 0 o 2 o 1 b); + check!("ab", 1 => 1 b 0 o 2 o 1 b 0 o 2 o); + check!("ab", 1 => 1 b 2 o 0 a 1 o 2 o 0 a 1 o 2 o 0 a); + check!("ab", 1 => 2 o 0 a 1 o 2 o 0 a 1 o); + check!("ab", 1 => 2 o 1 b 0 o 2 o 1 b 0 o); + check!("ab", 1 => 1 b 0 o 0 a 1 o 0 a 0 b 1 o 0 a 0 b); + check!("ab", 1 => 1 b 1 o 0 a 0 b 1 o 1 b 0 o 0 a 1 o 1 b); + + let o = (None, 3); + let a = (Some("a"), 2); + let b = (Some("b"), 1); + let c = (Some("c"), 0); + check!("abc", 1 => 0 a 0 b 0 c 0 o 0 a 0 b 0 c 0 o); + check!("abc", 1 => 1 b 1 o 1 b 1 o 1 b 1 o); + check!("abc", 1 => 2 c 2 o 2 c 2 o 2 c 2 o); + check!("abc", 1 => 0 a 1 c 0 o 1 b 0 c 1 o 0 a 1 c); + check!("abc", 1 => 1 b 0 c 1 o 0 a 1 c 0 o 1 b 0 c); + check!("abc", 1 => 0 a 2 o 0 a 2 o 0 a 2 o); + check!("abc", 1 => 2 c 0 o 2 c 0 o 2 c 0 o); + check!("abc", 1 => 1 b 2 o 1 b 2 o 1 b 2 o); + check!("abc", 1 => 2 c 1 o 2 c 1 o 2 c 1 o); + check!("abc", 1 => 0 a 1 c 2 o 0 a 1 c 2 o); + check!("abc", 1 => 0 a 2 o 1 b 0 c 2 o 1 b); + check!("abc", 1 => 1 b 0 c 2 o 1 b 0 c 2 o); + check!("abc", 1 => 1 b 2 o 0 a 1 c 2 o 0 a 1 c 2 o 0 a); + check!("abc", 1 => 2 c 0 o 1 b 2 o 0 a 1 c 2 o 0 a 1 c); + check!("abc", 1 => 2 c 1 o 0 a 2 o 1 b 0 c 2 o 1 b 0 c); + check!("abc", 1 => 1 b 0 c 0 o 1 b 0 c 0 o 1 b 0 c 0 o); + check!("abc", 1 => 1 b 1 o 0 a 0 b 1 o 1 b 0 c 0 o 1 b 1 o); + + // Higher power. + let o = (None, 1); + let aa = (Some("aa"), 0); + check!("a", 2 => 0 aa 0 o 0 aa 0 o 0 aa 0 o); + check!("a", 2 => 1 o 1 o 1 o 1 o 1 o 1 o); + check!("a", 2 => 2 o 2 o 2 o 2 o 2 o 2 o); + check!("a", 2 => 0 aa 1 o 0 aa 1 o 0 aa 1 o); + check!("a", 2 => 1 o 0 aa 1 o 0 aa 1 o 0 aa 1 o); + check!("a", 2 => 0 aa 2 o 0 aa 2 o 0 aa 2 o); + check!("a", 2 => 2 o 0 aa 2 o 0 aa 2 o 0 aa); + check!("a", 2 => 1 o 2 o 1 o 2 o 1 o 2 o); + check!("a", 2 => 2 o 1 o 2 o 1 o 2 o 1 o); + check!("a", 2 => 0 aa 1 o 2 o 0 aa 1 o 2 o); + check!("a", 2 => 0 aa 2 o 1 o 0 aa 2 o 1 o); + check!("a", 2 => 1 o 0 aa 2 o 1 o 0 aa 2 o); + check!("a", 2 => 1 o 2 o 0 aa 1 o 2 o 0 aa 1 o 2 o 0 aa); + check!("a", 2 => 2 o 0 aa 1 o 2 o 0 aa 1 o 2 o 0 aa 1 o); + check!("a", 2 => 2 o 1 o 0 aa 2 o 1 o 0 aa 2 o 1 o 0 aa); + check!("a", 2 => 1 o 0 aa 0 o 1 o 0 aa 0 o 1 o 0 aa 0 o); + check!("a", 2 => 1 o 1 o 0 aa 0 o 1 o 1 o 0 aa 0 o 1 o 1 o); + + let o = (None, 4); + let aa = (Some("aa"), 3); + let ab = (Some("ab"), 2); + let ba = (Some("ba"), 1); + let bb = (Some("bb"), 0); + check!("ab", 2 => 0 aa 0 ab 0 ba 0 bb 0 o 0 aa 0 ab); + check!("ab", 2 => 1 ab 1 bb 1 o 1 ab 1 bb 1 o); + check!("ab", 2 => 2 ba 2 o 2 ba 2 o 2 ba 2 o); + check!("ab", 2 => 0 aa 1 ba 0 bb 1 o 0 aa 1 ba); + check!("ab", 2 => 1 ab 0 ba 1 o 0 aa 1 ba 0 bb 1 o 0 aa 1 ba 0 bb); + check!("ab", 2 => 0 aa 2 bb 0 o 2 ba 0 bb 2 o 0 aa 2 bb); + check!("ab", 2 => 2 ba 0 bb 2 o 0 aa 2 bb 0 o 2 ba 0 bb); + check!("ab", 2 => 1 ab 2 o 1 ab 2 o 1 ab 2 o); + check!("ab", 2 => 2 ba 1 o 2 ba 1 o 2 ba 1 o); + check!("ab", 2 => 0 aa 1 ba 2 o 0 aa 1 ba 2 o); + check!("ab", 2 => 0 aa 2 bb 1 o 0 aa 2 bb 1 o); + check!("ab", 2 => 1 ab 0 ba 2 o 1 ab 0 ba 2 o); + check!("ab", 2 => 1 ab 2 o 0 aa 1 ba 2 o 0 aa 1 ba 2 o 0 aa); + check!("ab", 2 => 2 ba 0 bb 1 o 2 ba 0 bb 1 o 2 ba 0 bb 1 o); + check!("ab", 2 => 2 ba 1 o 0 aa 2 bb 1 o 0 aa 2 bb 1 o 0 aa); + check!("ab", 2 => 1 ab 0 ba 0 bb 1 o 0 aa 0 ab 1 bb 0 o 0 aa 1 ba 0 bb 0 o 1 ab 0 ba 0 bb); + check!("ab", 2 => 1 ab 1 bb 0 o 0 aa 1 ba 1 o 0 aa 0 ab 1 bb 1 o 0 aa 0 ab 1 bb 1 o); + + let o = (None, 9); + let aa = (Some("aa"), 8); + let ab = (Some("ab"), 7); + let ac = (Some("ac"), 6); + let ba = (Some("ba"), 5); + let bb = (Some("bb"), 4); + let bc = (Some("bc"), 3); + let ca = (Some("ca"), 2); + let cb = (Some("cb"), 1); + let cc = (Some("cc"), 0); + check!("abc", 2 => 0 aa 0 ab 0 ac 0 ba 0 bb 0 bc 0 ca 0 cb 0 cc 0 o 0 aa 0 ab 0 ac 0 ba); + check!("abc", 2 => 1 ab 1 ba 1 bc 1 cb 1 o 1 ab 1 ba 1 bc 1 cb 1 o 1 ab 1 ba 1 bc 1 cb 1 o); + check!("abc", 2 => 2 ac 2 bc 2 cc 2 o 2 ac 2 bc 2 cc 2 o 2 ac 2 bc 2 cc 2 o 2 ac 2 bc 2 cc); + check!("abc", 2 => 0 aa 1 ac 0 ba 1 bc 0 ca 1 cc 0 o 1 ab 0 ac 1 bb 0 bc 1 cb 0 cc 1 o); + check!("abc", 2 => 1 ab 0 ac 1 bb 0 bc 1 cb 0 cc 1 o 0 aa 1 ac 0 ba 1 bc 0 ca 1 cc 0 o); + check!("abc", 2 => 0 aa 2 ba 0 bb 2 cb 0 cc 2 o 0 aa 2 ba 0 bb 2 cb 0 cc 2 o 0 aa 2 ba); + check!("abc", 2 => 2 ac 0 ba 2 ca 0 cb 2 o 0 aa 2 ba 0 bb 2 cb 0 cc 2 o 0 aa 2 ba 0 bb); + check!("abc", 2 => 1 ab 2 bb 1 ca 2 o 1 ab 2 bb 1 ca 2 o 1 ab 2 bb 1 ca 2 o 1 ab 2 bb 1 ca); + check!("abc", 2 => 2 ac 1 bb 2 cb 1 o 2 ac 1 bb 2 cb 1 o 2 ac 1 bb 2 cb 1 o 2 ac 1 bb 2 cb); + check!("abc", 2 => 0 aa 1 ac 2 bc 0 ca 1 cc 2 o 0 aa 1 ac 2 bc 0 ca 1 cc 2 o 0 aa 1 ac); + check!("abc", 2 => 0 aa 2 ba 1 bc 0 ca 2 o 1 ab 0 ac 2 bc 1 cb 0 cc 2 o 1 ab 0 ac 2 bc); + check!("abc", 2 => 1 ab 0 ac 2 bc 1 cb 0 cc 2 o 1 ab 0 ac 2 bc 1 cb 0 cc 2 o 1 ab 0 ac); + check!("abc", 2 => 1 ab 2 bb 0 bc 1 cb 2 o 0 aa 1 ac 2 bc 0 ca 1 cc 2 o 0 aa 1 ac 2 bc); + check!("abc", 2 => 2 ac 0 ba 1 bc 2 cc 0 o 1 ab 2 bb 0 bc 1 cb 2 o 0 aa 1 ac 2 bc 0 ca); + check!("abc", 2 => 2 ac 1 bb 0 bc 2 cc 1 o 0 aa 2 ba 1 bc 0 ca 2 o 1 ab 0 ac 2 bc 1 cb); + check!("abc", 2 => 1 ab 0 ac 0 ba 1 bc 0 ca 0 cb 1 o 0 aa 0 ab 1 ba 0 bb 0 bc 1 cb 0 cc); + check!("abc", 2 => 1 ab 1 ba 0 bb 0 bc 1 cb 1 o 0 aa 0 ab 1 ba 1 bc 0 ca 0 cb 1 o 1 ab); + + let o = (None, 27); + let aaa = (Some("aaa"), 26); + let aab = (Some("aab"), 25); + let aac = (Some("aac"), 24); + let aba = (Some("aba"), 23); + let abb = (Some("abb"), 22); + let abc = (Some("abc"), 21); + let aca = (Some("aca"), 20); + let acb = (Some("acb"), 19); + let acc = (Some("acc"), 18); + let baa = (Some("baa"), 17); + let bab = (Some("bab"), 16); + let bac = (Some("bac"), 15); + let bba = (Some("bba"), 14); + let bbb = (Some("bbb"), 13); + let bbc = (Some("bbc"), 12); + let bca = (Some("bca"), 11); + let bcb = (Some("bcb"), 10); + let bcc = (Some("bcc"), 9); + let caa = (Some("caa"), 8); + let cab = (Some("cab"), 7); + let cac = (Some("cac"), 6); + let cba = (Some("cba"), 5); + let cbb = (Some("cbb"), 4); + let cbc = (Some("cbc"), 3); + let cca = (Some("cca"), 2); + let ccb = (Some("ccb"), 1); + let ccc = (Some("ccc"), 0); + + check!( + "abc", 3 => + 0 aaa + 0 aab + 0 aac + 0 aba + 0 abb + 0 abc + 0 aca + 0 acb + 0 acc + 0 baa + 0 bab + 0 bac + 0 bba + 0 bbb + 0 bbc + 0 bca + 0 bcb + 0 bcc + 0 caa + 0 cab + 0 cac + 0 cba + 0 cbb + 0 cbc + 0 cca + 0 ccb + 0 ccc + 0 o + 0 aaa + 0 aab + 0 aac + ); + + check!( + "abc", 3 => + 1 aab + 1 aba + 1 abc + 1 acb + 1 baa + 1 bac + 1 bbb + 1 bca + 1 bcc + 1 cab + 1 cba + 1 cbc + 1 ccb + 1 o + 1 aab + 1 aba + ); + + check!( + "abc", 3 => + 2 aac + 2 abc + 2 acc + 2 bac + 2 bbc + 2 bcc + 2 cac + 2 cbc + 2 ccc + 2 o + 2 aac + 2 abc + ); + + check!( + "abc", 3 => + 3 aba 3 acb 3 bac 3 bca 3 cab 3 cbc 3 o + 3 aba 3 acb 3 bac 3 bca 3 cab 3 cbc 3 o + ); + + check!( + "abc", 3 => + 4 abb 4 baa 4 bbc 4 cab 4 cca 4 o + 4 abb 4 baa 4 bbc 4 cab 4 cca 4 o + ); + + check!( + "abc", 3 => + 5 abc 5 bac 5 bcc 5 cbc 5 o + 5 abc 5 bac 5 bcc 5 cbc 5 o + ); + + check!("abc", 3 => + 6 aca 6 bbb 6 cac 6 o + 6 aca 6 bbb 6 cac 6 o + ); + + check!("abc", 3 => + 7 acb 7 bca 7 cbc 7 o + 7 acb 7 bca 7 cbc 7 o + ); + + check!( + "abc", 3 => + 8 acc 8 bcc 8 ccc 8 o + 8 acc 8 bcc 8 ccc 8 o + ); + + check!("abc", 3 => 9 baa 9 cab 9 o 9 baa 9 cab 9 o); + check!("abc", 3 => 10 bab 10 cba 10 o 10 bab 10 cba 10 o); + check!("abc", 3 => 11 bac 11 cbc 11 o 11 bac 11 cbc 11 o); + check!("abc", 3 => 12 bba 12 ccb 12 o 12 bba 12 ccb 12 o); + check!("abc", 3 => 13 bbb 13 o 13 bbb 13 o); + check!("abc", 3 => 14 bbc 14 o 14 bbc 14 o); + check!("abc", 3 => 25 ccb 25 o 25 ccb 25 o); + check!("abc", 3 => 26 ccc 26 o 26 ccc 26 o); + check!("abc", 3 => 27 o 27 o 27 o 27 o); + check!("abc", 3 => 28 o 28 o 28 o 28 o); +}