Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 0eefe16

Browse files
committedMar 10, 2023
Initial working example of lending iterator for combinations.
Added benchmarks comparing `combinations` and `combinations_lending`. They show the lending implementation is ~1x-7x speed. Feature works either on or off for conditional compilation.
1 parent 5c21a87 commit 0eefe16

File tree

6 files changed

+625
-24
lines changed

6 files changed

+625
-24
lines changed
 

‎Cargo.toml

+7
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ test = false
2727

2828
[dependencies]
2929
either = { version = "1.0", default-features = false }
30+
lending-iterator = { version = "0.1.6", optional = true}
3031

3132
[dev-dependencies]
3233
rand = "0.7"
@@ -39,6 +40,7 @@ quickcheck = { version = "0.9", default_features = false }
3940
default = ["use_std"]
4041
use_std = ["use_alloc", "either/use_std"]
4142
use_alloc = []
43+
lending_iters = ["dep:lending-iterator"]
4244

4345
[profile]
4446
bench = { debug = true }
@@ -71,6 +73,11 @@ harness = false
7173
name = "combinations"
7274
harness = false
7375

76+
[[bench]]
77+
name = "combinations_lending"
78+
harness = false
79+
required-features = ["default", "lending_iters"]
80+
7481
[[bench]]
7582
name = "powerset"
7683
harness = false

‎benches/combinations.rs

+94
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::collections::{HashSet, VecDeque};
2+
13
use criterion::{black_box, criterion_group, criterion_main, Criterion};
24
use itertools::Itertools;
35

@@ -110,6 +112,91 @@ fn comb_c14(c: &mut Criterion) {
110112
});
111113
}
112114

115+
fn comb_single_use(c: &mut Criterion) {
116+
c.bench_function("comb single use", move |b| {
117+
b.iter(|| {
118+
let mut combination_bitmask = 0usize;
119+
(0..N14).combinations(14).for_each(|combo| {
120+
let compared_bitmask = 0b101010101010101011110000usize;
121+
combo.into_iter().for_each(|bit_pos| {
122+
combination_bitmask |= 1 << bit_pos;
123+
});
124+
black_box((combination_bitmask & compared_bitmask).count_ones());
125+
});
126+
})
127+
});
128+
}
129+
130+
fn comb_into_hash_set(c: &mut Criterion) {
131+
c.bench_function("comb into hash set", move |b| {
132+
b.iter(|| {
133+
(0..N14).combinations(14).for_each(|combo| {
134+
black_box({
135+
let mut out = HashSet::with_capacity(14);
136+
out.extend(combo);
137+
out
138+
});
139+
});
140+
})
141+
});
142+
}
143+
144+
fn comb_into_vec_deque(c: &mut Criterion) {
145+
c.bench_function("comb into vec deque", move |b| {
146+
b.iter(|| {
147+
(0..N14).combinations(14).for_each(|combo| {
148+
black_box(VecDeque::from(combo));
149+
});
150+
})
151+
});
152+
}
153+
154+
fn comb_into_slice(c: &mut Criterion) {
155+
c.bench_function("comb into slice", move |b| {
156+
b.iter(|| {
157+
(0..N14).combinations(14).for_each(|combo| {
158+
black_box({
159+
let mut out = [0; 14];
160+
let mut combo_iter = combo.into_iter();
161+
out.fill_with(|| combo_iter.next().unwrap_or_default());
162+
out
163+
});
164+
});
165+
})
166+
});
167+
}
168+
169+
fn comb_into_slice_unchecked(c: &mut Criterion) {
170+
c.bench_function("comb into slice unchecked", move |b| {
171+
b.iter(|| {
172+
(0..N14).combinations(14).for_each(|combo| {
173+
black_box({
174+
let mut out = [0; 14];
175+
let mut combo_iter = combo.into_iter();
176+
out.fill_with(|| combo_iter.next().unwrap());
177+
out
178+
});
179+
});
180+
})
181+
});
182+
}
183+
184+
fn comb_into_slice_for_loop(c: &mut Criterion) {
185+
c.bench_function("comb into slice for loop", move |b| {
186+
b.iter(|| {
187+
(0..N14).combinations(14).for_each(|combo| {
188+
black_box({
189+
let mut out = [0; 14];
190+
for (i, elem) in combo.into_iter().enumerate() {
191+
out[i] = elem;
192+
}
193+
out
194+
});
195+
});
196+
})
197+
});
198+
}
199+
113200
criterion_group!(
114201
benches,
115202
comb_for1,
@@ -121,5 +208,12 @@ criterion_group!(
121208
comb_c3,
122209
comb_c4,
123210
comb_c14,
211+
comb_single_use,
212+
comb_into_hash_set,
213+
comb_into_vec_deque,
214+
comb_into_slice,
215+
comb_into_slice_unchecked,
216+
comb_into_slice_for_loop,
124217
);
218+
125219
criterion_main!(benches);

‎benches/combinations_lending.rs

+193
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
#![cfg(feature = "lending_iters")]
2+
3+
use std::collections::{HashSet, VecDeque};
4+
5+
use criterion::{black_box, criterion_group, criterion_main, Criterion};
6+
use itertools::Itertools;
7+
use itertools::LendingIterator;
8+
9+
// approximate 100_000 iterations for each combination
10+
const N1: usize = 100_000;
11+
const N2: usize = 448;
12+
const N3: usize = 86;
13+
const N4: usize = 41;
14+
const N14: usize = 21;
15+
16+
fn comb_lending_c1(c: &mut Criterion) {
17+
c.bench_function("comb lending c1", move |b| {
18+
b.iter(|| {
19+
(0..N1).combinations_lending(1).for_each(|combo| {
20+
black_box({
21+
let mut out = Vec::with_capacity(1);
22+
out.extend(combo);
23+
out
24+
});
25+
});
26+
})
27+
});
28+
}
29+
30+
fn comb_lending_c2(c: &mut Criterion) {
31+
c.bench_function("comb lending c2", move |b| {
32+
b.iter(|| {
33+
(0..N2).combinations_lending(2).for_each(|combo| {
34+
black_box({
35+
let mut out = Vec::with_capacity(2);
36+
out.extend(combo);
37+
out
38+
});
39+
});
40+
})
41+
});
42+
}
43+
44+
fn comb_lending_c3(c: &mut Criterion) {
45+
c.bench_function("comb lending c3", move |b| {
46+
b.iter(|| {
47+
(0..N3).combinations_lending(3).for_each(|combo| {
48+
black_box({
49+
let mut out = Vec::with_capacity(3);
50+
out.extend(combo);
51+
out
52+
});
53+
});
54+
})
55+
});
56+
}
57+
58+
fn comb_lending_c4(c: &mut Criterion) {
59+
c.bench_function("comb lending c4", move |b| {
60+
b.iter(|| {
61+
(0..N4).combinations_lending(4).for_each(|combo| {
62+
black_box({
63+
let mut out = Vec::with_capacity(4);
64+
out.extend(combo);
65+
out
66+
});
67+
});
68+
})
69+
});
70+
}
71+
72+
fn comb_lending_c14(c: &mut Criterion) {
73+
c.bench_function("comb lending c14", move |b| {
74+
b.iter(|| {
75+
(0..N14).combinations_lending(14).for_each(|combo| {
76+
black_box({
77+
let mut out = Vec::with_capacity(14);
78+
out.extend(combo);
79+
out
80+
});
81+
});
82+
})
83+
});
84+
}
85+
86+
fn comb_lending_single_use(c: &mut Criterion) {
87+
c.bench_function("comb lending single use", move |b| {
88+
b.iter(|| {
89+
let mut combination_bitmask = 0usize;
90+
(0..N14).combinations_lending(14).for_each(|combo| {
91+
let compared_bitmask = 0b101010101010101011110000usize;
92+
combo.for_each(|bit_pos| {
93+
combination_bitmask |= 1 << bit_pos;
94+
});
95+
black_box((combination_bitmask & compared_bitmask).count_ones());
96+
});
97+
})
98+
});
99+
}
100+
101+
fn comb_lending_into_hash_set_from_collect(c: &mut Criterion) {
102+
c.bench_function("comb lending into hash set from collect", move |b| {
103+
b.iter(|| {
104+
(0..N14).combinations_lending(14).for_each(|combo| {
105+
black_box(combo.collect::<HashSet<_>>());
106+
});
107+
})
108+
});
109+
}
110+
111+
fn comb_lending_into_hash_set_from_extend(c: &mut Criterion) {
112+
c.bench_function("comb lending into hash set from extend", move |b| {
113+
b.iter(|| {
114+
(0..N14).combinations_lending(14).for_each(|combo| {
115+
black_box({
116+
let mut out = HashSet::with_capacity(14);
117+
out.extend(combo);
118+
out
119+
});
120+
});
121+
})
122+
});
123+
}
124+
125+
fn comb_lending_into_vec_deque_from_collect(c: &mut Criterion) {
126+
c.bench_function("comb lending into vec deque from collect", move |b| {
127+
b.iter(|| {
128+
(0..N14).combinations_lending(14).for_each(|combo| {
129+
black_box(combo.collect::<VecDeque<_>>());
130+
});
131+
})
132+
});
133+
}
134+
135+
fn comb_lending_into_vec_deque_from_extend(c: &mut Criterion) {
136+
c.bench_function("comb lending into vec deque from extend", move |b| {
137+
b.iter(|| {
138+
(0..N14).combinations_lending(14).for_each(|combo| {
139+
black_box({
140+
let mut out = VecDeque::with_capacity(14);
141+
out.extend(combo);
142+
out
143+
});
144+
});
145+
})
146+
});
147+
}
148+
149+
fn comb_lending_into_slice(c: &mut Criterion) {
150+
c.bench_function("comb lending into slice", move |b| {
151+
b.iter(|| {
152+
(0..N14).combinations_lending(14).for_each(|mut combo| {
153+
black_box({
154+
let mut out = [0; 14];
155+
out.fill_with(|| combo.next().unwrap_or_default());
156+
out
157+
});
158+
});
159+
})
160+
});
161+
}
162+
163+
fn comb_lending_into_slice_unchecked(c: &mut Criterion) {
164+
c.bench_function("comb lending into slice unchecked", move |b| {
165+
b.iter(|| {
166+
(0..N14).combinations_lending(14).for_each(|mut combo| {
167+
black_box({
168+
let mut out = [0; 14];
169+
out.fill_with(|| combo.next().unwrap());
170+
out
171+
});
172+
});
173+
})
174+
});
175+
}
176+
177+
criterion_group!(
178+
benches,
179+
comb_lending_c1,
180+
comb_lending_c2,
181+
comb_lending_c3,
182+
comb_lending_c4,
183+
comb_lending_c14,
184+
comb_lending_single_use,
185+
comb_lending_into_hash_set_from_collect,
186+
comb_lending_into_hash_set_from_extend,
187+
comb_lending_into_vec_deque_from_collect,
188+
comb_lending_into_vec_deque_from_extend,
189+
comb_lending_into_slice,
190+
comb_lending_into_slice_unchecked,
191+
);
192+
193+
criterion_main!(benches);

‎src/combinations.rs

+206-23
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,44 @@ use std::iter::FusedIterator;
44
use super::lazy_buffer::LazyBuffer;
55
use alloc::vec::Vec;
66

7+
/// Marker indicating the iterator is being used as `LendingIterator` type.
8+
#[cfg(feature = "lending_iters")]
9+
pub struct Lending;
10+
/// Marker indicating the iterator is being used as `Iterator` type.
11+
pub struct NonLending;
12+
713
/// An iterator to iterate through all the `k`-length combinations in an iterator.
814
///
9-
/// See [`.combinations()`](crate::Itertools::combinations) for more information.
15+
/// See [`.combinations()`](crate::Itertools::combinations) and [`.combinations_lending()`](crate::Itertools::combinations_lending) for more information.
1016
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
11-
pub struct Combinations<I: Iterator> {
17+
pub struct Combinations<I: Iterator, State = NonLending> {
1218
indices: Vec<usize>,
1319
pool: LazyBuffer<I>,
1420
first: bool,
21+
// Disambiguate the purpose of the iterator and makes use not require fully qualified path to disambiguate. Instead chosen by constructor.
22+
phantom: std::marker::PhantomData<State>,
1523
}
1624

1725
impl<I> Clone for Combinations<I>
18-
where I: Clone + Iterator,
19-
I::Item: Clone,
26+
where
27+
I: Clone + Iterator,
28+
I::Item: Clone,
2029
{
21-
clone_fields!(indices, pool, first);
30+
clone_fields!(indices, pool, first, phantom);
2231
}
2332

24-
impl<I> fmt::Debug for Combinations<I>
25-
where I: Iterator + fmt::Debug,
26-
I::Item: fmt::Debug,
33+
impl<State, I> fmt::Debug for Combinations<I, State>
34+
where
35+
I: Iterator + fmt::Debug,
36+
I::Item: fmt::Debug,
2737
{
2838
debug_fmt_fields!(Combinations, indices, pool, first);
2939
}
3040

31-
/// Create a new `Combinations` from a clonable iterator.
41+
/// Create a new `Iterator` from an iterator.
3242
pub fn combinations<I>(iter: I, k: usize) -> Combinations<I>
33-
where I: Iterator
43+
where
44+
I: Iterator,
3445
{
3546
let mut pool = LazyBuffer::new(iter);
3647
pool.prefill(k);
@@ -39,27 +50,36 @@ pub fn combinations<I>(iter: I, k: usize) -> Combinations<I>
3950
indices: (0..k).collect(),
4051
pool,
4152
first: true,
53+
phantom: std::marker::PhantomData::<NonLending>,
4254
}
4355
}
4456

45-
impl<I: Iterator> Combinations<I> {
57+
impl<I: Iterator, State> Combinations<I, State> {
4658
/// Returns the length of a combination produced by this iterator.
4759
#[inline]
48-
pub fn k(&self) -> usize { self.indices.len() }
60+
pub fn k(&self) -> usize {
61+
self.indices.len()
62+
}
4963

5064
/// Returns the (current) length of the pool from which combination elements are
5165
/// selected. This value can change between invocations of [`next`](Combinations::next).
5266
#[inline]
53-
pub fn n(&self) -> usize { self.pool.len() }
67+
pub fn n(&self) -> usize {
68+
self.pool.len()
69+
}
5470

5571
/// Returns a reference to the source iterator.
5672
#[inline]
57-
pub(crate) fn src(&self) -> &I { &self.pool.it }
73+
#[allow(dead_code)] // Not actually dead. Used in powerset.
74+
pub(crate) fn src(&self) -> &I {
75+
&self.pool.it
76+
}
5877

5978
/// Resets this `Combinations` back to an initial state for combinations of length
6079
/// `k` over the same pool data source. If `k` is larger than the current length
6180
/// of the data pool an attempt is made to prefill the pool so that it holds `k`
6281
/// elements.
82+
#[allow(dead_code)] // Not actually dead. Used in powerset.
6383
pub(crate) fn reset(&mut self, k: usize) {
6484
self.first = true;
6585

@@ -68,7 +88,6 @@ impl<I: Iterator> Combinations<I> {
6888
for i in 0..k {
6989
self.indices[i] = i;
7090
}
71-
7291
} else {
7392
for i in 0..self.indices.len() {
7493
self.indices[i] = i;
@@ -79,9 +98,10 @@ impl<I: Iterator> Combinations<I> {
7998
}
8099
}
81100

82-
impl<I> Iterator for Combinations<I>
83-
where I: Iterator,
84-
I::Item: Clone
101+
impl<I> Iterator for Combinations<I, NonLending>
102+
where
103+
I: Iterator,
104+
I::Item: Clone,
85105
{
86106
type Item = Vec<I::Item>;
87107
fn next(&mut self) -> Option<Self::Item> {
@@ -112,17 +132,180 @@ impl<I> Iterator for Combinations<I>
112132

113133
// Increment index, and reset the ones to its right
114134
self.indices[i] += 1;
115-
for j in i+1..self.indices.len() {
135+
for j in i + 1..self.indices.len() {
116136
self.indices[j] = self.indices[j - 1] + 1;
117137
}
118138
}
119139

120140
// Create result vector based on the indices
121-
Some(self.indices.iter().map(|i| self.pool[*i].clone()).collect())
141+
let mut out = Vec::with_capacity(self.k());
142+
out.extend(self.indices.iter().map(|i| self.pool[*i].clone()));
143+
Some(out)
122144
}
123145
}
124146

125147
impl<I> FusedIterator for Combinations<I>
126-
where I: Iterator,
127-
I::Item: Clone
128-
{}
148+
where
149+
I: Iterator,
150+
I::Item: Clone,
151+
{
152+
}
153+
154+
#[cfg(feature = "lending_iters")]
155+
pub mod lending {
156+
use super::*;
157+
pub use lending_iterator::prelude::{gat, LendingIterator, LendingIteratorඞItem};
158+
159+
/// Create a new `LendingIterator` from an iterator.
160+
pub fn combinations_lending<I>(iter: I, k: usize) -> Combinations<I, Lending>
161+
where
162+
I: Iterator,
163+
{
164+
let mut pool = LazyBuffer::new(iter);
165+
pool.prefill(k);
166+
167+
Combinations {
168+
indices: (0..k).collect(),
169+
pool,
170+
first: true,
171+
phantom: std::marker::PhantomData::<Lending>,
172+
}
173+
}
174+
175+
#[gat]
176+
impl<I> LendingIterator for Combinations<I, Lending>
177+
where
178+
I: Iterator,
179+
I::Item: Clone,
180+
{
181+
type Item<'next>
182+
where
183+
Self: 'next,
184+
= Combination<'next, I>;
185+
fn next(&mut self) -> Option<Combination<I>> {
186+
if self.first {
187+
if self.k() > self.n() {
188+
return None;
189+
}
190+
self.first = false;
191+
} else if self.indices.is_empty() {
192+
return None;
193+
} else {
194+
// Scan from the end, looking for an index to increment
195+
let mut i: usize = self.indices.len() - 1;
196+
197+
// Check if we need to consume more from the iterator
198+
if self.indices[i] == self.pool.len() - 1 {
199+
self.pool.get_next(); // may change pool size
200+
}
201+
202+
while self.indices[i] == i + self.pool.len() - self.indices.len() {
203+
if i > 0 {
204+
i -= 1;
205+
} else {
206+
// Reached the last combination
207+
return None;
208+
}
209+
}
210+
211+
// Increment index, and reset the ones to its right
212+
self.indices[i] += 1;
213+
for j in i + 1..self.indices.len() {
214+
self.indices[j] = self.indices[j - 1] + 1;
215+
}
216+
}
217+
218+
// Create result vector based on the indices
219+
// let out: () = Some(self.indices.iter().map(|i| self.pool[*i].clone()));
220+
Some(Combination {
221+
combinations: &*self,
222+
index: 0,
223+
})
224+
}
225+
}
226+
227+
impl<I> Combinations<I, Lending>
228+
where
229+
I: Iterator,
230+
I::Item: Clone,
231+
{
232+
/// Applies `collect_vec()` on interior iterators and then again on the result.
233+
#[cfg(feature = "use_alloc")]
234+
pub fn collect_nested_vec(self) -> Vec<Vec<I::Item>>
235+
where
236+
Self: Sized,
237+
{
238+
use crate::Itertools;
239+
240+
self.map_into_iter(|x| x.collect_vec()).collect_vec()
241+
}
242+
}
243+
244+
// TODO Should take precedence over LendingIterator blanket impl for IntoIterator. How to do?
245+
// Appears to works correctly given sufficient type hints/context such as a for loop.
246+
impl<I> IntoIterator for Combinations<I, Lending>
247+
where
248+
I: Iterator,
249+
I::Item: Clone,
250+
{
251+
type Item = Vec<I::Item>;
252+
253+
type IntoIter = Combinations<I, NonLending>;
254+
255+
/// The phantom marker changing is sufficient to change this into an iterator because it implements `Iterator` as well and `Lendingiterator`
256+
#[inline]
257+
fn into_iter(self) -> Self::IntoIter {
258+
Combinations {
259+
indices: self.indices,
260+
pool: self.pool,
261+
first: self.first,
262+
phantom: core::marker::PhantomData::<NonLending>,
263+
}
264+
}
265+
}
266+
267+
/// Iterator over the elements of a particular combination. This allows avoiding unnecessary heap allocations if the use of the combinations is not a `Vec`.
268+
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
269+
#[derive(Clone)]
270+
pub struct Combination<'a, I>
271+
where
272+
I: Iterator,
273+
I::Item: Clone,
274+
{
275+
combinations: &'a Combinations<I, Lending>,
276+
index: usize, // Index of the combinations indices
277+
}
278+
279+
impl<'a, I> fmt::Debug for Combination<'a, I>
280+
where
281+
I: Iterator + fmt::Debug,
282+
I::Item: fmt::Debug,
283+
I::Item: Clone,
284+
{
285+
// Allows implementing Debug for items that implement Debug without requiring Debug to use the iterator.
286+
debug_fmt_fields!(Combination, combinations, index);
287+
}
288+
289+
impl<'a, I> Iterator for Combination<'a, I>
290+
where
291+
I: Iterator,
292+
I::Item: Clone,
293+
{
294+
type Item = I::Item;
295+
296+
// Simply increment through the indices that fetch values from the pool.
297+
fn next(&mut self) -> Option<Self::Item> {
298+
if self.index >= self.combinations.indices.len() {
299+
None
300+
} else {
301+
self.index += 1;
302+
Some(self.combinations.pool[self.combinations.indices[self.index - 1]].clone())
303+
}
304+
}
305+
306+
fn size_hint(&self) -> (usize, Option<usize>) {
307+
// If a combination is returned it is always of length k.
308+
(self.combinations.k(), Some(self.combinations.k()))
309+
}
310+
}
311+
}

‎src/lib.rs

+65-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,9 @@ pub mod structs {
113113
#[cfg(feature = "use_alloc")]
114114
pub use crate::adaptors::MultiProduct;
115115
#[cfg(feature = "use_alloc")]
116-
pub use crate::combinations::Combinations;
116+
pub use crate::combinations::{Combinations};
117+
#[cfg(feature = "lending_iters")]
118+
pub use crate::combinations::{Lending, NonLending, lending::{Combination, LendingIterator}};
117119
#[cfg(feature = "use_alloc")]
118120
pub use crate::combinations_with_replacement::CombinationsWithReplacement;
119121
pub use crate::cons_tuples_impl::ConsTuples;
@@ -1487,6 +1489,68 @@ pub trait Itertools : Iterator {
14871489
combinations::combinations(self, k)
14881490
}
14891491

1492+
/// Return an iterator adaptor that iterates over the `k`-length combinations of
1493+
/// the elements from an iterator. It is a `LendingIterator` of an `Iterator` of the elements.
1494+
///
1495+
/// LendingIterator element type is another iterator of type [`Combination<'next, I>`](crate::Combination) where
1496+
/// `'next` is the lifetime passed to the `.next()` call.
1497+
///
1498+
/// The element type of the interior iterator is the `Self::Item` this iterator is adapting.
1499+
///
1500+
/// This means that no extra heap allocation for a vector is necessary and can be multiple times faster.
1501+
///
1502+
/// Use `.into_iter()` (preferred) or `.map_into_iter(|x| x.collect_vec())` if you wish to convert it to a normal non lending `Iterator`.
1503+
/// This will lose the speed benefits of lending.
1504+
/// If used in a for loop implicit call to `.into_iter()` will allocate a vector on the heap like the non lending version.
1505+
/// Consider using `.for_each` or `while let Some(combination) = combinations_lending_iter.next()` instead.
1506+
///
1507+
/// Must import `itertools::LendingIterator` type. If you do not you will encounter a
1508+
/// "no method named `next` found for struct `Combinations` in the current scope" error.
1509+
/// ```
1510+
/// use itertools::{Itertools, LendingIterator};
1511+
///
1512+
/// let it = (1..5).combinations_lending(3);
1513+
/// itertools::assert_equal(it, vec![
1514+
/// vec![1, 2, 3],
1515+
/// vec![1, 2, 4],
1516+
/// vec![1, 3, 4],
1517+
/// vec![2, 3, 4],
1518+
/// ]);
1519+
/// ```
1520+
///
1521+
/// Collection into a non-vec type is more efficient with this method:
1522+
/// ```
1523+
/// use std::collections::HashSet;
1524+
/// use std::iter::FromIterator;
1525+
/// use itertools::{Itertools, LendingIterator};
1526+
///
1527+
/// let mut combinations_lending_iter = (0..20).combinations_lending(4);
1528+
/// let mut combinations_iter = (0..20).combinations(4);
1529+
/// while let Some(combination) = combinations_lending_iter.next() {
1530+
/// let combination_slow = combinations_iter.next().unwrap();
1531+
/// assert_eq!(combination.collect::<HashSet<_>>(), HashSet::from_iter(combination_slow.into_iter()))
1532+
/// }
1533+
/// ```
1534+
///
1535+
/// Note: Combinations does not take into account the equality of the iterated values.
1536+
/// ```
1537+
/// use itertools::{Itertools, LendingIterator};
1538+
///
1539+
/// let it = vec![1, 2, 2].into_iter().combinations(2);
1540+
/// itertools::assert_equal(it, vec![
1541+
/// vec![1, 2], // Note: these are the same
1542+
/// vec![1, 2], // Note: these are the same
1543+
/// vec![2, 2],
1544+
/// ]);
1545+
/// ```
1546+
#[cfg(feature = "lending_iters")]
1547+
fn combinations_lending(self, k: usize) -> combinations::Combinations<Self, combinations::Lending>
1548+
where Self: Sized,
1549+
Self::Item: Clone
1550+
{
1551+
combinations::lending::combinations_lending(self, k)
1552+
}
1553+
14901554
/// Return an iterator that iterates over the `k`-length combinations of
14911555
/// the elements from an iterator, with replacement.
14921556
///

‎tests/test_std.rs

+60
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ use rand::{seq::SliceRandom, thread_rng};
44
use std::{cmp::min, fmt::Debug, marker::PhantomData};
55
use itertools as it;
66
use crate::it::Itertools;
7+
#[cfg(feature = "lending_iters")]
8+
use crate::it::LendingIterator;
79
use crate::it::ExactlyOneError;
810
use crate::it::multizip;
911
use crate::it::multipeek;
@@ -867,6 +869,7 @@ fn concat_non_empty() {
867869

868870
#[test]
869871
fn combinations() {
872+
870873
assert!((1..3).combinations(5).next().is_none());
871874

872875
let it = (1..3).combinations(2);
@@ -884,6 +887,8 @@ fn combinations() {
884887
vec![3, 4],
885888
]);
886889

890+
891+
887892
it::assert_equal((0..0).tuple_combinations::<(_, _)>(), <Vec<_>>::new());
888893
it::assert_equal((0..1).tuple_combinations::<(_, _)>(), <Vec<_>>::new());
889894
it::assert_equal((0..2).tuple_combinations::<(_, _)>(), vec![(0, 1)]);
@@ -909,6 +914,61 @@ fn combinations_zero() {
909914
it::assert_equal((0..0).combinations(0), vec![vec![]]);
910915
}
911916

917+
#[test]
918+
#[cfg(feature = "lending_iters")]
919+
fn combinations_lending_feature_parity_to_non_lending() {
920+
assert!((1..3).combinations_lending(5).next().is_none());
921+
922+
let it = (1..3).combinations_lending(2).map_into_iter(|x| x.collect_vec()).collect::<Vec<_>>();
923+
it::assert_equal(it, vec![
924+
vec![1, 2],
925+
]);
926+
927+
let mut out = Vec::new();
928+
for i in (1..3).combinations_lending(2) {
929+
out.push(i);
930+
}
931+
it::assert_equal(out, vec![vec![1, 2]]);
932+
933+
let it = (1..5).combinations_lending(2).collect_nested_vec();
934+
it::assert_equal(it, vec![
935+
vec![1, 2],
936+
vec![1, 3],
937+
vec![1, 4],
938+
vec![2, 3],
939+
vec![2, 4],
940+
vec![3, 4],
941+
]);
942+
943+
it::assert_equal((0..0).combinations_lending(2).map_into_iter(|x| x.collect_vec()).collect_vec(), <Vec<Vec<_>>>::new());
944+
945+
it::assert_equal((0..1).combinations_lending(1).collect_nested_vec(), vec![vec![0]]);
946+
it::assert_equal((0..2).combinations_lending(1).collect_nested_vec(), vec![vec![0], vec![1]]);
947+
it::assert_equal((0..2).combinations_lending(2).collect_nested_vec(), vec![vec![0, 1]]);
948+
949+
for i in 1..10 {
950+
assert!((0..0).combinations_lending(i).next().is_none());
951+
assert!((0..i - 1).combinations_lending(i).next().is_none());
952+
953+
}
954+
it::assert_equal((1..3).combinations_lending(0), vec![vec![]]);
955+
it::assert_equal((0..0).combinations_lending(0), vec![vec![]]);
956+
957+
it::assert_equal((1..3).combinations(0), (1..3).combinations_lending(0));
958+
it::assert_equal((0..0).combinations(0), (0..0).combinations_lending(0));
959+
assert_eq!((1..3).combinations(0).collect_vec(), (1..3).combinations_lending(0).collect_nested_vec()); // Should match exactly including type.
960+
it::assert_equal((0..0).combinations(0), (0..0).combinations_lending(0));
961+
}
962+
963+
// Below shouldn't compile because of attempt to reference an already mut reference.
964+
// #[test]
965+
// fn combinations_lending_cant_double_mut() {
966+
// let mut out = (1..4).combinations_lending(2);
967+
// let mut combination = out.next().unwrap();
968+
// let combination2 = out.next().unwrap();
969+
// combination.next();
970+
// }
971+
912972
#[test]
913973
fn permutations_zero() {
914974
it::assert_equal((1..3).permutations(0), vec![vec![]]);

0 commit comments

Comments
 (0)
Please sign in to comment.