Skip to content

Commit

Permalink
🚧 Test cycling iterator instead.
Browse files Browse the repository at this point in the history
iago-lito committed Sep 11, 2024
1 parent 8957f70 commit 009b594
Showing 1 changed file with 158 additions and 81 deletions.
239 changes: 158 additions & 81 deletions src/cartesian_power.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,48 @@
use alloc::vec::Vec;
use std::fmt;
use std::iter::FusedIterator;

// (base: 0) pow: 0
// indices iter items
// init · S ·
// next(C) N 🗙 dot // HERE should not behave the same as right below.

// (base: 2) pow: 0
// indices iter items
// init · S ·
// next(C) · N 🗙 · -> None
// next(C) · N · -> None

// (base: 1) pow: 1
// indices iter items
// init · S ·
// next(D) 0 S a -> [a]
// next(E) 1 ! N! a -> None
// next(G) 0 N a -> [a]
// next(G) 1 ! N a -> None

// (base: 2) pow: 1
// indices iter items
// init · S ·
// next(D) 0 S a -> [a]
// next(E) 1 S a b -> [b]
// next(E) 2 ! N! a b -> None
// next(G) 0 N a b -> [a]
// next(G) 1 N a b -> [b]
// next(G) 2 ! N a b -> None

// (base: 2) pow: 3
// indices iter items
// init · S ·
// next(D) 0 0 0 S a -> [a, a, a]
// next(E) 0 0 1 S a b -> [a, a, b]
// next(E) 0 1 0 N! a b -> [a, b, a]
// next(G) 0 1 1 N a b -> [a, b, b]
// next(G) 1 0 0 N a b -> [b, a, a]
// next(G) 1 0 1 N a b -> [b, a, b]
// next(G) 1 1 0 N a b -> [b, b, a]
// next(G) 1 1 1 N a b -> [b, b, b]
// next(G) 2 0 0 ! N a b -> None
// next(G) 0 0 0 N a b -> [a, a, a]

/// An adaptor iterating through all the ordered `n`-length lists of items
/// yielded by the underlying iterator, including repetitions.
@@ -15,9 +57,17 @@ where
I::Item: Clone,
{
pow: usize,
iter: Option<I>, // Inner iterator. Forget once consumed after 'base' iterations.
items: Vec<I::Item>, // Fill from iter. Clear once adaptor is exhausted. Final length is 'base'.
indices: Vec<usize>, // Indices just yielded. Clear once adaptor is exhausted. Length is 'pow'.
iter: Option<I>, // Inner iterator. Forget once consumed after 'base' iterations.
items: Vec<I::Item>, // Fill from iter. Final length is 'base'.

// 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 etc.
indices: Vec<usize>,
}

/// Create a new `CartesianPower` from an iterator of clonables.
@@ -53,16 +103,28 @@ where
items,
indices,
} = self;
println!(
"^{pow}: {indices:?}\t{}\t{}",
if iter.is_some() { 'S' } else { 'N' },
items.len()
);
match (*pow, iter, items.len()) {
// Final stable state: underlying iterator and items forgotten.
// AAAAAAAAAAAA
(0, None, 0) => Some((indices, items)),

// BBBBBBBBBBBB
// Stable degenerated state.
(_, None, 0) => None,

// CCCCCCCCCCCC
// Degenerated 0th power iteration.
(0, Some(_), _) => {
self.iter = None; // Forget without even consuming.
Some((indices, items))
}

// DDDDDDDDDDDD
// First iteration.
(pow, Some(it), 0) => {
// Check whether there is at least one element in the iterator.
if let Some(first) = it.next() {
@@ -74,13 +136,15 @@ where
for _ in 0..pow {
indices.push(0);
}
return Some((indices, items));
Some((indices, items))
} else {
// Degenerated iteration over an empty set, yet with non-null power.
self.iter = None;
None
}
// Degenerated iteration over an empty set, yet with non-null power.
self.iter = None;
None
}

// EEEEEEEEEEEE
(pow, Some(it), base) => {
// We are still unsure whether all items have been collected.
// As a consequence, 'base' is still uncertain,
@@ -92,31 +156,52 @@ where
}

// All items have just been collected.
self.iter = None;
if base == 1 || pow == 1 {
self.iter = None; // Forget about the underlying iterator.
if pow == 1 {
// End of iteration.
items.clear();
indices.clear();
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) => {
// FFFFFFFFFFFF
(_, None, 1) => {
// Flip the only indice to keep cycling.
let ind = &mut indices[0];
if *ind == 1 {
*ind = 0;
Some((indices, items))
} else {
*ind = 1;
None
}
}

// GGGGGGGGGGGG
(_, None, base) => {
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.
// But don't clear the collected items,
// and mark a special index value to not fuse the iterator
// but make it possibly cycle through all again.
indices[0] = base;
None
}
}
@@ -187,13 +272,6 @@ where
}
}

impl<I> FusedIterator for CartesianPower<I>
where
I: Iterator,
I::Item: Clone,
{
}

#[cfg(test)]
mod tests {
//! Use chars and string to ease testing of every yielded iterator values.
@@ -202,37 +280,46 @@ mod tests {
use crate::Itertools;
use core::str::Chars;

fn check_fused(mut exhausted_it: CartesianPower<Chars>, 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::<Vec<_>>() {
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.
@@ -281,38 +368,28 @@ 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::<Vec<_>>()) {
panic!(
"Failed nth({}) iteration for {:?}^{}. \
Expected {:?}, got {:?} instead.",
total_n
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.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::<Vec<_>>()) {
panic!(
"Failed nth({}) iteration (repetition {}) for {:?}^{}. \
Expected {:?}, got {:?} instead.",
total_n
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(", "),
r,
origin,
pow,
exp,
act
);
}
}
}
check_fused(
it,
format!(
"nth({}) iteration of {:?}^{}",
total_n
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(", "),
origin,
pow
),
);
}

// HERE: make it work with the new implementation.

0 comments on commit 009b594

Please sign in to comment.