Skip to content

Commit 1bb419b

Browse files
ronnodasphimuemue
authored andcommitted
array_combinations using array::from_fn
1 parent a31e14f commit 1bb419b

File tree

4 files changed

+228
-3
lines changed

4 files changed

+228
-3
lines changed

src/array_combinations.rs

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
use core::iter::FusedIterator;
2+
use core::{array, fmt};
3+
4+
use crate::combinations::{n_and_count, remaining_for};
5+
use crate::lazy_buffer::LazyBuffer;
6+
7+
/// An iterator to iterate through all combinations in an iterator of `Clone`-able items that
8+
/// produces arrays of a specific size.
9+
///
10+
/// See [`.array_combinations()`](crate::Itertools::array_combinations) for more
11+
/// information.
12+
#[derive(Clone)]
13+
#[must_use = "this iterator adaptor is not lazy but does nearly nothing unless consumed"]
14+
pub struct ArrayCombinations<I: Iterator, const K: usize>
15+
where
16+
I::Item: Clone,
17+
{
18+
indices: [usize; K],
19+
pool: LazyBuffer<I>,
20+
first: bool,
21+
}
22+
23+
/// Create a new `ArrayCombinations` from a clonable iterator.
24+
pub fn array_combinations<I: Iterator, const K: usize>(iter: I) -> ArrayCombinations<I, K>
25+
where
26+
I::Item: Clone,
27+
{
28+
let indices = array::from_fn(|i| i);
29+
let pool = LazyBuffer::new(iter);
30+
31+
ArrayCombinations {
32+
indices,
33+
pool,
34+
first: true,
35+
}
36+
}
37+
38+
impl<I: Iterator, const K: usize> ArrayCombinations<I, K>
39+
where
40+
I::Item: Clone,
41+
{
42+
/// Returns the (current) length of the pool from which combination elements are
43+
/// selected. This value can change between invocations of [`next`](Combinations::next).
44+
#[inline]
45+
pub fn n(&self) -> usize {
46+
self.pool.len()
47+
}
48+
49+
/// Initialises the iterator by filling a buffer with elements from the
50+
/// iterator. Returns true if there are no combinations, false otherwise.
51+
fn init(&mut self) -> bool {
52+
self.pool.prefill(K);
53+
let done = K > self.n();
54+
if !done {
55+
self.first = false;
56+
}
57+
58+
done
59+
}
60+
61+
/// Increments indices representing the combination to advance to the next
62+
/// (in lexicographic order by increasing sequence) combination. For example
63+
/// if we have n=4 & k=2 then `[0, 1] -> [0, 2] -> [0, 3] -> [1, 2] -> ...`
64+
///
65+
/// Returns true if we've run out of combinations, false otherwise.
66+
fn increment_indices(&mut self) -> bool {
67+
if K == 0 {
68+
return true; // Done
69+
}
70+
71+
// Scan from the end, looking for an index to increment
72+
let mut i: usize = K - 1;
73+
74+
// Check if we need to consume more from the iterator
75+
if self.indices[i] == self.pool.len() - 1 {
76+
_ = self.pool.get_next(); // may change pool size
77+
}
78+
79+
while self.indices[i] == i + self.pool.len() - K {
80+
if i > 0 {
81+
i -= 1;
82+
} else {
83+
// Reached the last combination
84+
return true;
85+
}
86+
}
87+
88+
// Increment index, and reset the ones to its right
89+
self.indices[i] += 1;
90+
for j in i + 1..K {
91+
self.indices[j] = self.indices[j - 1] + 1;
92+
}
93+
94+
// If we've made it this far, we haven't run out of combos
95+
false
96+
}
97+
98+
/// Returns the n-th item or the number of successful steps.
99+
pub(crate) fn try_nth(&mut self, n: usize) -> Result<<Self as Iterator>::Item, usize>
100+
where
101+
I::Item: Clone,
102+
{
103+
let done = if self.first {
104+
self.init()
105+
} else {
106+
self.increment_indices()
107+
};
108+
if done {
109+
return Err(0);
110+
}
111+
for i in 0..n {
112+
if self.increment_indices() {
113+
return Err(i + 1);
114+
}
115+
}
116+
Ok(self.pool.get_array(self.indices))
117+
}
118+
}
119+
120+
impl<I: Iterator, const K: usize> Iterator for ArrayCombinations<I, K>
121+
where
122+
I::Item: Clone,
123+
{
124+
type Item = [I::Item; K];
125+
126+
fn next(&mut self) -> Option<Self::Item> {
127+
let done = if self.first {
128+
self.init()
129+
} else {
130+
self.increment_indices()
131+
};
132+
133+
(!done).then(|| self.pool.get_array(self.indices))
134+
}
135+
136+
fn nth(&mut self, n: usize) -> Option<Self::Item> {
137+
self.try_nth(n).ok()
138+
}
139+
140+
fn size_hint(&self) -> (usize, Option<usize>) {
141+
let (mut low, mut upp) = self.pool.size_hint();
142+
low = remaining_for(low, self.first, &self.indices).unwrap_or(usize::MAX);
143+
upp = upp.and_then(|upp| remaining_for(upp, self.first, &self.indices));
144+
(low, upp)
145+
}
146+
147+
#[inline]
148+
fn count(self) -> usize {
149+
n_and_count(self.pool, self.first, &self.indices).1
150+
}
151+
}
152+
153+
impl<I, const K: usize> fmt::Debug for ArrayCombinations<I, K>
154+
where
155+
I: Iterator + fmt::Debug,
156+
I::Item: Clone + fmt::Debug,
157+
{
158+
debug_fmt_fields!(ArrayCombinations, indices, pool, first);
159+
}
160+
161+
impl<I, const K: usize> FusedIterator for ArrayCombinations<I, K>
162+
where
163+
I: FusedIterator,
164+
I::Item: Clone,
165+
{
166+
}

src/combinations.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,7 @@ impl<I: Iterator> Combinations<I> {
9191
pool,
9292
first,
9393
} = self;
94-
let n = pool.count();
95-
(n, remaining_for(n, first, &indices).unwrap())
94+
n_and_count(pool, first, &indices)
9695
}
9796

9897
/// Initialises the iterator by filling a buffer with elements from the
@@ -210,8 +209,17 @@ where
210209
{
211210
}
212211

212+
pub(crate) fn n_and_count<I: Iterator>(
213+
pool: LazyBuffer<I>,
214+
first: bool,
215+
indices: &[usize],
216+
) -> (usize, usize) {
217+
let n = pool.count();
218+
(n, remaining_for(n, first, indices).unwrap())
219+
}
220+
213221
/// For a given size `n`, return the count of remaining combinations or None if it would overflow.
214-
fn remaining_for(n: usize, first: bool, indices: &[usize]) -> Option<usize> {
222+
pub(crate) fn remaining_for(n: usize, first: bool, indices: &[usize]) -> Option<usize> {
215223
let k = indices.len();
216224
if n < k {
217225
Some(0)

src/lazy_buffer.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ where
5959
pub fn get_at(&self, indices: &[usize]) -> Vec<I::Item> {
6060
indices.iter().map(|i| self.buffer[*i].clone()).collect()
6161
}
62+
63+
pub fn get_array<const K: usize>(&self, indices: [usize; K]) -> [I::Item; K] {
64+
indices.map(|i| self.buffer[i].clone())
65+
}
6266
}
6367

6468
impl<I, J> Index<J> for LazyBuffer<I>

src/lib.rs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ pub mod structs {
9696
FilterOk, Interleave, InterleaveShortest, MapInto, MapOk, Positions, Product, PutBack,
9797
TakeWhileRef, TupleCombinations, Update, WhileSome,
9898
};
99+
pub use crate::array_combinations::ArrayCombinations;
99100
#[cfg(feature = "use_alloc")]
100101
pub use crate::combinations::Combinations;
101102
#[cfg(feature = "use_alloc")]
@@ -177,6 +178,7 @@ pub use crate::either_or_both::EitherOrBoth;
177178
pub mod free;
178179
#[doc(inline)]
179180
pub use crate::free::*;
181+
mod array_combinations;
180182
#[cfg(feature = "use_alloc")]
181183
mod combinations;
182184
#[cfg(feature = "use_alloc")]
@@ -1674,6 +1676,51 @@ pub trait Itertools: Iterator {
16741676
adaptors::tuple_combinations(self)
16751677
}
16761678

1679+
/// Return an iterator adaptor that iterates over the combinations of the
1680+
/// elements from an iterator.
1681+
///
1682+
/// Iterator element can be any array of type `Self::Item`.
1683+
///
1684+
/// # Guarantees
1685+
///
1686+
/// If the adapted iterator is deterministic,
1687+
/// this iterator adapter yields items in a reliable order.
1688+
///
1689+
/// ```
1690+
/// use itertools::Itertools;
1691+
///
1692+
/// let mut v = Vec::new();
1693+
/// for [a, b] in (1..5).array_combinations() {
1694+
/// v.push([a, b]);
1695+
/// }
1696+
/// assert_eq!(v, vec![[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]);
1697+
///
1698+
/// let mut it = (1..5).array_combinations();
1699+
/// assert_eq!(Some([1, 2, 3]), it.next());
1700+
/// assert_eq!(Some([1, 2, 4]), it.next());
1701+
/// assert_eq!(Some([1, 3, 4]), it.next());
1702+
/// assert_eq!(Some([2, 3, 4]), it.next());
1703+
/// assert_eq!(None, it.next());
1704+
///
1705+
/// // this requires a type hint
1706+
/// let it = (1..5).array_combinations::<3>();
1707+
/// itertools::assert_equal(it, vec![[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]]);
1708+
///
1709+
/// // you can also specify the complete type
1710+
/// use itertools::ArrayCombinations;
1711+
/// use std::ops::Range;
1712+
///
1713+
/// let it: ArrayCombinations<Range<u32>, 3> = (1..5).array_combinations();
1714+
/// itertools::assert_equal(it, vec![[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]]);
1715+
/// ```
1716+
fn array_combinations<const K: usize>(self) -> ArrayCombinations<Self, K>
1717+
where
1718+
Self: Sized + Clone,
1719+
Self::Item: Clone,
1720+
{
1721+
array_combinations::array_combinations(self)
1722+
}
1723+
16771724
/// Return an iterator adaptor that iterates over the `k`-length combinations of
16781725
/// the elements from an iterator.
16791726
///

0 commit comments

Comments
 (0)