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

Powerset iterator adaptor #335

Merged
merged 4 commits into from Dec 10, 2020
Merged
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
8 changes: 8 additions & 0 deletions Cargo.toml
Expand Up @@ -67,3 +67,11 @@ harness = false
[[bench]]
name = "bench1"
harness = false

[[bench]]
name = "combinations"
harness = false

[[bench]]
name = "powerset"
harness = false
125 changes: 125 additions & 0 deletions benches/combinations.rs
@@ -0,0 +1,125 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use itertools::Itertools;

// approximate 100_000 iterations for each combination
const N1: usize = 100_000;
const N2: usize = 448;
const N3: usize = 86;
const N4: usize = 41;
const N14: usize = 21;

fn comb_for1(c: &mut Criterion) {
c.bench_function("comb for1", move |b| {
b.iter(|| {
for i in 0..N1 {
black_box(vec![i]);
}
})
});
}

fn comb_for2(c: &mut Criterion) {
c.bench_function("comb for2", move |b| {
b.iter(|| {
for i in 0..N2 {
for j in (i + 1)..N2 {
black_box(vec![i, j]);
}
}
})
});
}

fn comb_for3(c: &mut Criterion) {
c.bench_function("comb for3", move |b| {
b.iter(|| {
for i in 0..N3 {
for j in (i + 1)..N3 {
for k in (j + 1)..N3 {
black_box(vec![i, j, k]);
}
}
}
})
});
}

fn comb_for4(c: &mut Criterion) {
c.bench_function("comb for4", move |b| {
b.iter(|| {
for i in 0..N4 {
for j in (i + 1)..N4 {
for k in (j + 1)..N4 {
for l in (k + 1)..N4 {
black_box(vec![i, j, k, l]);
}
}
}
}
})
});
}

fn comb_c1(c: &mut Criterion) {
c.bench_function("comb c1", move |b| {
b.iter(|| {
for combo in (0..N1).combinations(1) {
black_box(combo);
}
})
});
}

fn comb_c2(c: &mut Criterion) {
c.bench_function("comb c2", move |b| {
b.iter(|| {
for combo in (0..N2).combinations(2) {
black_box(combo);
}
})
});
}

fn comb_c3(c: &mut Criterion) {
c.bench_function("comb c3", move |b| {
b.iter(|| {
for combo in (0..N3).combinations(3) {
black_box(combo);
}
})
});
}

fn comb_c4(c: &mut Criterion) {
c.bench_function("comb c4", move |b| {
b.iter(|| {
for combo in (0..N4).combinations(4) {
black_box(combo);
}
})
});
}

fn comb_c14(c: &mut Criterion) {
c.bench_function("comb c14", move |b| {
b.iter(|| {
for combo in (0..N14).combinations(14) {
black_box(combo);
}
})
});
}

criterion_group!(
benches,
comb_for1,
comb_for2,
comb_for3,
comb_for4,
comb_c1,
comb_c2,
comb_c3,
comb_c4,
comb_c14,
);
criterion_main!(benches);
44 changes: 44 additions & 0 deletions benches/powerset.rs
@@ -0,0 +1,44 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use itertools::Itertools;

// Keep aggregate generated elements the same, regardless of powerset length.
const TOTAL_ELEMENTS: usize = 1 << 12;
const fn calc_iters(n: usize) -> usize {
TOTAL_ELEMENTS / (1 << n)
}

fn powerset_n(c: &mut Criterion, n: usize) {
let id = format!("powerset {}", n);
c.bench_function(id.as_str(), move |b| {
b.iter(|| {
for _ in 0..calc_iters(n) {
for elt in (0..n).powerset() {
black_box(elt);
}
}
})
});
}

fn powerset_0(c: &mut Criterion) { powerset_n(c, 0); }

fn powerset_1(c: &mut Criterion) { powerset_n(c, 1); }

fn powerset_2(c: &mut Criterion) { powerset_n(c, 2); }

fn powerset_4(c: &mut Criterion) { powerset_n(c, 4); }

fn powerset_8(c: &mut Criterion) { powerset_n(c, 8); }

fn powerset_12(c: &mut Criterion) { powerset_n(c, 12); }

criterion_group!(
benches,
powerset_0,
powerset_1,
powerset_2,
powerset_4,
powerset_8,
powerset_12,
);
criterion_main!(benches);
48 changes: 24 additions & 24 deletions benches/tuple_combinations.rs
Expand Up @@ -7,8 +7,8 @@ const N2: usize = 448;
const N3: usize = 86;
const N4: usize = 41;

fn comb_for1(c: &mut Criterion) {
c.bench_function("comb for1", move |b| {
fn tuple_comb_for1(c: &mut Criterion) {
c.bench_function("tuple comb for1", move |b| {
b.iter(|| {
for i in 0..N1 {
black_box(i);
Expand All @@ -17,8 +17,8 @@ fn comb_for1(c: &mut Criterion) {
});
}

fn comb_for2(c: &mut Criterion) {
c.bench_function("comb for2", move |b| {
fn tuple_comb_for2(c: &mut Criterion) {
c.bench_function("tuple comb for2", move |b| {
b.iter(|| {
for i in 0..N2 {
for j in (i + 1)..N2 {
Expand All @@ -29,8 +29,8 @@ fn comb_for2(c: &mut Criterion) {
});
}

fn comb_for3(c: &mut Criterion) {
c.bench_function("comb for3", move |b| {
fn tuple_comb_for3(c: &mut Criterion) {
c.bench_function("tuple comb for3", move |b| {
b.iter(|| {
for i in 0..N3 {
for j in (i + 1)..N3 {
Expand All @@ -43,8 +43,8 @@ fn comb_for3(c: &mut Criterion) {
});
}

fn comb_for4(c: &mut Criterion) {
c.bench_function("comb for4", move |b| {
fn tuple_comb_for4(c: &mut Criterion) {
c.bench_function("tuple comb for4", move |b| {
b.iter(|| {
for i in 0..N4 {
for j in (i + 1)..N4 {
Expand All @@ -59,8 +59,8 @@ fn comb_for4(c: &mut Criterion) {
});
}

fn comb_c1(c: &mut Criterion) {
c.bench_function("comb c1", move |b| {
fn tuple_comb_c1(c: &mut Criterion) {
c.bench_function("tuple comb c1", move |b| {
b.iter(|| {
for (i,) in (0..N1).tuple_combinations() {
black_box(i);
Expand All @@ -69,8 +69,8 @@ fn comb_c1(c: &mut Criterion) {
});
}

fn comb_c2(c: &mut Criterion) {
c.bench_function("comb c2", move |b| {
fn tuple_comb_c2(c: &mut Criterion) {
c.bench_function("tuple comb c2", move |b| {
b.iter(|| {
for (i, j) in (0..N2).tuple_combinations() {
black_box(i + j);
Expand All @@ -79,8 +79,8 @@ fn comb_c2(c: &mut Criterion) {
});
}

fn comb_c3(c: &mut Criterion) {
c.bench_function("comb c3", move |b| {
fn tuple_comb_c3(c: &mut Criterion) {
c.bench_function("tuple comb c3", move |b| {
b.iter(|| {
for (i, j, k) in (0..N3).tuple_combinations() {
black_box(i + j + k);
Expand All @@ -89,8 +89,8 @@ fn comb_c3(c: &mut Criterion) {
});
}

fn comb_c4(c: &mut Criterion) {
c.bench_function("comb c4", move |b| {
fn tuple_comb_c4(c: &mut Criterion) {
c.bench_function("tuple comb c4", move |b| {
b.iter(|| {
for (i, j, k, l) in (0..N4).tuple_combinations() {
black_box(i + j + k + l);
Expand All @@ -101,13 +101,13 @@ fn comb_c4(c: &mut Criterion) {

criterion_group!(
benches,
comb_for1,
comb_for2,
comb_for3,
comb_for4,
comb_c1,
comb_c2,
comb_c3,
comb_c4,
tuple_comb_for1,
tuple_comb_for2,
tuple_comb_for3,
tuple_comb_for4,
tuple_comb_c1,
tuple_comb_c2,
tuple_comb_c3,
tuple_comb_c4,
);
criterion_main!(benches);
50 changes: 42 additions & 8 deletions src/combinations.rs
Expand Up @@ -31,13 +31,8 @@ impl<I> fmt::Debug for Combinations<I>
pub fn combinations<I>(iter: I, k: usize) -> Combinations<I>
where I: Iterator
{
let mut pool: LazyBuffer<I> = LazyBuffer::new(iter);

for _ in 0..k {
if !pool.get_next() {
break;
}
}
let mut pool = LazyBuffer::new(iter);
pool.prefill(k);

Combinations {
indices: (0..k).collect(),
Expand All @@ -46,14 +41,53 @@ pub fn combinations<I>(iter: I, k: usize) -> Combinations<I>
}
}

willcrozi marked this conversation as resolved.
Show resolved Hide resolved
impl<I: Iterator> Combinations<I> {
/// Returns the length of a combination produced by this iterator.
#[inline]
pub fn k(&self) -> usize { self.indices.len() }

/// Returns the (current) length of the pool from which combination elements are
/// selected. This value can change between invocations of [`next`].
///
/// [`next`]: #method.next
#[inline]
pub fn n(&self) -> usize { self.pool.len() }

/// Returns a reference to the source iterator.
#[inline]
pub(crate) fn src(&self) -> &I { &self.pool.it }

/// Resets this `Combinations` back to an initial state for combinations of length
/// `k` over the same pool data source. If `k` is larger than the current length
/// of the data pool an attempt is made to prefill the pool so that it holds `k`
/// elements.
pub(crate) fn reset(&mut self, k: usize) {
self.first = true;

if k < self.indices.len() {
self.indices.truncate(k);
for i in 0..k {
self.indices[i] = i;
}

} else {
for i in 0..self.indices.len() {
self.indices[i] = i;
}
self.indices.extend(self.indices.len()..k);
self.pool.prefill(k);
}
}
}

impl<I> Iterator for Combinations<I>
where I: Iterator,
I::Item: Clone
{
type Item = Vec<I::Item>;
fn next(&mut self) -> Option<Self::Item> {
if self.first {
if self.pool.is_done() {
if self.k() > self.n() {
willcrozi marked this conversation as resolved.
Show resolved Hide resolved
return None;
}
self.first = false;
Expand Down
15 changes: 11 additions & 4 deletions src/lazy_buffer.rs
Expand Up @@ -24,10 +24,6 @@ where
self.buffer.len()
}

pub fn is_done(&self) -> bool {
self.done
}

pub fn get_next(&mut self) -> bool {
if self.done {
return false;
Expand All @@ -44,6 +40,17 @@ where
}
}
}

pub fn prefill(&mut self, len: usize) {
let buffer_len = self.buffer.len();

if !self.done && len > buffer_len {
let delta = len - buffer_len;

self.buffer.extend(self.it.by_ref().take(delta));
self.done = self.buffer.len() < len;
}
}
}

impl<I, J> Index<J> for LazyBuffer<I>
Expand Down