Skip to content

Commit eb8f01e

Browse files
bors[bot]tobz1000
andauthored
Merge #292
292: Permutations r=jswrenn a=tobz1000 Fixes #285. This implementation is based on the Python implementation specified in the issue. There are quite a few points which should be considered. ## Method name, _k_-less form The adaptor function is called `permutations`, for user familiarity. However, since the size of the output can be specified, a more mathematically accurate name would probably be `k_permutations`. Perhaps two adaptor functions would be better: `.k_permutations(k: usize)` and `.permutations()` (which just sets `k = vals.len()`)? ## Item value ordering/distinctness Input items are not inspected in any way; they are treated purely by their initial index. This means that: 1. Permutations are yielded in lexicographical order of the index, not by any `PartialOrd` implementation of the items. 2. Identical items will not be detected, and will result in some identical permutations. __1__ can be achieved by the user by collecting-and-sorting their input. __2__ is a little trickier, but can be achieved by filtering the output. However, I think there is a more efficient algorithm to avoid duplicated. Maybe we should provide this option? ## Permutations from source buffer/indices In addition to the iterator adaptor, I've added `Permutations::from_vals`, to create directly from a `Vec` or a slice. This saves some cloning compared to using `source.[into_]iter().permutations(k)`. There is also `Permutations::new(n, k)`, which is functionally equivalent to `(0..n).permutations(k)`, but a little faster (about 0.6x the run time). But perhaps you would consider these unnecessary additions to the API? ## `PermutationSource` trait These different implementations (from `Vec`/slice/just indices) are achieved with the trait `PermutationSource`. It's visible to the user to implement for other structures if they wish, but this is probably of limited value. There's not much harm in allowing the user to access it, but again, maybe it's just API bloat. (or a future breaking change when it's removed/changed...) On the other hand, perhaps there are other places in the library which could benefit from taking a source generically? Any adaptor where input elements are used more than once will need to store them, and it might be more efficient to allow users to supply the memory directly. This could be done in another PR. For completeness, I also made full implementations of the three variations, without the trait, for benchmarking. The pure-indices implementation is about 10% slower using the trait, but `Vec`s and slices are unaffected (or even a little faster...). ## `Combinations` changes I've made small changes to the `Combinations` documentation to match `Permutations` - most significantly, replacing the letter `n` with `k`. I've also changed all uses of the variable `n` to `k` in the implementation... but maybe this is considered commit noise :) There are some other potential changes which would bring `Combinations` and `Permutations` more in line with one another. As mentioned above, `Combinations` might benefit from using a `_____Source` trait, to allow direct memory buffers. My `Permutations` implementations doesn't make use of `LazyBuffer`. Perhaps the buffer is useful for iterators which never complete, and have a very small `k` value. Has any benchmarking been done? Either way, it makes sense for both adaptors to either use or not use `LazyBuffer`. Maybe it could be integrated into the `_______Source` trait if it's useful? --- Let me know what you think when you have the chance. (Sorry for submitting two reasonably big PRs at once. I hope you get a chance to go through it all eventually!) Co-authored-by: Toby Dimmick <[email protected]>
2 parents a6e9713 + 88834c9 commit eb8f01e

9 files changed

+567
-47
lines changed

benches/bench1.rs

+43-1
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,11 @@ use test::{black_box};
77
use itertools::Itertools;
88

99
use itertools::free::cloned;
10+
use itertools::Permutations;
1011

1112
use std::iter::repeat;
1213
use std::cmp;
13-
use std::ops::Add;
14+
use std::ops::{Add, Range};
1415

1516
mod extra;
1617

@@ -762,3 +763,44 @@ fn all_equal_default(b: &mut test::Bencher) {
762763

763764
b.iter(|| xs.iter().dedup().nth(1).is_none())
764765
}
766+
767+
const PERM_COUNT: usize = 6;
768+
769+
#[bench]
770+
fn permutations_iter(b: &mut test::Bencher) {
771+
struct NewIterator(Range<usize>);
772+
773+
impl Iterator for NewIterator {
774+
type Item = usize;
775+
776+
fn next(&mut self) -> Option<Self::Item> {
777+
self.0.next()
778+
}
779+
}
780+
781+
b.iter(|| {
782+
for _ in NewIterator(0..PERM_COUNT).permutations(PERM_COUNT) {
783+
784+
}
785+
})
786+
}
787+
788+
#[bench]
789+
fn permutations_range(b: &mut test::Bencher) {
790+
b.iter(|| {
791+
for _ in (0..PERM_COUNT).permutations(PERM_COUNT) {
792+
793+
}
794+
})
795+
}
796+
797+
#[bench]
798+
fn permutations_slice(b: &mut test::Bencher) {
799+
let v = (0..PERM_COUNT).collect_vec();
800+
801+
b.iter(|| {
802+
for _ in v.as_slice().iter().permutations(PERM_COUNT) {
803+
804+
}
805+
})
806+
}

src/combinations.rs

+14-14
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ use std::fmt;
22

33
use super::lazy_buffer::LazyBuffer;
44

5-
/// An iterator to iterate through all the `n`-length combinations in an iterator.
5+
/// An iterator to iterate through all the `k`-length combinations in an iterator.
66
///
77
/// See [`.combinations()`](../trait.Itertools.html#method.combinations) for more information.
88
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
99
pub struct Combinations<I: Iterator> {
10-
n: usize,
10+
k: usize,
1111
indices: Vec<usize>,
1212
pool: LazyBuffer<I>,
1313
first: bool,
@@ -17,27 +17,27 @@ impl<I> fmt::Debug for Combinations<I>
1717
where I: Iterator + fmt::Debug,
1818
I::Item: fmt::Debug,
1919
{
20-
debug_fmt_fields!(Combinations, n, indices, pool, first);
20+
debug_fmt_fields!(Combinations, k, indices, pool, first);
2121
}
2222

2323
/// Create a new `Combinations` from a clonable iterator.
24-
pub fn combinations<I>(iter: I, n: usize) -> Combinations<I>
24+
pub fn combinations<I>(iter: I, k: usize) -> Combinations<I>
2525
where I: Iterator
2626
{
27-
let mut indices: Vec<usize> = Vec::with_capacity(n);
28-
for i in 0..n {
27+
let mut indices: Vec<usize> = Vec::with_capacity(k);
28+
for i in 0..k {
2929
indices.push(i);
3030
}
3131
let mut pool: LazyBuffer<I> = LazyBuffer::new(iter);
3232

33-
for _ in 0..n {
33+
for _ in 0..k {
3434
if !pool.get_next() {
3535
break;
3636
}
3737
}
3838

3939
Combinations {
40-
n: n,
40+
k: k,
4141
indices: indices,
4242
pool: pool,
4343
first: true,
@@ -52,18 +52,18 @@ impl<I> Iterator for Combinations<I>
5252
fn next(&mut self) -> Option<Self::Item> {
5353
let mut pool_len = self.pool.len();
5454
if self.pool.is_done() {
55-
if pool_len == 0 || self.n > pool_len {
55+
if pool_len == 0 || self.k > pool_len {
5656
return None;
5757
}
5858
}
5959

6060
if self.first {
6161
self.first = false;
62-
} else if self.n == 0 {
62+
} else if self.k == 0 {
6363
return None;
6464
} else {
6565
// Scan from the end, looking for an index to increment
66-
let mut i: usize = self.n - 1;
66+
let mut i: usize = self.k - 1;
6767

6868
// Check if we need to consume more from the iterator
6969
if self.indices[i] == pool_len - 1 && !self.pool.is_done() {
@@ -72,7 +72,7 @@ impl<I> Iterator for Combinations<I>
7272
}
7373
}
7474

75-
while self.indices[i] == i + pool_len - self.n {
75+
while self.indices[i] == i + pool_len - self.k {
7676
if i > 0 {
7777
i -= 1;
7878
} else {
@@ -84,14 +84,14 @@ impl<I> Iterator for Combinations<I>
8484
// Increment index, and reset the ones to its right
8585
self.indices[i] += 1;
8686
let mut j = i + 1;
87-
while j < self.n {
87+
while j < self.k {
8888
self.indices[j] = self.indices[j - 1] + 1;
8989
j += 1;
9090
}
9191
}
9292

9393
// Create result vector based on the indices
94-
let mut result = Vec::with_capacity(self.n);
94+
let mut result = Vec::with_capacity(self.k);
9595
for i in self.indices.iter() {
9696
result.push(self.pool[*i].clone());
9797
}

src/combinations_with_replacement.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ where
1111
I: Iterator,
1212
I::Item: Clone,
1313
{
14-
n: usize,
14+
k: usize,
1515
indices: Vec<usize>,
1616
// The current known max index value. This increases as pool grows.
1717
max_index: usize,
@@ -24,7 +24,7 @@ where
2424
I: Iterator + fmt::Debug,
2525
I::Item: fmt::Debug + Clone,
2626
{
27-
debug_fmt_fields!(Combinations, n, indices, max_index, pool, first);
27+
debug_fmt_fields!(Combinations, k, indices, max_index, pool, first);
2828
}
2929

3030
impl<I> CombinationsWithReplacement<I>
@@ -39,16 +39,16 @@ where
3939
}
4040

4141
/// Create a new `CombinationsWithReplacement` from a clonable iterator.
42-
pub fn combinations_with_replacement<I>(iter: I, n: usize) -> CombinationsWithReplacement<I>
42+
pub fn combinations_with_replacement<I>(iter: I, k: usize) -> CombinationsWithReplacement<I>
4343
where
4444
I: Iterator,
4545
I::Item: Clone,
4646
{
47-
let indices: Vec<usize> = vec![0; n];
47+
let indices: Vec<usize> = vec![0; k];
4848
let pool: LazyBuffer<I> = LazyBuffer::new(iter);
4949

5050
CombinationsWithReplacement {
51-
n,
51+
k,
5252
indices,
5353
max_index: 0,
5454
pool: pool,
@@ -66,7 +66,7 @@ where
6666
// If this is the first iteration, return early
6767
if self.first {
6868
// In empty edge cases, stop iterating immediately
69-
return if self.n == 0 || self.pool.is_done() {
69+
return if self.k == 0 || self.pool.is_done() {
7070
None
7171
// Otherwise, yield the initial state
7272
} else {

src/lazy_buffer.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::ops::Index;
22

33
#[derive(Debug, Clone)]
44
pub struct LazyBuffer<I: Iterator> {
5-
it: I,
5+
pub it: I,
66
done: bool,
77
buffer: Vec<I::Item>,
88
}
@@ -54,14 +54,15 @@ where
5454
}
5555
}
5656

57-
impl<I> Index<usize> for LazyBuffer<I>
57+
impl<I, J> Index<J> for LazyBuffer<I>
5858
where
5959
I: Iterator,
6060
I::Item: Sized,
61+
Vec<I::Item>: Index<J>
6162
{
62-
type Output = I::Item;
63+
type Output = <Vec<I::Item> as Index<J>>::Output;
6364

64-
fn index<'b>(&'b self, _index: usize) -> &'b I::Item {
65+
fn index(&self, _index: J) -> &Self::Output {
6566
self.buffer.index(_index)
6667
}
6768
}

src/lib.rs

+57-9
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ pub mod structs {
117117
pub use multipeek_impl::MultiPeek;
118118
pub use pad_tail::PadUsing;
119119
pub use peeking_take_while::PeekingTakeWhile;
120+
#[cfg(feature = "use_std")]
121+
pub use permutations::Permutations;
120122
pub use process_results_impl::ProcessResults;
121123
#[cfg(feature = "use_std")]
122124
pub use put_back_n_impl::PutBackN;
@@ -182,6 +184,8 @@ mod minmax;
182184
mod multipeek_impl;
183185
mod pad_tail;
184186
mod peeking_take_while;
187+
#[cfg(feature = "use_std")]
188+
mod permutations;
185189
mod process_results_impl;
186190
#[cfg(feature = "use_std")]
187191
mod put_back_n_impl;
@@ -1135,7 +1139,7 @@ pub trait Itertools : Iterator {
11351139
adaptors::tuple_combinations(self)
11361140
}
11371141

1138-
/// Return an iterator adaptor that iterates over the `n`-length combinations of
1142+
/// Return an iterator adaptor that iterates over the `k`-length combinations of
11391143
/// the elements from an iterator.
11401144
///
11411145
/// Iterator element type is `Vec<Self::Item>`. The iterator produces a new Vec per iteration,
@@ -1150,7 +1154,7 @@ pub trait Itertools : Iterator {
11501154
/// vec![1, 2, 4],
11511155
/// vec![1, 3, 4],
11521156
/// vec![2, 3, 4],
1153-
/// ]);
1157+
/// ]);
11541158
/// ```
11551159
///
11561160
/// Note: Combinations does not take into account the equality of the iterated values.
@@ -1164,16 +1168,15 @@ pub trait Itertools : Iterator {
11641168
/// vec![2, 2],
11651169
/// ]);
11661170
/// ```
1167-
///
11681171
#[cfg(feature = "use_std")]
1169-
fn combinations(self, n: usize) -> Combinations<Self>
1172+
fn combinations(self, k: usize) -> Combinations<Self>
11701173
where Self: Sized,
11711174
Self::Item: Clone
11721175
{
1173-
combinations::combinations(self, n)
1176+
combinations::combinations(self, k)
11741177
}
11751178

1176-
/// Return an iterator that iterates over the `n`-length combinations of
1179+
/// Return an iterator that iterates over the `k`-length combinations of
11771180
/// the elements from an iterator, with replacement.
11781181
///
11791182
/// Iterator element type is `Vec<Self::Item>`. The iterator produces a new Vec per iteration,
@@ -1190,15 +1193,60 @@ pub trait Itertools : Iterator {
11901193
/// vec![2, 2],
11911194
/// vec![2, 3],
11921195
/// vec![3, 3],
1193-
/// ]);
1196+
/// ]);
11941197
/// ```
11951198
#[cfg(feature = "use_std")]
1196-
fn combinations_with_replacement(self, n: usize) -> CombinationsWithReplacement<Self>
1199+
fn combinations_with_replacement(self, k: usize) -> CombinationsWithReplacement<Self>
11971200
where
11981201
Self: Sized,
11991202
Self::Item: Clone,
12001203
{
1201-
combinations_with_replacement::combinations_with_replacement(self, n)
1204+
combinations_with_replacement::combinations_with_replacement(self, k)
1205+
}
1206+
1207+
/// Return an iterator adaptor that iterates over all k-permutations of the
1208+
/// elements from an iterator.
1209+
///
1210+
/// Iterator element type is `Vec<Self::Item>` with length `k`. The iterator
1211+
/// produces a new Vec per iteration, and clones the iterator elements.
1212+
///
1213+
/// If `k` is greater than the length of the input iterator, the resultant
1214+
/// iterator adaptor will be empty.
1215+
///
1216+
/// ```
1217+
/// use itertools::Itertools;
1218+
///
1219+
/// let perms = (5..8).permutations(2);
1220+
/// itertools::assert_equal(perms, vec![
1221+
/// vec![5, 6],
1222+
/// vec![5, 7],
1223+
/// vec![6, 5],
1224+
/// vec![6, 7],
1225+
/// vec![7, 5],
1226+
/// vec![7, 6],
1227+
/// ]);
1228+
/// ```
1229+
///
1230+
/// Note: Permutations does not take into account the equality of the iterated values.
1231+
///
1232+
/// ```
1233+
/// use itertools::Itertools;
1234+
///
1235+
/// let it = vec![2, 2].into_iter().permutations(2);
1236+
/// itertools::assert_equal(it, vec![
1237+
/// vec![2, 2], // Note: these are the same
1238+
/// vec![2, 2], // Note: these are the same
1239+
/// ]);
1240+
/// ```
1241+
///
1242+
/// Note: The source iterator is collected lazily, and will not be
1243+
/// re-iterated if the permutations adaptor is completed and re-iterated.
1244+
#[cfg(feature = "use_std")]
1245+
fn permutations(self, k: usize) -> Permutations<Self>
1246+
where Self: Sized,
1247+
Self::Item: Clone
1248+
{
1249+
permutations::permutations(self, k)
12021250
}
12031251

12041252
/// Return an iterator adaptor that pads the sequence to a minimum length of

0 commit comments

Comments
 (0)