Skip to content

Commit

Permalink
Draft: Const generic permutations
Browse files Browse the repository at this point in the history
  • Loading branch information
FlareFlo committed May 31, 2024
1 parent ad5cc96 commit 80fc48d
Show file tree
Hide file tree
Showing 2 changed files with 200 additions and 0 deletions.
12 changes: 12 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ pub use crate::either_or_both::EitherOrBoth;
pub mod free;
#[doc(inline)]
pub use crate::free::*;
use crate::permutations_const::PermutationsConst;

#[cfg(feature = "use_alloc")]
mod combinations;
#[cfg(feature = "use_alloc")]
Expand Down Expand Up @@ -233,6 +235,7 @@ mod with_position;
mod zip_eq_impl;
mod zip_longest;
mod ziptuple;
mod permutations_const;

#[macro_export]
/// Create an iterator over the “cartesian product” of iterators.
Expand Down Expand Up @@ -1786,6 +1789,15 @@ pub trait Itertools: Iterator {
permutations::permutations(self, k)
}

#[cfg(feature = "use_alloc")]
fn permutations_const<const K: usize>(self) -> PermutationsConst<Self, K>
where
Self: Sized,
Self::Item: Clone,
{
permutations_const::permutations_const(self)
}

Check warning on line 1799 in src/lib.rs

View check run for this annotation

Codecov / codecov/patch

src/lib.rs#L1793-L1799

Added lines #L1793 - L1799 were not covered by tests

/// Return an iterator that iterates through the powerset of the elements from an
/// iterator.
///
Expand Down
188 changes: 188 additions & 0 deletions src/permutations_const.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
use alloc::boxed::Box;
use alloc::vec::Vec;
use std::fmt;
use std::iter::once;
use std::iter::FusedIterator;
use std::convert::TryInto;

use super::lazy_buffer::LazyBuffer;
use crate::size_hint::{self, SizeHint};

/// An iterator adaptor that iterates through all the `k`-permutations of the
/// elements from an iterator.
///
/// See [`.permutations()`](crate::Itertools::permutations) for
/// more information.
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
pub struct PermutationsConst<I: Iterator, const K: usize> {
vals: LazyBuffer<I>,
state: PermutationState<K>,
}

impl<I, const K: usize> Clone for PermutationsConst<I, K>
where
I: Clone + Iterator,
I::Item: Clone,
{
clone_fields!(vals, state);
}

#[derive(Clone, Debug)]
enum PermutationState<const K: usize> {
/// No permutation generated yet.
Start,
/// Values from the iterator are not fully loaded yet so `n` is still unknown.
Buffered { min_n: usize },
/// All values from the iterator are known so `n` is known.
Loaded {
indices: Box<[usize]>,
cycles: Box<[usize]>,
},
/// No permutation left to generate.
End,
}

impl<I, const K: usize> fmt::Debug for PermutationsConst<I, K>
where
I: Iterator + fmt::Debug,
I::Item: fmt::Debug,
{
debug_fmt_fields!(Permutations, vals, state);
}

pub fn permutations_const<I: Iterator, const K: usize>(iter: I) -> PermutationsConst<I, K> {
PermutationsConst {
vals: LazyBuffer::new(iter),
state: PermutationState::Start,
}
}

Check warning on line 58 in src/permutations_const.rs

View check run for this annotation

Codecov / codecov/patch

src/permutations_const.rs#L53-L58

Added lines #L53 - L58 were not covered by tests

impl<I, const K: usize> Iterator for PermutationsConst<I, K>
where
I: Iterator,
I::Item: Clone + Default,
{
type Item = [I::Item; K];

fn next(&mut self) -> Option<Self::Item> {
let Self { vals, state } = self;
match state {

Check warning on line 69 in src/permutations_const.rs

View check run for this annotation

Codecov / codecov/patch

src/permutations_const.rs#L67-L69

Added lines #L67 - L69 were not covered by tests
PermutationState::Start => {
*state = PermutationState::End;
// TODO: Consider this case and handle it somehow, currently just using default
Some(std::array::from_fn(|_|I::Item::default()))

Check warning on line 73 in src/permutations_const.rs

View check run for this annotation

Codecov / codecov/patch

src/permutations_const.rs#L71-L73

Added lines #L71 - L73 were not covered by tests
}
&mut PermutationState::Start => {
vals.prefill(K);
if vals.len() != K {
*state = PermutationState::End;
return None;
}
*state = PermutationState::Buffered { min_n: K };
let mut iter = vals[0..K].into_iter().cloned();
Some(std::array::from_fn(|_|iter.next().unwrap())) // TODO: Handle error case, maybe make this better

Check warning on line 83 in src/permutations_const.rs

View check run for this annotation

Codecov / codecov/patch

src/permutations_const.rs#L83

Added line #L83 was not covered by tests
}
PermutationState::Buffered { min_n } => {
if vals.get_next() {
let mut item = (0..K - 1)
.chain(once(*min_n))
.map(|i| vals[i].clone());
*min_n += 1;
Some(std::array::from_fn(|_|item.next().unwrap()))

Check warning on line 91 in src/permutations_const.rs

View check run for this annotation

Codecov / codecov/patch

src/permutations_const.rs#L85-L91

Added lines #L85 - L91 were not covered by tests
} else {
let n = *min_n;
let prev_iteration_count = n - K + 1;
let mut indices: Box<[_]> = (0..n).collect();
let mut cycles: Box<[_]> = (n - K..n).rev().collect();
// Advance the state to the correct point.
for _ in 0..prev_iteration_count {
if advance(&mut indices, &mut cycles) {
*state = PermutationState::End;
return None;
}

Check warning on line 102 in src/permutations_const.rs

View check run for this annotation

Codecov / codecov/patch

src/permutations_const.rs#L93-L102

Added lines #L93 - L102 were not covered by tests
}
let item = vals.get_at(&indices[0..K]); // TODO: Impl const sized variant otherwise this is pointless
*state = PermutationState::Loaded { indices, cycles };
Some(item.try_into().ok()?) // TODO: Handle error case

Check warning on line 106 in src/permutations_const.rs

View check run for this annotation

Codecov / codecov/patch

src/permutations_const.rs#L104-L106

Added lines #L104 - L106 were not covered by tests
}
}
PermutationState::Loaded { indices, cycles } => {
if advance(indices, cycles) {
*state = PermutationState::End;
return None;
}
let k = cycles.len();
Some(vals.get_at(&indices[0..k]).try_into().ok()?) // TODO: Handle error case and const size indexing

Check warning on line 115 in src/permutations_const.rs

View check run for this annotation

Codecov / codecov/patch

src/permutations_const.rs#L109-L115

Added lines #L109 - L115 were not covered by tests
}
PermutationState::End => None,

Check warning on line 117 in src/permutations_const.rs

View check run for this annotation

Codecov / codecov/patch

src/permutations_const.rs#L117

Added line #L117 was not covered by tests
}
}

Check warning on line 119 in src/permutations_const.rs

View check run for this annotation

Codecov / codecov/patch

src/permutations_const.rs#L119

Added line #L119 was not covered by tests

fn count(self) -> usize {
let Self { vals, state } = self;
let n = vals.count();
state.size_hint_for(n).1.unwrap()
}

Check warning on line 125 in src/permutations_const.rs

View check run for this annotation

Codecov / codecov/patch

src/permutations_const.rs#L121-L125

Added lines #L121 - L125 were not covered by tests

fn size_hint(&self) -> SizeHint {
let (mut low, mut upp) = self.vals.size_hint();
low = self.state.size_hint_for(low).0;
upp = upp.and_then(|n| self.state.size_hint_for(n).1);
(low, upp)
}

Check warning on line 132 in src/permutations_const.rs

View check run for this annotation

Codecov / codecov/patch

src/permutations_const.rs#L127-L132

Added lines #L127 - L132 were not covered by tests
}

impl<I, const K: usize> FusedIterator for PermutationsConst<I, K>
where
I: Iterator,
I::Item: Clone + Default,
{
}

fn advance(indices: &mut [usize], cycles: &mut [usize]) -> bool {
let n = indices.len();
let k = cycles.len();

Check warning on line 144 in src/permutations_const.rs

View check run for this annotation

Codecov / codecov/patch

src/permutations_const.rs#L142-L144

Added lines #L142 - L144 were not covered by tests
// NOTE: if `cycles` are only zeros, then we reached the last permutation.
for i in (0..k).rev() {
if cycles[i] == 0 {
cycles[i] = n - i - 1;
indices[i..].rotate_left(1);
} else {
let swap_index = n - cycles[i];
indices.swap(i, swap_index);
cycles[i] -= 1;
return false;

Check warning on line 154 in src/permutations_const.rs

View check run for this annotation

Codecov / codecov/patch

src/permutations_const.rs#L146-L154

Added lines #L146 - L154 were not covered by tests
}
}
true
}

Check warning on line 158 in src/permutations_const.rs

View check run for this annotation

Codecov / codecov/patch

src/permutations_const.rs#L157-L158

Added lines #L157 - L158 were not covered by tests

impl <const K: usize>PermutationState<K> {
fn size_hint_for(&self, n: usize) -> SizeHint {
// At the beginning, there are `n!/(n-k)!` items to come.
let at_start = |n, k| {
debug_assert!(n >= k);
let total = (n - k + 1..=n).try_fold(1usize, |acc, i| acc.checked_mul(i));
(total.unwrap_or(usize::MAX), total)
};
match *self {
Self::Start if n < K => (0, Some(0)),
Self::Start => at_start(n, K),
Self::Buffered { min_n } => {
// Same as `Start` minus the previously generated items.
size_hint::sub_scalar(at_start(n, K), min_n - K + 1)

Check warning on line 173 in src/permutations_const.rs

View check run for this annotation

Codecov / codecov/patch

src/permutations_const.rs#L161-L173

Added lines #L161 - L173 were not covered by tests
}
Self::Loaded {
ref indices,
ref cycles,
} => {
let count = cycles.iter().enumerate().try_fold(0usize, |acc, (i, &c)| {
acc.checked_mul(indices.len() - i)
.and_then(|count| count.checked_add(c))
});
(count.unwrap_or(usize::MAX), count)

Check warning on line 183 in src/permutations_const.rs

View check run for this annotation

Codecov / codecov/patch

src/permutations_const.rs#L176-L183

Added lines #L176 - L183 were not covered by tests
}
Self::End => (0, Some(0)),

Check warning on line 185 in src/permutations_const.rs

View check run for this annotation

Codecov / codecov/patch

src/permutations_const.rs#L185

Added line #L185 was not covered by tests
}
}

Check warning on line 187 in src/permutations_const.rs

View check run for this annotation

Codecov / codecov/patch

src/permutations_const.rs#L187

Added line #L187 was not covered by tests
}

0 comments on commit 80fc48d

Please sign in to comment.