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 3 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
138 changes: 137 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,142 @@ 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:
/// - pow is 0
/// - the original iterator is empty
/// - the adaptor is exhausted
Empty,
/// Otherwise, there will be items yielded.
Filled {
/// Copy of the original iterator, never consumed.
original: I,
/// Clones of the original iterator, scrolled at various speeds
/// and regularly replaced by fresh clones once exhausted.
clones: Vec<I>,
/// Current state of the iterator: the next 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 {
// Trivial case.
0 => Power::Empty,
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() {
// New trivial case: the given 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 trivial case last as it should be less frequent.
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

}
}

fn size_hint(&self) -> (usize, Option<usize>) {
match self {
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
32 changes: 32 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,37 @@ 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'],
/// ],
/// );
/// ```
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