permutation-generator 0.1.3

A direct permutation generator
Documentation
use crate::{PResult, PermutationGeneratorError, SinglePermutation8, factorial::factorial16};

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

impl PermutationGenerator8Gen {
    const MAX_ELEMENTS: u8 = 8;

    pub fn new(nb_elems: u8) -> PResult<Self> {
        Self::check_nb_elems(nb_elems)?;
        Ok(Self {
            next_idx: 0,
            nb_perms: factorial16(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: u16) -> PResult<Option<impl Iterator<Item = u8>>> {
        Self::check_nb_elems(nb_elems)?;
        Ok(SinglePermutation8::new_precalculated(nb_elems, factorial16(nb_elems), idx))
    }

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

    #[must_use]
    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 PermutationGenerator8Gen {
    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 std::iter::Iterator;

    use super::{PermutationGenerator8Gen, PermutationGeneratorError};
    use crate::factorial::factorial16;

    const NB_ELEMS: u8 = 4;

    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 new() {
        let pg = PermutationGenerator8Gen::new(14);
        assert_eq!(PermutationGeneratorError::TooManyElements, pg.unwrap_err());
    }

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

    #[test]
    fn next_permutation() {
        let mut pg = PermutationGenerator8Gen::new(NB_ELEMS).unwrap();
        test_slice(&[0, 1, 2, 3], pg.next_permutation());
        test_slice(&[0, 1, 3, 2], pg.next_permutation());

        let mut pg = PermutationGenerator8Gen::new(NB_ELEMS + 1).unwrap();
        test_slice(&[0, 1, 2, 3, 4], pg.next_permutation());
        test_slice(&[0, 1, 2, 4, 3], pg.next_permutation());
    }

    #[test]
    fn nth_absolute() {
        test_slice(&[0, 1, 2, 3], PermutationGenerator8Gen::nth_absolute(NB_ELEMS, 0).unwrap());
        test_slice(
            &[3, 2, 1, 0],
            PermutationGenerator8Gen::nth_absolute(NB_ELEMS, factorial16(NB_ELEMS) - 1).unwrap(),
        );
        test_slice(
            &[4, 3, 2, 1, 0],
            PermutationGenerator8Gen::nth_absolute(NB_ELEMS + 1, factorial16(NB_ELEMS + 1) - 1)
                .unwrap(),
        );
    }

    #[test]
    fn nth() {
        let mut pg = PermutationGenerator8Gen::new(NB_ELEMS).unwrap();
        test_slice(&[3, 2, 1, 0], pg.nth(factorial16(NB_ELEMS) - 1));
        assert!(pg.next_permutation().is_none());

        let mut pg = PermutationGenerator8Gen::new(NB_ELEMS).unwrap();
        test_slice(&[0, 1, 2, 3], pg.nth(0));
        test_slice(&[3, 2, 1, 0], pg.nth(factorial16(NB_ELEMS) - 2));
    }

    #[test]
    #[allow(clippy::cast_possible_truncation)]
    fn iter() {
        let list = PermutationGenerator8Gen::new(NB_ELEMS)
            .unwrap()
            .map(Iterator::collect::<Vec<_>>)
            .collect::<Vec<_>>();
        assert_eq!(factorial16(NB_ELEMS) as usize, list.len());
        assert_eq!(&[0, 1, 2, 3], list[0].as_slice());
        assert_eq!(&[3, 2, 1, 0], list[factorial16(NB_ELEMS) as usize - 1].as_slice());
    }
}