permutation-generator 0.1.3

A direct permutation generator
Documentation
use crate::{PResult, PermutationGeneratorError, SinglePermutation16, factorial::factorial64};

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PermutationGenerator16Gen {
    nb_elems: u8,
    nb_perms: u64,
    next_idx: u64,
}

impl PermutationGenerator16Gen {
    const MAX_ELEMENTS: u8 = 16;

    pub fn new(nb_elems: u8) -> PResult<Self> {
        Self::check_nb_elems(nb_elems)?;
        Ok(Self {
            next_idx: 0,
            nb_perms: factorial64(nb_elems),
            nb_elems,
        })
    }

    pub fn next_permutation(&mut self) -> Option<impl Iterator<Item = u8>> { self.nth(0) }

    pub fn nth_absolute(nb_elems: u8, idx: u64) -> PResult<Option<impl Iterator<Item = u8>>> {
        Self::check_nb_elems(nb_elems)?;
        Ok(SinglePermutation16::new(nb_elems, factorial64(nb_elems), idx))
    }

    pub fn nth(&mut self, step: u64) -> Option<impl Iterator<Item = u8>> {
        let step_result = self.next_idx.saturating_add(step);
        let res = SinglePermutation16::new(self.nb_elems, self.nb_perms, step_result);
        self.next_idx = step_result + 1;
        res
    }

    #[must_use]
    #[allow(clippy::cast_possible_truncation)]
    pub fn nb_remaining(&self) -> usize { (self.nb_perms - self.next_idx) as usize }

    #[inline]
    fn check_nb_elems(nb_elems: u8) -> PResult<()> {
        if nb_elems > Self::MAX_ELEMENTS {
            Err(PermutationGeneratorError::TooManyElements)
        } else {
            Ok(())
        }
    }
}

impl Iterator for PermutationGenerator16Gen {
    type Item = impl Iterator<Item = u8>;

    fn next(&mut self) -> Option<Self::Item> { self.next_permutation() }

    fn size_hint(&self) -> (usize, Option<usize>) {
        let nb_remaining = self.nb_remaining();
        (nb_remaining, Some(nb_remaining))
    }

    fn count(self) -> usize { self.nb_remaining() }
}

#[cfg(test)]
mod tests {
    use super::PermutationGenerator16Gen;
    use crate::factorial::factorial64;

    const NB_ELEMS: u8 = 9;

    fn test_slice(ref_slice: &[u8], some_iter: Option<impl Iterator<Item = u8>>) {
        assert_eq!(ref_slice, some_iter.unwrap().collect::<Vec<_>>().as_slice());
    }

    #[test]
    fn zero() {
        let mut pg = PermutationGenerator16Gen::new(0).unwrap();
        assert!(pg.next_permutation().is_none());
    }

    #[test]
    fn next_permutation() {
        let mut pg = PermutationGenerator16Gen::new(NB_ELEMS).unwrap();
        test_slice(&[0, 1, 2, 3, 4, 5, 6, 7, 8], pg.next_permutation());
        test_slice(&[0, 1, 2, 3, 4, 5, 6, 8, 7], pg.next_permutation());
    }

    #[test]
    fn nth_absolute() {
        test_slice(
            &[0, 1, 2, 3, 4, 5, 6, 7, 8],
            PermutationGenerator16Gen::nth_absolute(NB_ELEMS, 0).unwrap(),
        );
        test_slice(
            &[8, 7, 6, 5, 4, 3, 2, 1, 0],
            PermutationGenerator16Gen::nth_absolute(NB_ELEMS, factorial64(NB_ELEMS) - 1).unwrap(),
        );
        test_slice(
            &[1, 0, 2, 3, 4, 5, 6, 7, 8],
            PermutationGenerator16Gen::nth_absolute(NB_ELEMS, factorial64(NB_ELEMS - 1)).unwrap(),
        );
    }

    #[test]
    fn nth() {
        let mut pg = PermutationGenerator16Gen::new(NB_ELEMS).unwrap();
        test_slice(&[8, 7, 6, 5, 4, 3, 2, 1, 0], pg.nth(factorial64(NB_ELEMS) - 1));
        assert!(pg.next_permutation().is_none());

        let mut pg = PermutationGenerator16Gen::new(NB_ELEMS).unwrap();
        test_slice(&[0, 1, 2, 3, 4, 5, 6, 7, 8], pg.nth(0));
        test_slice(&[1, 0, 2, 3, 4, 5, 6, 7, 8], pg.nth(factorial64(NB_ELEMS - 1) - 1));
    }

    #[test]
    #[allow(clippy::cast_possible_truncation)]
    fn iter() {
        let iter = PermutationGenerator16Gen::new(NB_ELEMS).unwrap();
        assert_eq!(factorial64(NB_ELEMS) as usize, iter.count());
    }
}