I too think this isn't a great interface as far as the example goes. But anyway, we can abstract the question to be "what's a good API for constructing a permutation of a fixed size?" The metrics you've described are "infallible construction from the inputs" and "no factorially growing enums as inputs".
Thinking aloud...
Every permutation can be decomposed into a sequence of swaps, so one way would be to take an iterator of valid swaps ((i, j)
where i < j
). That's n * (n - 1) / 2
possibilities for size n
; going from size k
to k+1
introduces k
new possibilities. You could nest these, but the code would be gnarly. So probably you'd just make an enum
per size, and that's quadratic for a single size, but cubed if you need all smaller sizes.
Another possibility would be to take a Lehmer code in some form, like a tuple of (TwoOrLess, OneOrLess, Zero)
(or reversed; Zero
optional). Going from k
to k+1
again introduces k
new variants, but since they're used separately, you'd just introduce a new enum with n-1
variants. You always need all smaller sizes, but it is quadratic in total. In a sense you get the benefits of nesting the other suggestion for free. (If you nested this version, it would be linear, but again gnarly.)
Assuming you want a [uint; N]
permutation to come out, we might consider conversion cost. The number of transpositions needed or supplied may vary, but an optimal encoding will involve a linear number of swaps. The direct encoding/decoding of Lehmer codes is quadratic, but you could instead use a modified definition:
- Start with the identity permutation
- Largest radix digit is the index of the first element: swap first element with that index
- Second largest radix digit is the index of the second element out of the remaining elements
- Etc
In this modified form, the encoding can be seen as an alternative way to supply a sequence of transpositions.
So one way would be to have
#[derive(Copy, Clone)] #[repr(u8)] pub enum D1 { Zero, One, }
#[derive(Copy, Clone)] #[repr(u8)] pub enum D2 { Zero, One, Two, }
#[derive(Copy, Clone)] #[repr(u8)] pub enum D3 { Zero, One, Two, Three, }
And then to construct a permutation [u8; 4]
from a tuple = (D1, D2, D3)
you could start with the identity permutation = [0, 1, 2, 3]
and then
let indices = [tuple.2 as usize, tuple.1 as usize, tuple.0 as usize];
for (offset, idx) in indices.into_iter().enumerate() {
permutation.swap(offset, offset + idx);
}
Linear growth of variants for a quadratic total, bijection between inputs and outputs, linear construction.