diff --git a/src/comb.rs b/src/comb.rs new file mode 100644 index 0000000..998a2f1 --- /dev/null +++ b/src/comb.rs @@ -0,0 +1,149 @@ +//! Combinatorics. + +pub fn vectorize(cardinalities: &[usize], combination: u64, ordinals: &mut [usize]) { + let mut residual = combination; + for (index, &cardinality) in cardinalities.iter().enumerate() { + let cardinality = cardinality as u64; + let (quotient, remainder) = (residual / cardinality, residual % cardinality); + residual = quotient; + ordinals[index] = remainder as usize; + } +} + +pub fn combinations(cardinalities: &[usize]) -> u64 { + cardinalities.iter().product::() as u64 +} + +pub struct Combinator<'a> { + cardinalities: &'a [usize], + combinations: u64, +} +impl<'a> Combinator<'a> { + pub fn new(cardinalities: &'a [usize]) -> Self { + let combinations = combinations(cardinalities); + Self { + cardinalities, + combinations, + } + } +} + +impl<'a> IntoIterator for Combinator<'a> { + type Item = Vec; + type IntoIter = Iter<'a>; + + fn into_iter(self) -> Self::IntoIter { + Self::IntoIter { + combinator: self, + combination: 0, + } + } +} + +pub struct Iter<'a> { + combinator: Combinator<'a>, + combination: u64, +} +impl<'a> Iterator for Iter<'a> { + type Item = Vec; + + fn next(&mut self) -> Option { + if self.combination != self.combinator.combinations { + let mut ordinals = vec![0; self.combinator.cardinalities.len()]; + vectorize( + self.combinator.cardinalities, + self.combination, + &mut ordinals, + ); + self.combination += 1; + Some(ordinals) + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn vectorize_all() { + let cardinalities = &[2, 3, 4]; + let mut outputs = vec![]; + let combinations = combinations(cardinalities); + assert_eq!(24, combinations); + for combination in 0..combinations { + let mut ordinals = [0; 3]; + vectorize(cardinalities, combination, &mut ordinals); + outputs.push(ordinals.to_vec()); + println!("ordinals: {ordinals:?}"); + } + let expected_outputs = vec![ + [0, 0, 0], + [1, 0, 0], + [0, 1, 0], + [1, 1, 0], + [0, 2, 0], + [1, 2, 0], + [0, 0, 1], + [1, 0, 1], + [0, 1, 1], + [1, 1, 1], + [0, 2, 1], + [1, 2, 1], + [0, 0, 2], + [1, 0, 2], + [0, 1, 2], + [1, 1, 2], + [0, 2, 2], + [1, 2, 2], + [0, 0, 3], + [1, 0, 3], + [0, 1, 3], + [1, 1, 3], + [0, 2, 3], + [1, 2, 3], + ] + .iter() + .map(|array| array.to_vec()) + .collect::>(); + assert_eq!(expected_outputs, outputs); + } + + #[test] + fn iterator() { + let combinator = Combinator::new(&[2, 3, 4]); + let outputs = combinator.into_iter().collect::>(); + let expected_outputs = vec![ + [0, 0, 0], + [1, 0, 0], + [0, 1, 0], + [1, 1, 0], + [0, 2, 0], + [1, 2, 0], + [0, 0, 1], + [1, 0, 1], + [0, 1, 1], + [1, 1, 1], + [0, 2, 1], + [1, 2, 1], + [0, 0, 2], + [1, 0, 2], + [0, 1, 2], + [1, 1, 2], + [0, 2, 2], + [1, 2, 2], + [0, 0, 3], + [1, 0, 3], + [0, 1, 3], + [1, 1, 3], + [0, 2, 3], + [1, 2, 3], + ] + .iter() + .map(|array| array.to_vec()) + .collect::>(); + assert_eq!(expected_outputs, outputs); + } +} diff --git a/src/lib.rs b/src/lib.rs index 018da4d..25ddb7b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ #![allow(clippy::too_many_arguments)] pub mod capture; +pub mod comb; pub mod csv; pub mod data; pub mod display;