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,