use alloc::vec::Vec;
use core::iter::{FusedIterator, Iterator};
#[derive(Clone)]
pub struct LazyCombinationGenerator<const K: usize> {
indices: [usize; K],
done: bool,
}
impl<const K: usize> LazyCombinationGenerator<K> {
pub fn new() -> Self {
Self {
indices: core::array::from_fn(|i| i),
done: false,
}
}
pub fn max_index(&self) -> Option<usize> {
self.indices.last().copied()
}
pub fn is_done(&self, item_count: usize) -> bool {
self.done || self.max_index() >= Some(item_count)
}
pub fn indices(&self) -> &[usize; K] {
&self.indices
}
pub fn step(&mut self) {
if K == 0 {
self.done = true;
} else {
let mut i = 0;
while i + 1 < K && self.indices[i] + 1 == self.indices[i + 1] {
self.indices[i] = i;
i += 1;
}
self.indices[i] += 1;
}
}
}
#[derive(Clone)]
struct State<const K: usize> {
gen: LazyCombinationGenerator<K>,
}
impl<const K: usize> State<K> {
fn new() -> Self {
Self {
gen: LazyCombinationGenerator::new(),
}
}
fn max_index(&self) -> Option<usize> {
self.gen.max_index()
}
fn get_and_step<'a, T, O, F>(&mut self, items: &'a [T], f: F) -> Option<[O; K]>
where
F: Fn(&'a T) -> O,
O: 'a,
{
if self.gen.is_done(items.len()) {
None
} else {
let indices = self.gen.indices();
let res = core::array::from_fn(|i| f(&items[indices[i]]));
self.gen.step();
Some(res)
}
}
}
#[derive(Clone)]
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
pub struct Combinations<I, const K: usize>
where
I: Iterator,
{
iter: I,
items: Vec<I::Item>,
state: State<K>,
}
impl<I, const K: usize> Combinations<I, K>
where
I: Iterator,
{
pub(crate) fn new(iter: I) -> Self {
Self {
iter,
items: Vec::new(),
state: State::new(),
}
}
}
impl<I, const K: usize> Iterator for Combinations<I, K>
where
I: Iterator,
I::Item: Clone,
{
type Item = [I::Item; K];
fn next(&mut self) -> Option<[I::Item; K]> {
if K > 0 {
let max_index = self.state.max_index().unwrap();
let missing_count = (max_index + 1).saturating_sub(self.items.len());
if missing_count > 0 {
self.items.extend(self.iter.by_ref().take(missing_count));
}
}
self.state.get_and_step(&self.items, |t| t.clone())
}
}
impl<I, const K: usize> FusedIterator for Combinations<I, K>
where
I: FusedIterator,
I::Item: Clone,
{
}
#[derive(Clone)]
#[must_use = "iterator does nothing unless consumed"]
pub struct SliceCombinations<'a, T, const K: usize> {
items: &'a [T],
state: State<K>,
}
impl<'a, T, const K: usize> SliceCombinations<'a, T, K> {
pub(crate) fn new(items: &'a [T]) -> Self {
Self {
items,
state: State::new(),
}
}
}
impl<'a, T, const K: usize> Iterator for SliceCombinations<'a, T, K> {
type Item = [&'a T; K];
fn next(&mut self) -> Option<[&'a T; K]> {
self.state.get_and_step(self.items, |t| t)
}
}
impl<T, const K: usize> FusedIterator for SliceCombinations<'_, T, K> {}
#[cfg(test)]
mod test {
use super::*;
use crate::IterExt;
use core::sync::atomic::{AtomicUsize, Ordering};
#[test]
fn order() {
let mut combinations = (1..6).combinations();
assert_eq!(combinations.next(), Some([1, 2, 3]));
assert_eq!(combinations.next(), Some([1, 2, 4]));
assert_eq!(combinations.next(), Some([1, 3, 4]));
assert_eq!(combinations.next(), Some([2, 3, 4]));
assert_eq!(combinations.next(), Some([1, 2, 5]));
assert_eq!(combinations.next(), Some([1, 3, 5]));
assert_eq!(combinations.next(), Some([2, 3, 5]));
assert_eq!(combinations.next(), Some([1, 4, 5]));
assert_eq!(combinations.next(), Some([2, 4, 5]));
assert_eq!(combinations.next(), Some([3, 4, 5]));
assert_eq!(combinations.next(), None);
assert_eq!(combinations.next(), None);
}
#[test]
fn none_on_size_too_big() {
let mut combinations = (1..2).combinations::<2>();
assert_eq!(combinations.next(), None);
assert_eq!(combinations.next(), None);
}
#[test]
fn empty_arr_on_n_zero() {
let mut combinations = (1..5).combinations();
assert_eq!(combinations.next(), Some([]));
assert_eq!(combinations.next(), None);
assert_eq!(combinations.next(), None);
}
#[test]
fn fused_propagation() {
let fused = [1, 2, 3].iter().fuse();
let combinations = fused.combinations::<2>();
fn is_fused<T: FusedIterator>(_: T) {}
is_fused(combinations);
}
#[test]
fn resume_after_none() {
struct ResumeIter<'l, 'a, T>
where
T: Copy,
{
items: &'a [T],
i: usize,
len: &'l AtomicUsize,
}
impl<T> Iterator for ResumeIter<'_, '_, T>
where
T: Copy,
{
type Item = T;
fn next(&mut self) -> Option<T> {
if self.i >= self.len.load(Ordering::SeqCst) {
None
} else {
self.i += 1;
Some(self.items[self.i - 1])
}
}
}
let len = AtomicUsize::new(0);
let mut combinations = ResumeIter {
items: &[1, 2, 3, 4],
len: &len,
i: 0,
}
.combinations();
assert_eq!(combinations.next(), None);
len.fetch_add(1, Ordering::SeqCst);
assert_eq!(combinations.next(), None);
len.fetch_add(1, Ordering::SeqCst);
assert_eq!(combinations.next(), None);
len.fetch_add(1, Ordering::SeqCst);
assert_eq!(combinations.next(), Some([1, 2, 3]));
assert_eq!(combinations.next(), None);
len.fetch_add(1, Ordering::SeqCst);
assert_eq!(combinations.next(), Some([1, 2, 4]));
assert_eq!(combinations.next(), Some([1, 3, 4]));
assert_eq!(combinations.next(), Some([2, 3, 4]));
assert_eq!(combinations.next(), None);
assert_eq!(combinations.next(), None);
}
}
#[cfg(test)]
mod slice_test {
use crate::SliceExt;
#[test]
fn order() {
let mut combinations = [1, 2, 3, 4, 5].combinations();
assert_eq!(combinations.next(), Some([&1, &2, &3]));
assert_eq!(combinations.next(), Some([&1, &2, &4]));
assert_eq!(combinations.next(), Some([&1, &3, &4]));
assert_eq!(combinations.next(), Some([&2, &3, &4]));
assert_eq!(combinations.next(), Some([&1, &2, &5]));
assert_eq!(combinations.next(), Some([&1, &3, &5]));
assert_eq!(combinations.next(), Some([&2, &3, &5]));
assert_eq!(combinations.next(), Some([&1, &4, &5]));
assert_eq!(combinations.next(), Some([&2, &4, &5]));
assert_eq!(combinations.next(), Some([&3, &4, &5]));
assert_eq!(combinations.next(), None);
assert_eq!(combinations.next(), None);
}
#[test]
fn none_on_size_too_big() {
let mut combinations = [1].combinations::<2>();
assert_eq!(combinations.next(), None);
assert_eq!(combinations.next(), None);
}
#[test]
fn empty_arr_on_n_zero() {
let mut combinations = [1, 2, 3, 4].combinations();
assert_eq!(combinations.next(), Some([]));
assert_eq!(combinations.next(), None);
assert_eq!(combinations.next(), None);
}
}