diff --git a/src/cartesian_power.rs b/src/cartesian_power.rs index 3954a3e4c..c99ff653b 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,176 @@ 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() { + q = (*index + q) / base; + *index = (*index + n) % 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. + #[allow(clippy::too_many_lines)] // HERE: fix when tests pass. fn increment_indices_by_n(&mut self, n: usize) -> Option<(&[usize], &[I::Item])> { - todo!() + let Self { + pow, + iter, + items, + indices, + } = self; + print!( + "^{pow}: +{n} {} {indices:?}\t{:?}\t-> ", + if iter.is_some() { 'S' } else { 'N' }, + items.as_ref().map(Vec::len) + ); + + match (*pow, iter, &mut *items, n) { + // First iteration with degenerated 0th power. + (0, Some(_), items @ None, 0) => { + println!("AAA"); + // Same as .next(). + self.iter = None; + let empty = items.insert(Vec::new()); + Some((indices, empty)) + } + (0, Some(_), None, _) => { + println!("BBB"); + // Saturate. + self.iter = None; + None + } + + // Subsequent degenerated 0th power iteration. + // Same as `.next()`. + (0, None, items @ None, 0) => { + println!("CCC"); + Some((indices, items.insert(Vec::new()))) + } + // Saturate. + (0, None, items, _) => { + println!("DDD"); + *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) => { + println!("EEE"); + 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, _) => { + println!("FFF"); + None + } + + // Subsequent iteration in the general case. + // Again, immediate saturation is an option. + (pow, Some(it), Some(items), mut remaining) => { + println!("GGG"); + if let Some(next) = it.next() { + items.push(next); + loop { + if remaining == 0 { + indices[pow - 1] += n; // 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, 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) => { + println!("HHH"); + let base = items.len(); + if indices[0] == base { + // Start over for a new round. + indices[0] = 0; + } + if Self::inbounds_increment_by(n, indices, base) { + return Some((indices, items)); + } + // Immediate re-saturation. + indices[0] = base; + None + } + } } } @@ -319,66 +482,63 @@ 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 {:?}^{}. \ + 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(", "), - r, - origin, - pow, - exp, - act - ); - } + 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. + macro_rules! check { + ($base:expr, $pow:expr => $( $n:literal $expected:expr)+ ) => { + check($base, $pow, &[$(($n, $expected)),+]); + }; + } + + // Degenerated cases. + for base in ["", "a", "ab"] { + check!(base, 0 => 0 Some("") 0 None 0 Some("") 0 None ); + check!(base, 0 => 0 Some("") 1 None 0 Some("") 1 None ); + check!(base, 0 => 0 Some("") 2 None 1 None 0 Some("")); + check!(base, 0 => 1 None 0 Some("") 0 None 1 None ); + check!(base, 0 => 1 None 1 None 0 Some("") 0 None ); + check!(base, 0 => 1 None 2 None 0 Some("") 1 None ); + check!(base, 0 => 2 None 0 Some("") 1 None 0 Some("")); + check!(base, 0 => 2 None 1 None 2 None 0 Some("")); + check!(base, 0 => 2 None 2 None 0 Some("") 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)]); + check!("a", 1 => 0 Some("a") 0 None 0 Some("a") 0 None ); // HERE: fix. + check!("a", 1 => 0 Some("a") 1 None 0 Some("a") 1 None ); + check!("a", 1 => 0 Some("a") 2 None 1 None 0 Some("a")); + check!("a", 1 => 1 None 0 Some("a") 0 None 1 None ); + check!("a", 1 => 1 None 1 None 0 Some("a") 0 None ); + check!("a", 1 => 1 None 2 None 0 Some("a") 1 None ); + check!("a", 1 => 2 None 0 Some("a") 1 None 0 Some("a")); + check!("a", 1 => 2 None 1 None 2 None 0 Some("a")); + check!("a", 1 => 2 None 2 None 0 Some("a") 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); } }