From ac30a048000c08c99122f62a638d94482b0b0324 Mon Sep 17 00:00:00 2001 From: Iago-lito Date: Thu, 3 Oct 2024 23:19:32 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=9A=A7=20Implement=20`.size=5Fhint()`=20(?= =?UTF-8?q?untested=20yet).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cartesian_power.rs | 107 +++++++++++++++++++++++++++++++++-------- src/lib.rs | 2 +- 2 files changed, 87 insertions(+), 22 deletions(-) diff --git a/src/cartesian_power.rs b/src/cartesian_power.rs index 0f5d53e60..953334ce6 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,7 +343,77 @@ where } fn size_hint(&self) -> (usize, Option) { - todo!() + let Self { + pow, + iter, + items, + indices, + } = self; + + // The following case analysis matches implementation of `.next()`. + 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.map(|b| b.checked_pow(pow)).flatten(), + ) + } + + // 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(minus_already) + .unwrap_or(usize::MAX), + b.map(|b| (b + already).checked_pow(pow).map(minus_already)) + .flatten(), + ) + } + + // 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 comp = base - 1; + let mut r = 1usize; + for (&i, rank) in indices.iter().rev().zip(0u32..) { + let increment = (comp - i).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 { @@ -378,19 +443,19 @@ 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); + // Check size_hint on the fly. + let e_hint = expected.len(); // HERE: do. // Check thrice that it's cycling. for r in 1..=3 { println!("- - {r} - - - - - -"); @@ -473,7 +538,7 @@ 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>)]) { println!("================== ({origin:?}^{pow})"); let mut it = origin.chars().cartesian_power(pow); let mut total_n = Vec::new(); diff --git a/src/lib.rs b/src/lib.rs index 945391d67..c938b6b40 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1757,7 +1757,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,