Skip to content

Commit

Permalink
🚧 Test cycling iterator instead.
Browse files Browse the repository at this point in the history
  • Loading branch information
iago-lito authored and phimuemue committed Oct 7, 2024
1 parent de97b82 commit 99148d1
Showing 1 changed file with 126 additions and 98 deletions.
224 changes: 126 additions & 98 deletions src/cartesian_power.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -15,9 +14,19 @@ 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: Option<Vec<I::Item>>, // 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<usize>,
}

/// Create a new `CartesianPower` from an iterator of clonables.
Expand All @@ -29,7 +38,7 @@ where
CartesianPower {
pow,
iter: Some(iter),
items: Vec::new(),
items: None,
indices: Vec::new(),
}
}
Expand All @@ -53,70 +62,99 @@ 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);
indices[pow - 1] += 1;
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
}
}
Expand Down Expand Up @@ -187,13 +225,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.
Expand All @@ -202,41 +233,50 @@ 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.
check("", 0, &[""]);
check("", 0, &[""]); // 0^0 = 1.
check("", 1, &[]);
check("", 2, &[]);
check("", 3, &[]);
Expand Down Expand Up @@ -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::<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.

// Check degenerated cases.
check("", 0, &[(0, Some("")), (0, None)]);
check("", 0, &[(0, Some("")), (1, None)]);
Expand Down

0 comments on commit 99148d1

Please sign in to comment.