From 1819546ebd34584f4978e463722dfe8ab60980a7 Mon Sep 17 00:00:00 2001 From: Juergen Stuber Date: Sun, 4 Sep 2022 17:19:44 +0200 Subject: [PATCH] Add permutations with length determined by a const parameter --- src/const_permutation.rs | 440 +++++++++++++++++++++++++++++++++++++++ src/error.rs | 14 ++ src/lib.rs | 10 + src/permutation.rs | 33 +-- src/permutations.rs | 172 ++++++++++++--- src/util.rs | 15 ++ 6 files changed, 629 insertions(+), 55 deletions(-) create mode 100644 src/const_permutation.rs create mode 100644 src/error.rs create mode 100644 src/util.rs diff --git a/src/const_permutation.rs b/src/const_permutation.rs new file mode 100644 index 0000000..eae048d --- /dev/null +++ b/src/const_permutation.rs @@ -0,0 +1,440 @@ +use core::fmt::Debug; + +use std::ops; + +#[cfg(feature = "random")] +use rand::Rng; + +use crate::util::is_permutation; +#[cfg(feature = "random")] +use crate::Permutations; +use crate::TryFromError; + +/// A permutation with a constant length fixed at compile time. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ConstPermutation([usize; N]); +impl ConstPermutation { + /// Returns the identity permutation. + pub fn identity() -> Self { + ConstPermutation((0..N).collect::>().try_into().unwrap()) + } + /// Returns the permutation which rotates r steps to the left. + pub fn rotation_left(r: usize) -> Self { + ConstPermutation( + (0..N) + .map(|i| (i + r) % N) + .collect::>() + .try_into() + .unwrap(), + ) + } + /// Returns the permutation which rotates r steps to the right. + pub fn rotation_right(r: usize) -> Self { + ConstPermutation::rotation_left(N - (r % N)) + } + /// Returns the permutation which exchanges the elements at i and j. + pub fn transposition(i: usize, j: usize) -> Self { + assert!(i < N && j < N); + ConstPermutation( + (0..N) + .map(|k| { + if k == i { + j + } else if k == j { + i + } else { + k + } + }) + .collect::>() + .try_into() + .unwrap(), + ) + } + + /// Returns a random permutation. + /// + /// Uses a uniform distribution. + #[cfg(feature = "random")] + pub fn random(rng: &mut R) -> Self + where + R: Rng, + { + let ps = Permutations::new(N); + let i = rng.gen_range(0..ps.len()); + ps.get(i).expect("random index out of range") + } + /// Applies the permutation to an element. + pub fn apply(&self, i: usize) -> usize { + self.0[i] + } + /// Returns an array permuted by this permutation. + pub fn permute(&self, a: &[T; N]) -> [T; N] + where + T: Clone + Debug, + { + (0..N) + .map(|i| a[self.apply(i)].clone()) + .collect::>() + .try_into() + .unwrap() + } + /// Returns the composition of the permutation with itself. + pub fn square(&self) -> Self { + self * self + } + /// Returns the composition of the permutation with itself `exp` number of times. + pub fn pow(&self, exp: u32) -> Self { + if exp == 0 { + ConstPermutation::identity() + } else if exp == 1 { + self.clone() + } else if exp % 2 == 0 { + self.square().pow(exp / 2) + } else { + self * self.pow(exp - 1) + } + } + /// Returns the inverse permutation. + pub fn inv(&self) -> Self { + let mut map = [0; N]; + for i in 0..N { + let j = self.0[i]; + map[j] = i; + } + ConstPermutation(map) + } + /// The length of the permutation. + /// + /// That is, the length of its domain and range. + pub fn len(&self) -> usize { + self.0.len() + } + /// Returns true if this is the empty permutation. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + /// Returns true if the permutation is even. + pub fn is_even(&self) -> bool { + let mut even = true; + let mut seen = vec![false; self.len()]; + for i in 0..self.len() { + if !seen[i] { + seen[i] = true; + let mut j = self.apply(i); + while j != i { + seen[j] = true; + j = self.apply(j); + even = !even; + } + } + } + even + } + /// Returns true if the permutation is odd. + pub fn is_odd(&self) -> bool { + !self.is_even() + } + /// Returns the sign of a permutation. + pub fn sign(&self) -> isize { + if self.is_even() { + 1 + } else { + -1 + } + } +} +impl ops::Mul> for ConstPermutation { + type Output = ConstPermutation; + fn mul(self, other: ConstPermutation) -> Self::Output { + let mut map = [0; N]; + for i in 0..N { + map[i] = other.apply(self.apply(i)); + } + ConstPermutation(map) + } +} +impl ops::Mul> for &ConstPermutation { + type Output = ConstPermutation; + fn mul(self, other: ConstPermutation) -> Self::Output { + let mut map = [0; N]; + for i in 0..N { + map[i] = other.apply(self.apply(i)); + } + ConstPermutation(map) + } +} +impl ops::Mul<&ConstPermutation> for ConstPermutation { + type Output = ConstPermutation; + fn mul(self, other: &ConstPermutation) -> Self::Output { + let mut map = [0; N]; + for i in 0..N { + map[i] = other.apply(self.apply(i)); + } + ConstPermutation(map) + } +} +impl ops::Mul<&ConstPermutation> for &ConstPermutation { + type Output = ConstPermutation; + fn mul(self, other: &ConstPermutation) -> Self::Output { + let mut map = [0; N]; + for i in 0..N { + map[i] = other.apply(self.apply(i)); + } + ConstPermutation(map) + } +} +impl TryFrom> for ConstPermutation { + type Error = TryFromError; + fn try_from(v: Vec) -> Result, TryFromError> { + ConstPermutation::::try_from(&v[..]) + } +} +impl<'a, const N: usize> TryFrom<&'a Vec> for ConstPermutation { + type Error = TryFromError; + fn try_from(v: &'a Vec) -> Result, TryFromError> { + ConstPermutation::::try_from(&v[..]) + } +} +impl TryFrom<&[usize]> for ConstPermutation { + type Error = TryFromError; + fn try_from(a: &[usize]) -> Result, TryFromError> { + if a.len() == N && is_permutation(a) { + Ok(ConstPermutation(a.try_into().unwrap())) + } else { + Err(TryFromError) + } + } +} +impl TryFrom<&[usize; N]> for ConstPermutation { + type Error = TryFromError; + fn try_from(a: &[usize; N]) -> Result, TryFromError> { + if is_permutation(a) { + Ok(ConstPermutation(*a)) + } else { + Err(TryFromError) + } + } +} + +#[cfg(test)] +mod tests { + #[cfg(feature = "random")] + use rand::rngs::StdRng; + #[cfg(feature = "random")] + use rand::SeedableRng; + + use super::is_permutation; + use super::ConstPermutation; + use super::TryFromError; + + #[test] + fn test_is_permutation_true() { + assert_eq!(true, is_permutation(&vec![2, 1, 0])); + } + #[test] + fn test_is_permutation_false_missing_element() { + assert_eq!(false, is_permutation(&vec![0, 1, 1])); + } + #[test] + fn test_is_permutation_false_out_of_range() { + assert_eq!(false, is_permutation(&vec![2, 7, 1])); + } + + #[test] + fn test_identity() { + let id = ConstPermutation::<3>::identity(); + assert_eq!(ConstPermutation([0, 1, 2]), id); + } + + #[test] + fn test_rotation_left() { + let p = ConstPermutation::<3>::rotation_left(1); + assert_eq!(ConstPermutation([1, 2, 0]), p); + } + + #[test] + fn test_rotation_right() { + let p = ConstPermutation::<3>::rotation_right(1); + assert_eq!(ConstPermutation([2, 0, 1]), p); + } + + #[test] + fn test_transposition() { + let p = ConstPermutation::<3>::transposition(1, 2); + assert_eq!(ConstPermutation([0, 2, 1]), p); + } + + #[test] + fn test_apply() { + let p = ConstPermutation([0, 2, 1]); + assert_eq!(2, p.apply(1)); + } + + #[test] + fn test_permute() { + let p = ConstPermutation([0, 2, 1]); + assert_eq!(['a', 'c', 'b'], p.permute(&['a', 'b', 'c'])); + } + + #[test] + fn test_square() { + let p = ConstPermutation::<3>::rotation_left(1); + assert_eq!(ConstPermutation([2, 0, 1]), p.square()); + } + + #[test] + fn test_pow() { + let p = ConstPermutation::<3>::rotation_left(1); + assert_eq!(ConstPermutation::<3>::identity(), p.pow(3)); + } + + #[test] + fn test_inv() { + let p = ConstPermutation::<3>::rotation_left(1); + assert_eq!(ConstPermutation::<3>::rotation_right(1), p.inv()); + } + #[test] + fn test_inv_identity() { + let id = ConstPermutation::<3>::identity(); + assert_eq!(id, id.inv()); + } + + #[test] + fn test_len() { + let id = ConstPermutation::<3>::identity(); + assert_eq!(3, id.len()); + } + + #[test] + fn test_is_empty_true() { + let id = ConstPermutation::<0>::identity(); + assert_eq!(true, id.is_empty()); + } + #[test] + fn test_is_empty_false() { + let id = ConstPermutation::<3>::identity(); + assert_eq!(false, id.is_empty()); + } + + #[test] + fn test_is_even_identity() { + let id = ConstPermutation::<3>::identity(); + assert_eq!(true, id.is_even()); + } + #[test] + fn test_is_even_rotation() { + let p = ConstPermutation::<3>::rotation_left(1); + assert_eq!(true, p.is_even()); + } + #[test] + fn test_is_even_transposition() { + let p = ConstPermutation::<3>::transposition(0, 1); + assert_eq!(false, p.is_even()); + } + + #[test] + fn test_is_odd_identity() { + let id = ConstPermutation::<3>::identity(); + assert_eq!(false, id.is_odd()); + } + #[test] + fn test_is_odd_rotation() { + let p = ConstPermutation::<3>::rotation_left(1); + assert_eq!(false, p.is_odd()); + } + #[test] + fn test_is_odd_transposition() { + let p = ConstPermutation::<3>::transposition(0, 1); + assert_eq!(true, p.is_odd()); + } + + #[test] + fn test_sign_identity() { + let id = ConstPermutation::<3>::identity(); + assert_eq!(1, id.sign()); + } + #[test] + fn test_sign_rotation() { + let p = ConstPermutation::<3>::rotation_left(1); + assert_eq!(1, p.sign()); + } + #[test] + fn test_sign_transposition() { + let p = ConstPermutation::<3>::transposition(0, 1); + assert_eq!(-1, p.sign()); + } + + #[cfg(feature = "random")] + #[test] + fn test_random() { + let mut rng = StdRng::seed_from_u64(42); + let p = ConstPermutation([2, 3, 1, 4, 0]); + assert_eq!(p, ConstPermutation::<5>::random(&mut rng)); + } + + #[test] + fn test_mul_mm() { + let p0 = ConstPermutation::<3>::rotation_left(1); + let p1 = ConstPermutation::<3>::rotation_right(1); + assert_eq!(ConstPermutation::<3>::identity(), p0 * p1); + } + #[test] + fn test_mul_mr() { + let p0 = ConstPermutation::<3>::rotation_left(1); + let p1 = ConstPermutation::<3>::rotation_right(1); + assert_eq!(ConstPermutation::<3>::identity(), p0 * &p1); + } + #[test] + fn test_mul_rm() { + let p0 = ConstPermutation::<3>::rotation_left(1); + let p1 = ConstPermutation::<3>::rotation_right(1); + assert_eq!(ConstPermutation::<3>::identity(), &p0 * p1); + } + #[test] + fn test_mul_rr() { + let p0 = ConstPermutation::<3>::rotation_left(1); + let p1 = ConstPermutation::<3>::rotation_right(1); + assert_eq!(ConstPermutation::<3>::identity(), &p0 * &p1); + } + + #[test] + fn test_try_from_ok_owned() { + let v = vec![2, 1, 0]; + let result = Ok(ConstPermutation([2, 1, 0])); + assert_eq!(result, ConstPermutation::try_from(v)); + } + // #[test] + // fn test_try_from_ok_vec_ref() { + // let v = vec![2, 1, 0]; + // let result = Ok(ConstPermutation([2, 1, 0])); + // assert_eq!(result, ConstPermutation::try_from(&v)); + // } + #[test] + fn test_try_from_ok_slice_ref() { + let v = vec![2, 1, 0]; + let result = Ok(ConstPermutation([2, 1, 0])); + assert_eq!(result, ConstPermutation::try_from(&v[..])); + } + #[test] + fn test_try_from_err_missing_element_owned() { + assert_eq!( + Err(TryFromError), + ConstPermutation::<3>::try_from(vec![0, 1, 1]) + ); + } + #[test] + fn test_try_from_err_out_of_range_ref() { + assert_eq!( + Err(TryFromError), + ConstPermutation::<3>::try_from(&[2, 7, 1]) + ); + } + #[test] + fn test_try_from_err_wrong_length() { + assert_eq!( + Err(TryFromError), + ConstPermutation::<3>::try_from(vec![0, 1]) + ); + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..344664c --- /dev/null +++ b/src/error.rs @@ -0,0 +1,14 @@ +//! Errors returned by this library. + +use std::error; +use std::fmt; + +/// The error returned when a slice cannot be converted to a permutation. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct TryFromError; +impl fmt::Display for TryFromError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "ill-formed slice to permutation conversion attempted",) + } +} +impl error::Error for TryFromError {} diff --git a/src/lib.rs b/src/lib.rs index f545a1d..3f92c71 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,10 +27,20 @@ mod permutation; #[doc(inline)] pub use crate::permutation::Permutation; +mod const_permutation; +#[doc(inline)] +pub use crate::const_permutation::ConstPermutation; + mod permutations; #[doc(inline)] pub use crate::permutations::Permutations; +mod error; +#[doc(inline)] +pub use crate::error::TryFromError; + +mod util; + #[cfg(test)] mod tests { fn factorial(n: u128) -> u128 { diff --git a/src/permutation.rs b/src/permutation.rs index 020b73f..b2c8cfc 100644 --- a/src/permutation.rs +++ b/src/permutation.rs @@ -1,35 +1,12 @@ -use std::error; -use std::fmt; use std::ops; #[cfg(feature = "random")] use rand::Rng; +use crate::util::is_permutation; #[cfg(feature = "random")] use crate::Permutations; - -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct TryFromError; -impl fmt::Display for TryFromError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "ill-formed slice to permutation conversion attempted",) - } -} -impl error::Error for TryFromError {} - -/// Returns true if a slice is a permutation. -/// -/// That is, all the elements in `0..len` occur exactly once in the slice. -fn is_permutation(v: &[usize]) -> bool { - let n = v.len(); - let mut seen = (0..n).map(|_| false).collect::>(); - for &e in v { - if (0..n).contains(&e) { - seen[e] = true; - } - } - seen.into_iter().all(|b| b) -} +use crate::TryFromError; /// A permutation. #[derive(Clone, Debug, PartialEq, Eq)] @@ -41,7 +18,11 @@ impl Permutation { } /// Returns the permutation of n elements which rotates r steps to the left. pub fn rotation_left(n: usize, r: usize) -> Permutation { - Permutation((0..n).map(|i| (i + r) % n).collect::>()) + Permutation( + (0..n) + .map(|i| (i + r) % n) + .collect::>() + ) } /// Returns the permutation of n elements which rotates r steps to the right. pub fn rotation_right(n: usize, r: usize) -> Permutation { diff --git a/src/permutations.rs b/src/permutations.rs index e1af634..712c2e6 100644 --- a/src/permutations.rs +++ b/src/permutations.rs @@ -1,21 +1,38 @@ //! Iterators and indexed access to permutations. -use crate::Permutation; +use core::fmt::Debug; +use core::marker::PhantomData; /// The sequence of all permutations of `n` elements in lexicographic order. #[derive(Clone, Debug)] -pub struct Permutations { +pub struct Permutations

+where + P: Clone, + P: TryFrom>, +{ n: usize, len: usize, + _phantom_data: PhantomData

, } -impl Permutations { +impl

Permutations

+where + P: Clone, + P: TryFrom>, +{ /// Constructs the sequence of permutations of `n` elements. - pub fn new(n: usize) -> Permutations { + pub fn new(n: usize) -> Self { let len = (2..=n).product::(); - Permutations { n, len } + Permutations { + n, + len, + _phantom_data: PhantomData, + } } /// Returns the permutation at a given index. - pub fn get(&self, index: usize) -> Option { + pub fn get(&self, index: usize) -> Option

+ where +

>>::Error: Debug, + { if index < self.len { let mut v = Vec::new(); let mut es = (0..self.n).collect::>(); @@ -29,7 +46,7 @@ impl Permutations { i %= divisor; k -= 1; } - Some(Permutation::try_from(v).unwrap()) + Some(P::try_from(v).unwrap()) } else { None } @@ -43,32 +60,50 @@ impl Permutations { self.len } /// Returns an iterator over the permutations. - pub fn iter(&self) -> Iter { + pub fn iter(&self) -> Iter

{ Iter::new(self.clone()) } } -impl IntoIterator for Permutations { - type Item = Permutation; - type IntoIter = Iter; - fn into_iter(self) -> Iter { +impl

IntoIterator for Permutations

+where + P: Clone, + P: TryFrom>, +

>>::Error: Debug, +{ + type Item = P; + type IntoIter = Iter

; + fn into_iter(self) -> Iter

{ Iter::new(self) } } -impl<'a> IntoIterator for &'a Permutations { - type Item = Permutation; - type IntoIter = Iter; - fn into_iter(self) -> Iter { +impl<'a, P> IntoIterator for &'a Permutations

+where + P: Clone, + P: TryFrom>, +

>>::Error: Debug, +{ + type Item = P; + type IntoIter = Iter

; + fn into_iter(self) -> Iter

{ self.iter() } } #[derive(Clone, Debug)] -pub struct Iter { - permutations: Permutations, +pub struct Iter

+where + P: Clone, + P: TryFrom>, +{ + permutations: Permutations

, next_index: usize, } -impl Iter { - fn new(permutations: Permutations) -> Iter { +impl

Iter

+where + P: Clone, + P: TryFrom>, +{ + fn new(permutations: Permutations

) -> Iter

{ let next_index = 0; Iter { permutations, @@ -76,9 +111,14 @@ impl Iter { } } } -impl Iterator for Iter { - type Item = Permutation; - fn next(&mut self) -> Option { +impl

Iterator for Iter

+where + P: Clone, + P: TryFrom>, +

>>::Error: Debug, +{ + type Item = P; + fn next(&mut self) -> Option

{ if let Some(result) = self.permutations.get(self.next_index) { self.next_index += 1; Some(result) @@ -91,31 +131,38 @@ impl Iterator for Iter { (len, Some(len)) } } -impl ExactSizeIterator for Iter {} +impl

ExactSizeIterator for Iter

+where + P: Clone, + P: TryFrom>, +

>>::Error: Debug, +{ +} #[cfg(test)] mod tests { + use crate::ConstPermutation; use crate::Permutation; use crate::Permutations; #[test] fn test_len_0() { - let ps = Permutations::new(0); + let ps = Permutations::::new(0); assert_eq!(1, ps.len()); } #[test] fn test_len_1() { - let ps = Permutations::new(1); + let ps = Permutations::::new(1); assert_eq!(1, ps.len()); } #[test] fn test_len_2() { - let ps = Permutations::new(2); + let ps = Permutations::::new(2); assert_eq!(2, ps.len()); } #[test] fn test_len_3() { - let ps = Permutations::new(3); + let ps = Permutations::::new(3); assert_eq!(6, ps.len()); } @@ -226,10 +273,77 @@ mod tests { #[test] fn test_iter_exact_size_iterator_len() { - let ps = &Permutations::new(3); + let ps = &Permutations::::new(3); let mut iter = ps.into_iter(); assert_eq!(6, iter.len()); iter.next(); assert_eq!(5, iter.len()); } + + #[test] + fn test_len_0_const() { + let ps = Permutations::>::new(0); + assert_eq!(1, ps.len()); + } + + #[test] + fn test_get_const() { + let ps = Permutations::>::new(3); + assert_eq!( + Some(ConstPermutation::<3>::try_from(&[0, 1, 2]).unwrap()), + ps.get(0) + ); + assert_eq!( + Some(ConstPermutation::<3>::try_from(&[0, 2, 1]).unwrap()), + ps.get(1) + ); + assert_eq!( + Some(ConstPermutation::<3>::try_from(&[1, 0, 2]).unwrap()), + ps.get(2) + ); + assert_eq!( + Some(ConstPermutation::<3>::try_from(&[1, 2, 0]).unwrap()), + ps.get(3) + ); + assert_eq!( + Some(ConstPermutation::<3>::try_from(&[2, 0, 1]).unwrap()), + ps.get(4) + ); + assert_eq!( + Some(ConstPermutation::<3>::try_from(&[2, 1, 0]).unwrap()), + ps.get(5) + ); + assert_eq!(None, ps.get(6)); + } + + #[test] + fn test_iter_const() { + let ps = Permutations::>::new(3); + let mut iter = ps.iter(); + assert_eq!( + Some(ConstPermutation::<3>::try_from(&[0, 1, 2]).unwrap()), + iter.next() + ); + assert_eq!( + Some(ConstPermutation::<3>::try_from(&[0, 2, 1]).unwrap()), + iter.next() + ); + assert_eq!( + Some(ConstPermutation::<3>::try_from(&[1, 0, 2]).unwrap()), + iter.next() + ); + assert_eq!( + Some(ConstPermutation::<3>::try_from(&[1, 2, 0]).unwrap()), + iter.next() + ); + assert_eq!( + Some(ConstPermutation::<3>::try_from(&[2, 0, 1]).unwrap()), + iter.next() + ); + assert_eq!( + Some(ConstPermutation::<3>::try_from(&[2, 1, 0]).unwrap()), + iter.next() + ); + assert_eq!(None, ps.get(6)); + } } diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..43b2c3a --- /dev/null +++ b/src/util.rs @@ -0,0 +1,15 @@ +/// Utility functions. + +/// Returns true if a slice is a permutation. +/// +/// That is, all the elements in `0..len` occur exactly once in the slice. +pub fn is_permutation(v: &[usize]) -> bool { + let n = v.len(); + let mut seen = (0..n).map(|_| false).collect::>(); + for &e in v { + if (0..n).contains(&e) { + seen[e] = true; + } + } + seen.into_iter().all(|b| b) +}