aoc/util/slice.rs
1//! Extension methods for slices.
2//!
3//! # Methods
4//!
5//! [`permutations`]
6//!
7//! Generates all possible permutations of a mutable slice, passing them one at a time to a
8//! callback function.
9//! Uses [Heap's algorithm](https://en.wikipedia.org/wiki/Heap%27s_algorithm) for efficiency,
10//! modifying the slice in place.
11//!
12//! [`half_permutations`]
13//!
14//! Like `permuations`, but skip any permutation which is lexically reversed from an earlier
15//! callback. Only half the permutations are visited in total.
16//! Uses [Steinhaus-Johnson-Trotter's algorithm](https://en.wikipedia.org/wiki/Steinhaus%E2%80%93Johnson%E2%80%93Trotter_algorithm),
17//! modifying the slice in place.
18//!
19//! [`permutations`]: SliceOps::permutations
20//! [`half_permutations`]: SliceOps::half_permutations
21pub trait SliceOps<T> {
22 fn permutations(self, callback: impl FnMut(&[T]));
23 fn half_permutations(self, callback: impl FnMut(&[T]));
24}
25
26impl<T> SliceOps<T> for &mut [T] {
27 fn permutations(self, mut callback: impl FnMut(&[T])) {
28 callback(self);
29
30 let n = self.len();
31 let mut c = vec![0; n];
32 let mut i = 1;
33
34 while i < n {
35 if c[i] < i {
36 let swap_index = if i.is_multiple_of(2) { 0 } else { c[i] };
37 self.swap(swap_index, i);
38 callback(self);
39 c[i] += 1;
40 i = 1;
41 } else {
42 c[i] = 0;
43 i += 1;
44 }
45 }
46 }
47
48 fn half_permutations(self, mut callback: impl FnMut(&[T])) {
49 let n = self.len();
50 // Compute n!/2, the number of iterations we need.
51 let limit = (1..n + 1).product::<usize>() / 2;
52
53 // Track how far each element has moved from its original position
54 let mut pos = vec![0_usize; n];
55
56 // Track which direction an element needs to move next (-1 or 1)
57 let mut dir = vec![1_isize; n];
58
59 for _ in 0..limit {
60 callback(self);
61
62 // Each iteration requires scanning up to n positions to locate a candidate to swap
63 // with its neighbor, by moving left and right towards one another and updating pos and
64 // dir along the way.
65 let mut left = 0;
66 let mut right = n - 1;
67
68 loop {
69 pos[right] = pos[right].wrapping_add_signed(dir[right]);
70 if right + 1 == pos[right] {
71 dir[right] = -1;
72 if right > 1 {
73 right -= 1;
74 } else {
75 self.swap(left, left + 1);
76 break;
77 }
78 } else if pos[right] == 0 {
79 dir[right] = 1;
80 left += 1;
81 if right > 1 {
82 right -= 1;
83 } else {
84 self.swap(left, left + 1);
85 break;
86 }
87 } else {
88 let index = left + pos[right] - 1;
89 self.swap(index, index + 1);
90 break;
91 }
92 }
93 }
94 }
95}