Skip to main content

itertools/
combinations_with_replacement.rs

1use alloc::boxed::Box;
2use std::fmt;
3use std::iter::FusedIterator;
4
5use super::lazy_buffer::LazyBuffer;
6use crate::adaptors::checked_binomial;
7use crate::combinations::PoolIndex;
8/// An iterator to iterate through all the `n`-length combinations in an iterator, with replacement.
9///
10/// See [`.combinations_with_replacement()`](crate::Itertools::combinations_with_replacement)
11/// for more information.
12#[derive(Clone)]
13#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
14pub struct CombinationsWithReplacementGeneric<I, Idx>
15where
16    I: Iterator,
17    I::Item: Clone,
18{
19    indices: Idx,
20    pool: LazyBuffer<I>,
21    first: bool,
22}
23
24/// Iterator for `Box<[I]>` valued combinations_with_replacement returned by [`.combinations_with_replacement()`](crate::Itertools::combinations_with_replacement)
25pub type CombinationsWithReplacement<I> = CombinationsWithReplacementGeneric<I, Box<[usize]>>;
26/// Iterator for const generic combinations_with_replacement returned by [`.array_combinations_with_replacement()`](crate::Itertools::array_combinations_with_replacement)
27pub type ArrayCombinationsWithReplacement<I, const K: usize> =
28    CombinationsWithReplacementGeneric<I, [usize; K]>;
29
30impl<I, Idx> fmt::Debug for CombinationsWithReplacementGeneric<I, Idx>
31where
32    I: Iterator + fmt::Debug,
33    I::Item: fmt::Debug + Clone,
34    Idx: fmt::Debug,
35{
36    debug_fmt_fields!(CombinationsWithReplacementGeneric, indices, pool, first);
37}
38
39/// Create a new `ArrayCombinationsWithReplacement`` from a cloneable iterator.
40pub fn array_combinations_with_replacement<I: Iterator, const K: usize>(
41    iter: I,
42) -> ArrayCombinationsWithReplacement<I, K>
43where
44    I::Item: Clone,
45{
46    ArrayCombinationsWithReplacement::new(iter, [0; K])
47}
48/// Create a new `CombinationsWithReplacement` from a cloneable iterator.
49pub fn combinations_with_replacement<I>(iter: I, k: usize) -> CombinationsWithReplacement<I>
50where
51    I: Iterator,
52    I::Item: Clone,
53{
54    let indices = alloc::vec![0; k].into_boxed_slice();
55
56    CombinationsWithReplacementGeneric::new(iter, indices)
57}
58
59impl<I: Iterator, Idx: PoolIndex<I::Item>> CombinationsWithReplacementGeneric<I, Idx>
60where
61    I: Iterator,
62    I::Item: Clone,
63{
64    /// Increments indices representing the combination to advance to the next
65    /// (in lexicographic order by increasing sequence) combination.
66    ///
67    /// Returns true if we've run out of combinations, false otherwise.
68    fn increment_indices(&mut self) -> bool {
69        // Check if we need to consume more from the iterator
70        // This will run while we increment our first index digit
71        self.pool.get_next();
72
73        // Work out where we need to update our indices
74        let mut increment = None;
75        let indices: &mut [usize] = self.indices.borrow_mut();
76        for (i, indices_int) in indices.iter().enumerate().rev() {
77            if *indices_int < self.pool.len() - 1 {
78                increment = Some((i, indices_int + 1));
79                break;
80            }
81        }
82        match increment {
83            // If we can update the indices further
84            Some((increment_from, increment_value)) => {
85                // We need to update the rightmost non-max value
86                // and all those to the right
87                indices[increment_from..].fill(increment_value);
88                false
89            }
90            // Otherwise, we're done
91            None => true,
92        }
93    }
94    /// Constructor with arguments the inner iterator and the initial state for the indices.
95    fn new(iter: I, indices: Idx) -> Self {
96        Self {
97            indices,
98            pool: LazyBuffer::new(iter),
99            first: true,
100        }
101    }
102}
103
104impl<I, Idx> Iterator for CombinationsWithReplacementGeneric<I, Idx>
105where
106    I: Iterator,
107    I::Item: Clone,
108    Idx: PoolIndex<I::Item>,
109{
110    type Item = Idx::Item;
111
112    fn next(&mut self) -> Option<Self::Item> {
113        if self.first {
114            // In empty edge cases, stop iterating immediately
115            if !(self.indices.borrow().is_empty() || self.pool.get_next()) {
116                return None;
117            }
118            self.first = false;
119        } else if self.increment_indices() {
120            return None;
121        }
122        Some(self.indices.extract_item(&self.pool))
123    }
124
125    fn nth(&mut self, n: usize) -> Option<Self::Item> {
126        if self.first {
127            // In empty edge cases, stop iterating immediately
128            if !(self.indices.borrow().is_empty() || self.pool.get_next()) {
129                return None;
130            }
131            self.first = false;
132        } else if self.increment_indices() {
133            return None;
134        }
135        for _ in 0..n {
136            if self.increment_indices() {
137                return None;
138            }
139        }
140        Some(self.indices.extract_item(&self.pool))
141    }
142
143    fn size_hint(&self) -> (usize, Option<usize>) {
144        let (mut low, mut upp) = self.pool.size_hint();
145        low = remaining_for(low, self.first, self.indices.borrow()).unwrap_or(usize::MAX);
146        upp = upp.and_then(|upp| remaining_for(upp, self.first, self.indices.borrow()));
147        (low, upp)
148    }
149
150    fn count(self) -> usize {
151        let Self {
152            indices,
153            pool,
154            first,
155        } = self;
156        let n = pool.count();
157        remaining_for(n, first, indices.borrow()).unwrap()
158    }
159}
160
161impl<I, Idx> FusedIterator for CombinationsWithReplacementGeneric<I, Idx>
162where
163    I: Iterator,
164    I::Item: Clone,
165    Idx: PoolIndex<I::Item>,
166{
167}
168
169/// For a given size `n`, return the count of remaining combinations with replacement or None if it would overflow.
170fn remaining_for(n: usize, first: bool, indices: &[usize]) -> Option<usize> {
171    // With a "stars and bars" representation, choose k values with replacement from n values is
172    // like choosing k out of k + n − 1 positions (hence binomial(k + n - 1, k) possibilities)
173    // to place k stars and therefore n - 1 bars.
174    // Example (n=4, k=6): ***|*||** represents [0,0,0,1,3,3].
175    let count = |n: usize, k: usize| {
176        let positions = if n == 0 {
177            k.saturating_sub(1)
178        } else {
179            (n - 1).checked_add(k)?
180        };
181        checked_binomial(positions, k)
182    };
183    let k = indices.len();
184    if first {
185        count(n, k)
186    } else {
187        // The algorithm is similar to the one for combinations *without replacement*,
188        // except we choose values *with replacement* and indices are *non-strictly* monotonically sorted.
189
190        // The combinations generated after the current one can be counted by counting as follows:
191        // - The subsequent combinations that differ in indices[0]:
192        //   If subsequent combinations differ in indices[0], then their value for indices[0]
193        //   must be at least 1 greater than the current indices[0].
194        //   As indices is monotonically sorted, this means we can effectively choose k values with
195        //   replacement from (n - 1 - indices[0]), leading to count(n - 1 - indices[0], k) possibilities.
196        // - The subsequent combinations with same indices[0], but differing indices[1]:
197        //   Here we can choose k - 1 values with replacement from (n - 1 - indices[1]) values,
198        //   leading to count(n - 1 - indices[1], k - 1) possibilities.
199        // - (...)
200        // - The subsequent combinations with same indices[0..=i], but differing indices[i]:
201        //   Here we can choose k - i values with replacement from (n - 1 - indices[i]) values: count(n - 1 - indices[i], k - i).
202        //   Since subsequent combinations can in any index, we must sum up the aforementioned binomial coefficients.
203
204        // Below, `n0` resembles indices[i].
205        indices.iter().enumerate().try_fold(0usize, |sum, (i, n0)| {
206            sum.checked_add(count(n - 1 - *n0, k - i)?)
207        })
208    }
209}