Skip to content

Commit 31d598f

Browse files
Merge #739
739: Complete `Permutations::size_hint` r=jswrenn a=Philippe-Cholet `@phimuemue` This series of size hint/count improvements ends where it started: with a TODO I wanted to fix. The end user will mostly enjoy that `(0..n).permutations(k).map(...).collect_vec()` will now allocate to the resulting vector in _one go_ (`PermutationState::StartUnknownLen` case). In the first commit, you probably will want me to unwrap but `panic!(message)` is more similar to `expect` than `unwrap`. Which is why I previously used `expect` too. The 2nd commit (about `enough_vals`) is off topic but I really don't see why it would not be a (minor) improvement. I have a test `permutations_inexact_size_hints` ready if you want (similar to `combinations_inexact_size_hints`). Co-authored-by: Philippe-Cholet <[email protected]>
2 parents 557cb89 + 65d0c60 commit 31d598f

File tree

3 files changed

+65
-45
lines changed

3 files changed

+65
-45
lines changed

src/permutations.rs

Lines changed: 27 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::fmt;
33
use std::iter::once;
44

55
use super::lazy_buffer::LazyBuffer;
6+
use crate::size_hint::{self, SizeHint};
67

78
/// An iterator adaptor that iterates through all the `k`-permutations of the
89
/// elements from an iterator.
@@ -47,11 +48,6 @@ enum CompleteState {
4748
}
4849
}
4950

50-
enum CompleteStateRemaining {
51-
Known(usize),
52-
Overflow,
53-
}
54-
5551
impl<I> fmt::Debug for Permutations<I>
5652
where I: Iterator + fmt::Debug,
5753
I::Item: fmt::Debug,
@@ -72,14 +68,8 @@ pub fn permutations<I: Iterator>(iter: I, k: usize) -> Permutations<I> {
7268
};
7369
}
7470

75-
let mut enough_vals = true;
76-
77-
while vals.len() < k {
78-
if !vals.get_next() {
79-
enough_vals = false;
80-
break;
81-
}
82-
}
71+
vals.prefill(k);
72+
let enough_vals = vals.len() == k;
8373

8474
let state = if enough_vals {
8575
PermutationState::StartUnknownLen { k }
@@ -123,12 +113,7 @@ where
123113

124114
fn count(self) -> usize {
125115
fn from_complete(complete_state: CompleteState) -> usize {
126-
match complete_state.remaining() {
127-
CompleteStateRemaining::Known(count) => count,
128-
CompleteStateRemaining::Overflow => {
129-
panic!("Iterator count greater than usize::MAX");
130-
}
131-
}
116+
complete_state.remaining().expect("Iterator count greater than usize::MAX")
132117
}
133118

134119
let Permutations { vals, state } = self;
@@ -151,13 +136,23 @@ where
151136
}
152137
}
153138

154-
fn size_hint(&self) -> (usize, Option<usize>) {
139+
fn size_hint(&self) -> SizeHint {
140+
let at_start = |k| {
141+
// At the beginning, there are `n!/(n-k)!` items to come (see `remaining`) but `n` might be unknown.
142+
let (mut low, mut upp) = self.vals.size_hint();
143+
low = CompleteState::Start { n: low, k }.remaining().unwrap_or(usize::MAX);
144+
upp = upp.and_then(|n| CompleteState::Start { n, k }.remaining());
145+
(low, upp)
146+
};
155147
match self.state {
156-
PermutationState::StartUnknownLen { .. } |
157-
PermutationState::OngoingUnknownLen { .. } => (0, None), // TODO can we improve this lower bound?
148+
PermutationState::StartUnknownLen { k } => at_start(k),
149+
PermutationState::OngoingUnknownLen { k, min_n } => {
150+
// Same as `StartUnknownLen` minus the previously generated items.
151+
size_hint::sub_scalar(at_start(k), min_n - k + 1)
152+
}
158153
PermutationState::Complete(ref state) => match state.remaining() {
159-
CompleteStateRemaining::Known(count) => (count, Some(count)),
160-
CompleteStateRemaining::Overflow => (::std::usize::MAX, None)
154+
Some(count) => (count, Some(count)),
155+
None => (::std::usize::MAX, None)
161156
}
162157
PermutationState::Empty => (0, Some(0))
163158
}
@@ -238,39 +233,27 @@ impl CompleteState {
238233
}
239234
}
240235

241-
fn remaining(&self) -> CompleteStateRemaining {
242-
use self::CompleteStateRemaining::{Known, Overflow};
243-
236+
/// Returns the count of remaining permutations, or None if it would overflow.
237+
fn remaining(&self) -> Option<usize> {
244238
match *self {
245239
CompleteState::Start { n, k } => {
246240
if n < k {
247-
return Known(0);
241+
return Some(0);
248242
}
249-
250-
let count: Option<usize> = (n - k + 1..n + 1).fold(Some(1), |acc, i| {
243+
(n - k + 1..=n).fold(Some(1), |acc, i| {
251244
acc.and_then(|acc| acc.checked_mul(i))
252-
});
253-
254-
match count {
255-
Some(count) => Known(count),
256-
None => Overflow
257-
}
245+
})
258246
}
259247
CompleteState::Ongoing { ref indices, ref cycles } => {
260248
let mut count: usize = 0;
261249

262250
for (i, &c) in cycles.iter().enumerate() {
263251
let radix = indices.len() - i;
264-
let next_count = count.checked_mul(radix)
265-
.and_then(|count| count.checked_add(c));
266-
267-
count = match next_count {
268-
Some(count) => count,
269-
None => { return Overflow; }
270-
};
252+
count = count.checked_mul(radix)
253+
.and_then(|count| count.checked_add(c))?;
271254
}
272255

273-
Known(count)
256+
Some(count)
274257
}
275258
}
276259
}

src/size_hint.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ pub fn add_scalar(sh: SizeHint, x: usize) -> SizeHint {
3030

3131
/// Subtract `x` correctly from a `SizeHint`.
3232
#[inline]
33-
#[allow(dead_code)]
3433
pub fn sub_scalar(sh: SizeHint, x: usize) -> SizeHint {
3534
let (mut low, mut hi) = sh;
3635
low = low.saturating_sub(x);

tests/test_std.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -986,6 +986,44 @@ fn permutations_zero() {
986986
it::assert_equal((0..0).permutations(0), vec![vec![]]);
987987
}
988988

989+
#[test]
990+
fn permutations_range_count() {
991+
for n in 0..=7 {
992+
for k in 0..=7 {
993+
let len = if k <= n {
994+
(n - k + 1..=n).product()
995+
} else {
996+
0
997+
};
998+
let mut it = (0..n).permutations(k);
999+
assert_eq!(len, it.clone().count());
1000+
assert_eq!(len, it.size_hint().0);
1001+
assert_eq!(Some(len), it.size_hint().1);
1002+
for count in (0..len).rev() {
1003+
let elem = it.next();
1004+
assert!(elem.is_some());
1005+
assert_eq!(count, it.clone().count());
1006+
assert_eq!(count, it.size_hint().0);
1007+
assert_eq!(Some(count), it.size_hint().1);
1008+
}
1009+
let should_be_none = it.next();
1010+
assert!(should_be_none.is_none());
1011+
}
1012+
}
1013+
}
1014+
1015+
#[test]
1016+
fn permutations_overflowed_size_hints() {
1017+
let mut it = std::iter::repeat(()).permutations(2);
1018+
assert_eq!(it.size_hint().0, usize::MAX);
1019+
assert_eq!(it.size_hint().1, None);
1020+
for nb_generated in 1..=1000 {
1021+
it.next();
1022+
assert!(it.size_hint().0 >= usize::MAX - nb_generated);
1023+
assert_eq!(it.size_hint().1, None);
1024+
}
1025+
}
1026+
9891027
#[test]
9901028
fn combinations_with_replacement() {
9911029
// Pool smaller than n

0 commit comments

Comments
 (0)