Skip to content

Const generic permutations #954

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 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 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 @@
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
}
Comment on lines +70 to +74
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how to proceed about this, from what i can tell the API guarantees the vector to always be K elements long, but the reference returns an empty vec?

&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();
Comment on lines +104 to +114
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indexing into LazyBuffer would require a const-generic function also, otherwise this effort would be pointless

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
}
Loading