Skip to content

Commit 8cdf928

Browse files
committed
FEAT: Powerset iterator adaptor
An iterator to iterate through the powerset of the elements from an iterator.
1 parent 2b005c2 commit 8cdf928

File tree

6 files changed

+169
-12
lines changed

6 files changed

+169
-12
lines changed

src/combinations.rs

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,8 @@ impl<I> fmt::Debug for Combinations<I>
3131
pub fn combinations<I>(iter: I, k: usize) -> Combinations<I>
3232
where I: Iterator
3333
{
34-
let mut pool: LazyBuffer<I> = LazyBuffer::new(iter);
35-
36-
for _ in 0..k {
37-
if !pool.get_next() {
38-
break;
39-
}
40-
}
34+
let mut pool = LazyBuffer::new(iter);
35+
pool.prefill(k);
4136

4237
Combinations {
4338
indices: (0..k).collect(),
@@ -46,14 +41,49 @@ pub fn combinations<I>(iter: I, k: usize) -> Combinations<I>
4641
}
4742
}
4843

44+
impl<I: Iterator> Combinations<I> {
45+
/// Returns the length of a combination produced by this iterator.
46+
#[inline]
47+
pub fn k(&self) -> usize { self.indices.len() }
48+
49+
/// Returns the (current) length of the pool from which combination elements are
50+
/// selected. This value can change between invocations of [`next`].
51+
///
52+
/// [`next`]: #method.next
53+
#[inline]
54+
pub fn n(&self) -> usize { self.pool.len() }
55+
56+
/// Resets this `Combinations` back to an initial state for combinations of length
57+
/// `k` over the same pool data source. If `k` is larger than the current length
58+
/// of the data pool an attempt is made to prefill the pool so that it holds `k`
59+
/// elements.
60+
pub(crate) fn reset(&mut self, k: usize) {
61+
self.first = true;
62+
63+
if k < self.indices.len() {
64+
self.indices.truncate(k);
65+
for i in 0..k {
66+
self.indices[i] = i;
67+
}
68+
69+
} else {
70+
for i in 0..self.indices.len() {
71+
self.indices[i] = i;
72+
}
73+
self.indices.extend(self.indices.len()..k);
74+
self.pool.prefill(k);
75+
}
76+
}
77+
}
78+
4979
impl<I> Iterator for Combinations<I>
5080
where I: Iterator,
5181
I::Item: Clone
5282
{
5383
type Item = Vec<I::Item>;
5484
fn next(&mut self) -> Option<Self::Item> {
5585
if self.first {
56-
if self.pool.is_done() {
86+
if self.k() > self.n() {
5787
return None;
5888
}
5989
self.first = false;

src/lazy_buffer.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,6 @@ where
2424
self.buffer.len()
2525
}
2626

27-
pub fn is_done(&self) -> bool {
28-
self.done
29-
}
30-
3127
pub fn get_next(&mut self) -> bool {
3228
if self.done {
3329
return false;
@@ -44,6 +40,17 @@ where
4440
}
4541
}
4642
}
43+
44+
pub fn prefill(&mut self, len: usize) {
45+
let buffer_len = self.buffer.len();
46+
47+
if !self.done && len > buffer_len {
48+
let delta = len - buffer_len;
49+
50+
self.buffer.extend(self.it.by_ref().take(delta));
51+
self.done = self.buffer.len() < len;
52+
}
53+
}
4754
}
4855

4956
impl<I, J> Index<J> for LazyBuffer<I>

src/lib.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ pub mod structs {
134134
pub use crate::permutations::Permutations;
135135
pub use crate::process_results_impl::ProcessResults;
136136
#[cfg(feature = "use_alloc")]
137+
pub use crate::powerset::Powerset;
138+
#[cfg(feature = "use_alloc")]
137139
pub use crate::put_back_n_impl::PutBackN;
138140
#[cfg(feature = "use_alloc")]
139141
pub use crate::rciter_impl::RcIter;
@@ -207,6 +209,8 @@ mod peek_nth;
207209
mod peeking_take_while;
208210
#[cfg(feature = "use_alloc")]
209211
mod permutations;
212+
#[cfg(feature = "use_alloc")]
213+
mod powerset;
210214
mod process_results_impl;
211215
#[cfg(feature = "use_alloc")]
212216
mod put_back_n_impl;
@@ -1406,6 +1410,42 @@ pub trait Itertools : Iterator {
14061410
permutations::permutations(self, k)
14071411
}
14081412

1413+
/// Return an iterator that iterates through the powerset of the elements from an
1414+
/// iterator.
1415+
///
1416+
/// Iterator element type is `Vec<Self::Item>`. The iterator produces a new `Vec`
1417+
/// per iteration, and clones the iterator elements.
1418+
///
1419+
/// The powerset of a set contains all subsets including the empty set and the full
1420+
/// input set. A powerset has length _2^n_ where _n_ is the length of the input
1421+
/// set.
1422+
///
1423+
/// Each `Vec` produced by this iterator represents a subset of the elements
1424+
/// produced by the source iterator.
1425+
///
1426+
/// ```
1427+
/// use itertools::Itertools;
1428+
///
1429+
/// let sets = (1..4).powerset().collect::<Vec<_>>();
1430+
/// itertools::assert_equal(sets, vec![
1431+
/// vec![],
1432+
/// vec![1],
1433+
/// vec![2],
1434+
/// vec![3],
1435+
/// vec![1, 2],
1436+
/// vec![1, 3],
1437+
/// vec![2, 3],
1438+
/// vec![1, 2, 3],
1439+
/// ]);
1440+
/// ```
1441+
#[cfg(feature = "use_alloc")]
1442+
fn powerset(self) -> Powerset<Self>
1443+
where Self: Sized,
1444+
Self::Item: Clone,
1445+
{
1446+
powerset::powerset(self)
1447+
}
1448+
14091449
/// Return an iterator adaptor that pads the sequence to a minimum length of
14101450
/// `min` by filling missing elements using a closure `f`.
14111451
///

src/powerset.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
use std::fmt;
2+
use alloc::vec::Vec;
3+
4+
use super::combinations::{Combinations, combinations};
5+
6+
/// An iterator to iterate through the powerset of the elements from an iterator.
7+
///
8+
/// See [`.powerset()`](../trait.Itertools.html#method.powerset) for more
9+
/// information.
10+
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
11+
pub struct Powerset<I: Iterator> {
12+
combs: Combinations<I>,
13+
}
14+
15+
impl<I> Clone for Powerset<I>
16+
where I: Clone + Iterator,
17+
I::Item: Clone,
18+
{
19+
clone_fields!(combs);
20+
}
21+
22+
impl<I> fmt::Debug for Powerset<I>
23+
where I: Iterator + fmt::Debug,
24+
I::Item: fmt::Debug,
25+
{
26+
debug_fmt_fields!(Powerset, combs);
27+
}
28+
29+
/// Create a new `Powerset` from a clonable iterator.
30+
pub fn powerset<I>(src: I) -> Powerset<I>
31+
where I: Iterator,
32+
I::Item: Clone,
33+
{
34+
Powerset { combs: combinations(src, 0) }
35+
}
36+
37+
impl<I> Iterator for Powerset<I>
38+
where
39+
I: Iterator,
40+
I::Item: Clone,
41+
{
42+
type Item = Vec<I::Item>;
43+
44+
fn next(&mut self) -> Option<Self::Item> {
45+
if let Some(elt) = self.combs.next() {
46+
Some(elt)
47+
} else if self.combs.k() < self.combs.n()
48+
|| self.combs.k() == 0
49+
{
50+
self.combs.reset(self.combs.k() + 1);
51+
self.combs.next()
52+
} else {
53+
None
54+
}
55+
}
56+
}

tests/quick.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,13 @@ quickcheck! {
907907
}
908908
}
909909

910+
quickcheck! {
911+
fn size_powerset(it: Iter<u8, Exact>) -> bool {
912+
// Powerset cardinality gets large very quickly, limit input to keep test fast.
913+
correct_size_hint(it.take(12).powerset())
914+
}
915+
}
916+
910917
quickcheck! {
911918
fn size_unique(it: Iter<i8>) -> bool {
912919
correct_size_hint(it.unique())

tests/test_std.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,23 @@ fn combinations_with_replacement() {
764764
);
765765
}
766766

767+
#[test]
768+
fn powerset() {
769+
it::assert_equal((0..0).powerset(), vec![vec![]]);
770+
it::assert_equal((0..1).powerset(), vec![vec![], vec![0]]);
771+
it::assert_equal((0..2).powerset(), vec![vec![], vec![0], vec![1], vec![0, 1]]);
772+
it::assert_equal((0..3).powerset(), vec![
773+
vec![],
774+
vec![0], vec![1], vec![2],
775+
vec![0, 1], vec![0, 2], vec![1, 2],
776+
vec![0, 1, 2]
777+
]);
778+
779+
assert_eq!((0..4).powerset().count(), 1 << 4);
780+
assert_eq!((0..8).powerset().count(), 1 << 8);
781+
assert_eq!((0..16).powerset().count(), 1 << 16);
782+
}
783+
767784
#[test]
768785
fn diff_mismatch() {
769786
let a = vec![1, 2, 3, 4];

0 commit comments

Comments
 (0)