Skip to content

Commit

Permalink
🚧 Implement .nth() with cycling.
Browse files Browse the repository at this point in the history
  • Loading branch information
iago-lito committed Oct 2, 2024
1 parent 47f44a6 commit 6273611
Showing 1 changed file with 219 additions and 59 deletions.
278 changes: 219 additions & 59 deletions src/cartesian_power.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
}
}
}

Expand Down Expand Up @@ -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::<Vec<_>>()) {
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::<Vec<_>>()) {
panic!(
"Failed nth({}) iteration for {:?}^{}. \
Expected {:?}, got {:?} instead.",
total_n
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(", "),
r,
origin,
pow,
exp,
act
);
}
total_n
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.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);
}
}

0 comments on commit 6273611

Please sign in to comment.