Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Naive implementation of cartesian_power. #486

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
148 changes: 147 additions & 1 deletion src/adaptors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub use self::map::MapResults;
pub use self::multi_product::*;

use std::fmt;
use std::iter::{Fuse, Peekable, FromIterator};
use std::iter::{self, Fuse, Peekable, FromIterator};
use std::marker::PhantomData;
use crate::size_hint;

Expand Down Expand Up @@ -361,6 +361,152 @@ impl<I, J> Iterator for Product<I, J>
}
}

#[derive(Debug, Clone)]
/// An iterator adaptor that iterates over the cartesian power of
/// the element set of iterator `I`.
///
/// Iterator element type is `Vec<I::Item>`, with length `pow`.
///
/// See [`.cartesian_power()`](../trait.Itertools.html#method.cartesian_power) for more information.
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
pub enum Power<I>
where
I: Iterator,
{
/// The adaptor is 'Empty' if either:
/// - The original iterator is empty.
/// - The adaptor is exhausted.
Empty,
/// The adaptor is 'Degenerated' if the given pow == 0.
/// In this case, it yields 1 empty item before switching to Empty itself.
/// This ensures that cartesian_power(it, n).count() == it.count().pow(n) for all n.
/// In the case it.is_empty() && n == 0, we choose convention 0^0=1.
Degenerated,
/// In any other case, there will be non-empty items yielded.
Filled {
/// Copy of the original iterator, never consumed.
original: I,
/// Clones of the original iterator,
/// scrolled at various speeds from left to right
/// and regularly replaced by fresh clones once exhausted.
clones: Vec<I>,
/// Current state of the iterator: the next non-empty item to be yielded.
state: Vec<I::Item>,
},
}

/// Create a new cartesian power iterator.
///
/// Iterator element type is `Vec<I::Item>` with length `pow`.
pub fn cartesian_power<I>(it: I, pow: usize) -> Power<I>
where
I: Iterator + Clone,
I::Item: Clone,
{
match pow {
0 => Power::Degenerated,
pow => {
// Test one clone first to determine whether
// some values are actually yielded by the given iterator.
let mut first_clone = it.clone();
match first_clone.next() {
// No item will be yielded if the iterator is empty.
None => Power::Empty,
Some(first_state) => {
// Produce other clones until we have `pow` of them.
let mut clones = iter::once(first_clone)
.chain((0..(pow - 1)).map(|_| it.clone()))
.collect::<Vec<_>>();
Power::Filled {
// Prepare initial state and ensure that all clones have been stepped once.
state: iter::once(first_state)
.chain((1..pow).map(|i| clones[i].next().unwrap()))
.collect::<Vec<_>>(),
clones,
original: it,
}
}
}
}
}
}

impl<I> Iterator for Power<I>
where
I: Iterator + Clone,
I::Item: Clone,
{
type Item = Vec<I::Item>;

fn next(&mut self) -> Option<Self::Item> {
match self {
Power::Filled {
original,
clones,
state,
} => {
// Prepare a copy of current state to be yielded to user.
let res = state.clone();
// Scroll right clone first.
for i in (0..clones.len()).rev() {
match clones[i].next() {
Some(next_value) => {
state[i] = next_value;
// No need to step left clones
// while this one is not exhausted.
break;
}
None => {
if i > 0 {
// When a clone is exhausted,
// replace with a fresh one
// and go step the clone to the left.
let mut fresh_clone = original.clone();
state[i] = fresh_clone.next().unwrap();
clones[i] = fresh_clone;
} else {
// When the leftmost clone is exhausted,
// then the cartesian power is exhausted.
*self = Power::Empty;
break; // (useless, but reassures the borrow-checker)
}
}
}
}
Some(res)
}
// Check les frequent cases last.
Power::Empty => None,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For pow==0/Power::Empty, shouldn't the iterator should yield a single element (namely the empty vector)? (Instead of yielding no elements at all.)

This would ensure that it.cartesian_power(n) yields it.count().pow(n) elements. (We had a similar case back then where (I think it was) combinations had a special case for n==0, which lead to inconsitencies.)

It would possibly also help in getting rid of some special casings (i.e. possible eliminate Power).

Copy link
Author

@iago-lito iago-lito Oct 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@phimuemue Well, I had this vague feeling I was neglicting something sneaky here. Thank you for pointing it out.
The sneaky stuff is that cartesian_product("abc", 0) should not yield the same as cartesian_product("", 3). And I bet that your criterion it.count().pow(n) is the right thing to consider. If we agree that:

2 1 0
2 2^2=4 2^1=2 2^0=1
1 1^2=1 1^1=1 1^1=1
0 0^2=0 0^1=0 0^0=1?

Then the adaptor should yield:

2 1 0
"ab" ["aa", "ab", "ba", "bb"] ["a", "b"] [""]
"a" ["aa"] ["a"] [""]
"" [] [] [""]?

So there is not 1 degenerated case, but 2 or 3, depending on the convention we pick for the 0^0 situation. I propose we stick to the "combinatorics" convention that 0^0=1, so there are only 2 corner cases. The only other solution I can think of is erroring or panicking instead when encountering cartesian_product("", 0).

I'll update the PR to better accomodate these degenerated situations :)

[from the future] see 9027ef0

Power::Degenerated => {
// Yield One empty item and get exhausted.
*self = Power::Empty;
Some(Vec::new())
}
}
}

fn size_hint(&self) -> (usize, Option<usize>) {
match self {
Power::Degenerated => (1, Some(1)),
Power::Empty => (0, Some(0)),
iago-lito marked this conversation as resolved.
Show resolved Hide resolved
Power::Filled {
original, clones, ..
} => {
// Exactly original.count()^pow items are expected,
// but this may be larger than usize?

// Is there no size_hint::pow_scalar ?
let factor = original.size_hint();
let mut accumulator = factor;
for _ in 0..clones.len() {
accumulator = size_hint::mul(accumulator, factor)
}
accumulator
}
}
}
}

/// A “meta iterator adaptor”. Its closure receives a reference to the iterator
/// and may pick off as many elements as it likes, to produce the next iterator element.
///
Expand Down
39 changes: 39 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ pub mod structs {
FilterMapOk,
FilterOk,
Product,
Power,
PutBack,
Batching,
MapInto,
Expand Down Expand Up @@ -980,6 +981,44 @@ pub trait Itertools : Iterator {
adaptors::cartesian_product(self, other.into_iter())
}

/// Return an iterator adaptor that iterates over the cartesian power of
/// the element set of the iterator.
///
/// Iterator element type is `Vec<Self::Item>`.
///
/// ```
/// use itertools::Itertools;
///
/// let it = "ab".chars().cartesian_power(3);
/// itertools::assert_equal(
/// it,
/// vec![
/// ['a', 'a', 'a'],
/// ['a', 'a', 'b'],
/// ['a', 'b', 'a'],
/// ['a', 'b', 'b'],
/// ['b', 'a', 'a'],
/// ['b', 'a', 'b'],
/// ['b', 'b', 'a'],
/// ['b', 'b', 'b'],
/// ],
/// );
///
/// // Watch various possible degenerated cases:
/// let empty_result: Vec<Vec<char>> = vec![];
/// let singleton_result: Vec<Vec<char>> = vec![vec![]];
/// itertools::assert_equal("".chars().cartesian_power(3), empty_result.clone());
/// itertools::assert_equal("ab".chars().cartesian_power(0), singleton_result.clone());
/// itertools::assert_equal("".chars().cartesian_power(0), singleton_result.clone());
/// ```
fn cartesian_power(self, pow: usize) -> Power<Self>
where
Self: Sized + Clone,
Self::Item: Clone,
{
adaptors::cartesian_power(self, pow)
}

/// Return an iterator adaptor that iterates over the cartesian product of
/// all subiterators returned by meta-iterator `self`.
///
Expand Down
5 changes: 5 additions & 0 deletions tests/quick.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,11 @@ quickcheck! {
fn size_product(a: Iter<u16>, b: Iter<u16>) -> bool {
correct_size_hint(a.cartesian_product(b))
}
fn size_power(_it: Iter<u16>, _pow: usize) -> bool {
// XXX: Not sure why this times out. Does it explode?
// correct_size_hint(it.clone().cartesian_power(pow))
true
}
fn size_product3(a: Iter<u16>, b: Iter<u16>, c: Iter<u16>) -> bool {
correct_size_hint(iproduct!(a, b, c))
}
Expand Down