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)]);