Skip to content

Commit f49c53b

Browse files
Merge #735
735: `Powerset` count r=phimuemue a=Philippe-Cholet `@phimuemue` After counting combinations last week, I thought a simple PR would be nice to start the week. The first three commits are about `Powerset::count` while the next four commits are about `Powerset::size_hint`. Co-authored-by: Philippe-Cholet <[email protected]>
2 parents f920a9c + 04d7573 commit f49c53b

File tree

4 files changed

+97
-47
lines changed

4 files changed

+97
-47
lines changed

src/combinations.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ impl<I: Iterator> Combinations<I> {
7777
self.pool.prefill(k);
7878
}
7979
}
80+
81+
pub(crate) fn n_and_count(self) -> (usize, usize) {
82+
let Self { indices, pool, first } = self;
83+
let n = pool.count();
84+
(n, remaining_for(n, first, &indices).unwrap())
85+
}
8086
}
8187

8288
impl<I> Iterator for Combinations<I>
@@ -128,10 +134,9 @@ impl<I> Iterator for Combinations<I>
128134
(low, upp)
129135
}
130136

137+
#[inline]
131138
fn count(self) -> usize {
132-
let Self { indices, pool, first } = self;
133-
let n = pool.count();
134-
remaining_for(n, first, &indices).unwrap()
139+
self.n_and_count().1
135140
}
136141
}
137142

src/powerset.rs

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use std::iter::FusedIterator;
33
use std::usize;
44
use alloc::vec::Vec;
55

6-
use super::combinations::{Combinations, combinations};
7-
use super::size_hint;
6+
use super::combinations::{Combinations, checked_binomial, combinations};
7+
use crate::size_hint::{self, SizeHint};
88

99
/// An iterator to iterate through the powerset of the elements from an iterator.
1010
///
@@ -13,22 +13,20 @@ use super::size_hint;
1313
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
1414
pub struct Powerset<I: Iterator> {
1515
combs: Combinations<I>,
16-
// Iterator `position` (equal to count of yielded elements).
17-
pos: usize,
1816
}
1917

2018
impl<I> Clone for Powerset<I>
2119
where I: Clone + Iterator,
2220
I::Item: Clone,
2321
{
24-
clone_fields!(combs, pos);
22+
clone_fields!(combs);
2523
}
2624

2725
impl<I> fmt::Debug for Powerset<I>
2826
where I: Iterator + fmt::Debug,
2927
I::Item: fmt::Debug,
3028
{
31-
debug_fmt_fields!(Powerset, combs, pos);
29+
debug_fmt_fields!(Powerset, combs);
3230
}
3331

3432
/// Create a new `Powerset` from a clonable iterator.
@@ -38,7 +36,6 @@ pub fn powerset<I>(src: I) -> Powerset<I>
3836
{
3937
Powerset {
4038
combs: combinations(src, 0),
41-
pos: 0,
4239
}
4340
}
4441

@@ -51,35 +48,30 @@ impl<I> Iterator for Powerset<I>
5148

5249
fn next(&mut self) -> Option<Self::Item> {
5350
if let Some(elt) = self.combs.next() {
54-
self.pos = self.pos.saturating_add(1);
5551
Some(elt)
5652
} else if self.combs.k() < self.combs.n()
5753
|| self.combs.k() == 0
5854
{
5955
self.combs.reset(self.combs.k() + 1);
60-
self.combs.next().map(|elt| {
61-
self.pos = self.pos.saturating_add(1);
62-
elt
63-
})
56+
self.combs.next()
6457
} else {
6558
None
6659
}
6760
}
6861

69-
fn size_hint(&self) -> (usize, Option<usize>) {
62+
fn size_hint(&self) -> SizeHint {
63+
let k = self.combs.k();
7064
// Total bounds for source iterator.
71-
let src_total = self.combs.src().size_hint();
72-
73-
// Total bounds for self ( length(powerset(set) == 2 ^ length(set) )
74-
let self_total = size_hint::pow_scalar_base(2, src_total);
65+
let (n_min, n_max) = self.combs.src().size_hint();
66+
let low = remaining_for(n_min, k).unwrap_or(usize::MAX);
67+
let upp = n_max.and_then(|n| remaining_for(n, k));
68+
size_hint::add(self.combs.size_hint(), (low, upp))
69+
}
7570

76-
if self.pos < usize::MAX {
77-
// Subtract count of elements already yielded from total.
78-
size_hint::sub_scalar(self_total, self.pos)
79-
} else {
80-
// Fallback: self.pos is saturated and no longer reliable.
81-
(0, self_total.1)
82-
}
71+
fn count(self) -> usize {
72+
let k = self.combs.k();
73+
let (n, combs_count) = self.combs.n_and_count();
74+
combs_count + remaining_for(n, k).unwrap()
8375
}
8476
}
8577

@@ -88,3 +80,9 @@ impl<I> FusedIterator for Powerset<I>
8880
I: Iterator,
8981
I::Item: Clone,
9082
{}
83+
84+
fn remaining_for(n: usize, k: usize) -> Option<usize> {
85+
(k + 1..=n).fold(Some(0), |sum, i| {
86+
sum.and_then(|s| s.checked_add(checked_binomial(n, i)?))
87+
})
88+
}

src/size_hint.rs

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
44
use std::usize;
55
use std::cmp;
6-
use std::u32;
76

87
/// `SizeHint` is the return type of `Iterator::size_hint()`.
98
pub type SizeHint = (usize, Option<usize>);
@@ -75,20 +74,6 @@ pub fn mul_scalar(sh: SizeHint, x: usize) -> SizeHint {
7574
(low, hi)
7675
}
7776

78-
/// Raise `base` correctly by a `SizeHint` exponent.
79-
#[inline]
80-
pub fn pow_scalar_base(base: usize, exp: SizeHint) -> SizeHint {
81-
let exp_low = cmp::min(exp.0, u32::MAX as usize) as u32;
82-
let low = base.saturating_pow(exp_low);
83-
84-
let hi = exp.1.and_then(|exp| {
85-
let exp_hi = cmp::min(exp, u32::MAX as usize) as u32;
86-
base.checked_pow(exp_hi)
87-
});
88-
89-
(low, hi)
90-
}
91-
9277
/// Return the maximum
9378
#[inline]
9479
pub fn max(a: SizeHint, b: SizeHint) -> SizeHint {

tests/test_std.rs

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -909,15 +909,19 @@ fn combinations_zero() {
909909
it::assert_equal((0..0).combinations(0), vec![vec![]]);
910910
}
911911

912+
fn binomial(n: usize, k: usize) -> usize {
913+
if k > n {
914+
0
915+
} else {
916+
(n - k + 1..=n).product::<usize>() / (1..=k).product::<usize>()
917+
}
918+
}
919+
912920
#[test]
913921
fn combinations_range_count() {
914922
for n in 0..=10 {
915923
for k in 0..=10 {
916-
let len = if k<=n {
917-
(n - k + 1..=n).product::<usize>() / (1..=k).product::<usize>()
918-
} else {
919-
0
920-
};
924+
let len = binomial(n, k);
921925
let mut it = (0..n).combinations(k);
922926
assert_eq!(len, it.clone().count());
923927
assert_eq!(len, it.size_hint().0);
@@ -935,6 +939,47 @@ fn combinations_range_count() {
935939
}
936940
}
937941

942+
#[test]
943+
fn combinations_inexact_size_hints() {
944+
for k in 0..=10 {
945+
let mut numbers = (0..18).filter(|i| i % 2 == 0); // 9 elements
946+
let mut it = numbers.clone().combinations(k);
947+
let real_n = numbers.clone().count();
948+
let len = binomial(real_n, k);
949+
assert_eq!(len, it.clone().count());
950+
951+
let mut nb_loaded = numbers.by_ref().take(k).count(); // because of `LazyBuffer::prefill(k)`
952+
let sh = numbers.size_hint();
953+
assert_eq!(binomial(sh.0 + nb_loaded, k), it.size_hint().0);
954+
assert_eq!(sh.1.map(|n| binomial(n + nb_loaded, k)), it.size_hint().1);
955+
956+
for next_count in 1..=len {
957+
let elem = it.next();
958+
assert!(elem.is_some());
959+
assert_eq!(len - next_count, it.clone().count());
960+
// It does not load anything more the very first time (it's prefilled).
961+
if next_count > 1 {
962+
// Then it loads one item each time until exhausted.
963+
let nb = numbers.next();
964+
if nb.is_some() {
965+
nb_loaded += 1;
966+
}
967+
}
968+
let sh = numbers.size_hint();
969+
if next_count > real_n - k + 1 {
970+
assert_eq!(0, sh.0);
971+
assert_eq!(Some(0), sh.1);
972+
assert_eq!(real_n, nb_loaded);
973+
// Once it's fully loaded, size hints of `it` are exacts.
974+
}
975+
assert_eq!(binomial(sh.0 + nb_loaded, k) - next_count, it.size_hint().0);
976+
assert_eq!(sh.1.map(|n| binomial(n + nb_loaded, k) - next_count), it.size_hint().1);
977+
}
978+
let should_be_none = it.next();
979+
assert!(should_be_none.is_none());
980+
}
981+
}
982+
938983
#[test]
939984
fn permutations_zero() {
940985
it::assert_equal((1..3).permutations(0), vec![vec![]]);
@@ -989,6 +1034,23 @@ fn powerset() {
9891034
assert_eq!((0..4).powerset().count(), 1 << 4);
9901035
assert_eq!((0..8).powerset().count(), 1 << 8);
9911036
assert_eq!((0..16).powerset().count(), 1 << 16);
1037+
1038+
for n in 0..=10 {
1039+
let mut it = (0..n).powerset();
1040+
let len = 2_usize.pow(n);
1041+
assert_eq!(len, it.clone().count());
1042+
assert_eq!(len, it.size_hint().0);
1043+
assert_eq!(Some(len), it.size_hint().1);
1044+
for count in (0..len).rev() {
1045+
let elem = it.next();
1046+
assert!(elem.is_some());
1047+
assert_eq!(count, it.clone().count());
1048+
assert_eq!(count, it.size_hint().0);
1049+
assert_eq!(Some(count), it.size_hint().1);
1050+
}
1051+
let should_be_none = it.next();
1052+
assert!(should_be_none.is_none());
1053+
}
9921054
}
9931055

9941056
#[test]

0 commit comments

Comments
 (0)