Skip to content

Permutation Algorithms #487

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

Merged
merged 3 commits into from
Apr 24, 2023
Merged
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
4 changes: 4 additions & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@
* [Mex](https://github.com/TheAlgorithms/Rust/blob/master/src/general/mex.rs)
* [Nqueens](https://github.com/TheAlgorithms/Rust/blob/master/src/general/nqueens.rs)
* [Two Sum](https://github.com/TheAlgorithms/Rust/blob/master/src/general/two_sum.rs)
* Permutations
* [Naive Implementation](https://github.com/TheAlgorithms/Rust/blob/master/src/general/permutations/naive.rs)
* [Heap's algorithm](https://github.com/TheAlgorithms/Rust/blob/master/src/general/permutations/heap.rs)
* [Steinhaus-Johnson-Trotter algorithm](https://github.com/TheAlgorithms/Rust/blob/master/src/general/permutations/steinhaus-johnson-trotter.rs)
* Geometry
* [Closest Points](https://github.com/TheAlgorithms/Rust/blob/master/src/geometry/closest_points.rs)
* Graph
Expand Down
5 changes: 5 additions & 0 deletions src/general/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ mod huffman_encoding;
mod kmeans;
mod mex;
mod nqueens;
mod permutations;
mod two_sum;

pub use self::convex_hull::convex_hull_graham;
pub use self::fisher_yates_shuffle::fisher_yates_shuffle;
pub use self::hanoi::hanoi;
Expand All @@ -15,4 +17,7 @@ pub use self::kmeans::f64::kmeans as kmeans_f64;
pub use self::mex::mex_using_set;
pub use self::mex::mex_using_sort;
pub use self::nqueens::nqueens;
pub use self::permutations::{
heap_permute, permute, permute_unique, steinhaus_johnson_trotter_permute,
};
pub use self::two_sum::two_sum;
66 changes: 66 additions & 0 deletions src/general/permutations/heap.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
use std::fmt::Debug;

/// Computes all permutations of an array using Heap's algorithm
/// Read `recurse_naive` first, since we're building on top of the same intuition
pub fn heap_permute<T: Clone + Debug>(arr: &[T]) -> Vec<Vec<T>> {
if arr.is_empty() {
return vec![vec![]];
}
let n = arr.len();
let mut collector = Vec::with_capacity((1..=n).product()); // collects the permuted arrays
let mut arr = arr.to_owned(); // Heap's algorithm needs to mutate the array
heap_recurse(&mut arr, n, &mut collector);
collector
}

fn heap_recurse<T: Clone + Debug>(arr: &mut [T], k: usize, collector: &mut Vec<Vec<T>>) {
if k == 1 {
// same base-case as in the naive version
collector.push((*arr).to_owned());
return;
}
// Remember the naive recursion. We did the following: swap(i, last), recurse, swap back(i, last)
// Heap's algorithm has a more clever way of permuting the elements so that we never need to swap back!
for i in 0..k {
// now deal with [a, b]
let swap_idx = if k % 2 == 0 { i } else { 0 };
arr.swap(swap_idx, k - 1);
heap_recurse(arr, k - 1, collector);
}
}

#[cfg(test)]
mod tests {
use quickcheck_macros::quickcheck;

use crate::general::permutations::heap_permute;
use crate::general::permutations::tests::{
assert_permutations, assert_valid_permutation, NotTooBigVec,
};

#[test]
fn test_3_different_values() {
let original = vec![1, 2, 3];
let res = heap_permute(&original);
assert_eq!(res.len(), 6); // 3!
for permut in res {
assert_valid_permutation(&original, &permut)
}
}

#[test]
fn test_3_times_the_same_value() {
let original = vec![1, 1, 1];
let res = heap_permute(&original);
assert_eq!(res.len(), 6); // 3!
for permut in res {
assert_valid_permutation(&original, &permut)
}
}

#[quickcheck]
fn test_some_elements(NotTooBigVec { inner: original }: NotTooBigVec) {
let permutations = heap_permute(&original);
assert_permutations(&original, &permutations)
}
}
96 changes: 96 additions & 0 deletions src/general/permutations/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
mod heap;
mod naive;
mod steinhaus_johnson_trotter;

pub use self::heap::heap_permute;
pub use self::naive::{permute, permute_unique};
pub use self::steinhaus_johnson_trotter::steinhaus_johnson_trotter_permute;

#[cfg(test)]
mod tests {
use quickcheck::{Arbitrary, Gen};
use std::collections::HashMap;

pub(crate) fn assert_permutations(original: &[i32], permutations: &[Vec<i32>]) {
if original.is_empty() {
assert_eq!(vec![vec![] as Vec<i32>], permutations);
return;
}
let n = original.len();
assert_eq!((1..=n).product::<usize>(), permutations.len()); // n!
for permut in permutations {
assert_valid_permutation(original, permut);
}
}

pub(crate) fn assert_valid_permutation(original: &[i32], permuted: &[i32]) {
assert_eq!(original.len(), permuted.len());
let mut indices = HashMap::with_capacity(original.len());
for value in original {
*indices.entry(*value).or_insert(0) += 1;
}
for permut_value in permuted {
let count = indices.get_mut(permut_value).unwrap_or_else(|| {
panic!(
"Value {} appears too many times in permutation",
permut_value,
)
});
*count -= 1; // use this value
if *count == 0 {
indices.remove(permut_value); // so that we can simply check every value has been removed properly
}
}
assert!(indices.is_empty())
}

#[test]
fn test_valid_permutations() {
assert_valid_permutation(&[1, 2, 3], &[1, 2, 3]);
assert_valid_permutation(&[1, 2, 3], &[1, 3, 2]);
assert_valid_permutation(&[1, 2, 3], &[2, 1, 3]);
assert_valid_permutation(&[1, 2, 3], &[2, 3, 1]);
assert_valid_permutation(&[1, 2, 3], &[3, 1, 2]);
assert_valid_permutation(&[1, 2, 3], &[3, 2, 1]);
}

#[test]
#[should_panic]
fn test_invalid_permutation_1() {
assert_valid_permutation(&[1, 2, 3], &[4, 2, 3]);
}

#[test]
#[should_panic]
fn test_invalid_permutation_2() {
assert_valid_permutation(&[1, 2, 3], &[1, 4, 3]);
}

#[test]
#[should_panic]
fn test_invalid_permutation_3() {
assert_valid_permutation(&[1, 2, 3], &[1, 2, 4]);
}

#[test]
#[should_panic]
fn test_invalid_permutation_repeat() {
assert_valid_permutation(&[1, 2, 3], &[1, 2, 2]);
}

/// A Data Structure for testing permutations
/// Holds a Vec<i32> with just a few items, so that it's not too long to compute permutations
#[derive(Debug, Clone)]
pub(crate) struct NotTooBigVec {
pub(crate) inner: Vec<i32>, // opaque type alias so that we can implement Arbitrary
}

const MAX_SIZE: usize = 8; // 8! ~= 40k permutations already
impl Arbitrary for NotTooBigVec {
fn arbitrary(g: &mut Gen) -> Self {
let size = usize::arbitrary(g) % MAX_SIZE;
let res = (0..size).map(|_| i32::arbitrary(g)).collect();
NotTooBigVec { inner: res }
}
}
}
128 changes: 128 additions & 0 deletions src/general/permutations/naive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
use std::collections::HashSet;
use std::fmt::Debug;
use std::hash::Hash;

/// Here's a basic (naive) implementation for generating permutations
pub fn permute<T: Clone + Debug>(arr: &[T]) -> Vec<Vec<T>> {
if arr.is_empty() {
return vec![vec![]];
}
let n = arr.len();
let count = (1..=n).product(); // n! permutations
let mut collector = Vec::with_capacity(count); // collects the permuted arrays
let mut arr = arr.to_owned(); // we'll need to mutate the array

// the idea is the following: imagine [a, b, c]
// always swap an item with the last item, then generate all permutations from the first k characters
// permute_recurse(arr, k - 1, collector); // leave the last character alone, and permute the first k-1 characters
permute_recurse(&mut arr, n, &mut collector);
collector
}

fn permute_recurse<T: Clone + Debug>(arr: &mut Vec<T>, k: usize, collector: &mut Vec<Vec<T>>) {
if k == 1 {
collector.push(arr.to_owned());
return;
}
for i in 0..k {
arr.swap(i, k - 1); // swap i with the last character
permute_recurse(arr, k - 1, collector); // collect the permutations of the rest
arr.swap(i, k - 1); // swap back to original
}
}

/// A common variation of generating permutations is to generate only unique permutations
/// Of course, we could use the version above together with a Set as collector instead of a Vec.
/// But let's try something different: how can we avoid to generate duplicated permutations in the first place, can we tweak the algorithm above?
pub fn permute_unique<T: Clone + Debug + Eq + Hash + Copy>(arr: &[T]) -> Vec<Vec<T>> {
if arr.is_empty() {
return vec![vec![]];
}
let n = arr.len();
let count = (1..=n).product(); // n! permutations
let mut collector = Vec::with_capacity(count); // collects the permuted arrays
let mut arr = arr.to_owned(); // Heap's algorithm needs to mutate the array
permute_recurse_unique(&mut arr, n, &mut collector);
collector
}

fn permute_recurse_unique<T: Clone + Debug + Eq + Hash + Copy>(
arr: &mut Vec<T>,
k: usize,
collector: &mut Vec<Vec<T>>,
) {
// We have the same base-case as previously, whenever we reach the first element in the array, collect the result
if k == 1 {
collector.push(arr.to_owned());
return;
}
// We'll keep the same idea (swap with last item, and generate all permutations for the first k - 1)
// But we'll have to be careful though: how would we generate duplicates?
// Basically if, when swapping i with k-1, we generate the exact same array as in a previous iteration
// Imagine [a, a, b]
// i = 0:
// Swap (a, b) => [b, a, a], fix 'a' as last, and generate all permutations of [b, a] => [b, a, a], [a, b, a]
// Swap Back to [a, a, b]
// i = 1:
// Swap(a, b) => [b, a, a], we've done that already!!
let mut swapped = HashSet::with_capacity(k);
for i in 0..k {
if swapped.contains(&arr[i]) {
continue;
}
swapped.insert(arr[i]);
arr.swap(i, k - 1); // swap i with the last character
permute_recurse_unique(arr, k - 1, collector); // collect the permutations
arr.swap(i, k - 1); // go back to original
}
}

#[cfg(test)]
mod tests {
use crate::general::permutations::naive::{permute, permute_unique};
use crate::general::permutations::tests::{
assert_permutations, assert_valid_permutation, NotTooBigVec,
};
use quickcheck_macros::quickcheck;
use std::collections::HashSet;

#[test]
fn test_3_different_values() {
let original = vec![1, 2, 3];
let res = permute(&original);
assert_eq!(res.len(), 6); // 3!
for permut in res {
assert_valid_permutation(&original, &permut)
}
}

#[test]
fn test_3_times_the_same_value() {
let original = vec![1, 1, 1];
let res = permute(&original);
assert_eq!(res.len(), 6); // 3!
for permut in res {
assert_valid_permutation(&original, &permut)
}
}

#[quickcheck]
fn test_some_elements(NotTooBigVec { inner: original }: NotTooBigVec) {
let permutations = permute(&original);
assert_permutations(&original, &permutations)
}

#[test]
fn test_unique_values() {
let original = vec![1, 1, 2, 2];
let unique_permutations = permute_unique(&original);
let every_permutation = permute(&original);
for unique_permutation in &unique_permutations {
assert!(every_permutation.contains(unique_permutation));
}
assert_eq!(
unique_permutations.len(),
every_permutation.iter().collect::<HashSet<_>>().len()
)
}
}
61 changes: 61 additions & 0 deletions src/general/permutations/steinhaus_johnson_trotter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/// https://en.wikipedia.org/wiki/Steinhaus%E2%80%93Johnson%E2%80%93Trotter_algorithm
pub fn steinhaus_johnson_trotter_permute<T: Clone>(array: &[T]) -> Vec<Vec<T>> {
let len = array.len();
let mut array = array.to_owned();
let mut inversion_vector = vec![0; len];
let mut i = 1;
let mut res = Vec::with_capacity((1..=len).product());
res.push(array.clone());
while i < len {
if inversion_vector[i] < i {
if i % 2 == 0 {
array.swap(0, i);
} else {
array.swap(inversion_vector[i], i);
}
res.push(array.to_vec());
inversion_vector[i] += 1;
i = 1;
} else {
inversion_vector[i] = 0;
i += 1;
}
}
res
}

#[cfg(test)]
mod tests {
use quickcheck_macros::quickcheck;

use crate::general::permutations::steinhaus_johnson_trotter::steinhaus_johnson_trotter_permute;
use crate::general::permutations::tests::{
assert_permutations, assert_valid_permutation, NotTooBigVec,
};

#[test]
fn test_3_different_values() {
let original = vec![1, 2, 3];
let res = steinhaus_johnson_trotter_permute(&original);
assert_eq!(res.len(), 6); // 3!
for permut in res {
assert_valid_permutation(&original, &permut)
}
}

#[test]
fn test_3_times_the_same_value() {
let original = vec![1, 1, 1];
let res = steinhaus_johnson_trotter_permute(&original);
assert_eq!(res.len(), 6); // 3!
for permut in res {
assert_valid_permutation(&original, &permut)
}
}

#[quickcheck]
fn test_some_elements(NotTooBigVec { inner: original }: NotTooBigVec) {
let permutations = steinhaus_johnson_trotter_permute(&original);
assert_permutations(&original, &permutations)
}
}