-
Notifications
You must be signed in to change notification settings - Fork 2
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
Add permutations with length determined by a const parameter #2
base: main
Are you sure you want to change the base?
Conversation
impl<const N: usize> ConstPermutation<N> { | ||
/// Returns the identity permutation. | ||
pub fn identity() -> Self { | ||
ConstPermutation((0..N).collect::<Vec<_>>().try_into().unwrap()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be great if the const versions of these methods could avoid heap allocations and just allocate the array. E.g., here we could do something like
ConstPermutation((0..N).collect::<Vec<_>>().try_into().unwrap()) | |
let mut storage = [0; N]; | |
for i in 0..N { | |
storage[i] = i; | |
} | |
Self(storage) |
/// Returns an array permuted by this permutation. | ||
pub fn permute<T>(&self, a: &[T; N]) -> [T; N] | ||
where | ||
T: Clone + Debug, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need T: Debug
. Maybe this was left over from debugging?
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) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would you think of implementing MulAssign
for ConstPermutation
? Then I think this could be written iteratively using exponentiation by squaring to avoid creating multiple intermediate arrays — which, since they're stored on the stack, could result in a stack overflow much more quickly than with Box<[usize]>
in Permutation
.
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) | |
} | |
let mut result = Self::identity(); | |
let mut term = self.clone(); | |
while exp > 0 { | |
if exp % 2 == 1 { | |
result *= term; | |
} | |
term *= term; | |
exp /= 2; | |
} | |
return result; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And for what it's worth, MulAssign
could be implemented space-efficiently using permute_in_place
from #3.
impl<const N: usize> ops::MulAssign<ConstPermutation<N>> for ConstPermutation<N> {
fn mul_assign(&mut self, rhs: ConstPermutation<N>) {
rhs.permute_in_place(&mut self.0);
}
}
let mut map = [0; N]; | ||
for i in 0..N { | ||
map[i] = other.apply(self.apply(i)); | ||
} | ||
ConstPermutation(map) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this just permute
?
let mut map = [0; N]; | |
for i in 0..N { | |
map[i] = other.apply(self.apply(i)); | |
} | |
ConstPermutation(map) | |
ConstPermutation(other.permute(&self.0)) |
type Error = TryFromError; | ||
fn try_from(a: &[usize; N]) -> Result<ConstPermutation<N>, TryFromError> { | ||
if is_permutation(a) { | ||
Ok(ConstPermutation(*a)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huh, I actually had no idea that [T; N]: Copy
when T: Copy
.
/// 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::<Vec<_>>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this could just be
let mut seen = (0..n).map(|_| false).collect::<Vec<_>>(); | |
let mut seen = vec![false; n]; |
let n = v.len(); | ||
let mut seen = (0..n).map(|_| false).collect::<Vec<_>>(); | ||
for &e in v { | ||
if (0..n).contains(&e) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is an unsigned value, I think we can just check
if (0..n).contains(&e) { | |
if e < n { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Though see my other suggestion about early return.
if (0..n).contains(&e) { | ||
seen[e] = true; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, since this can't be a valid permutation if it contains an out-of-bounds index, we could return immediately.
if (0..n).contains(&e) { | |
seen[e] = true; | |
} | |
if e >= n { | |
return false; | |
} | |
seen[e] = true; |
use core::fmt::Debug; | ||
|
||
use std::ops; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should these use
s be joined into a single group?
ConstPermutation( | ||
(0..N) | ||
.map(|k| { | ||
if k == i { | ||
j | ||
} else if k == j { | ||
i | ||
} else { | ||
k | ||
} | ||
}) | ||
.collect::<Vec<_>>() | ||
.try_into() | ||
.unwrap(), | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we could reuse identity
to make this
ConstPermutation( | |
(0..N) | |
.map(|k| { | |
if k == i { | |
j | |
} else if k == j { | |
i | |
} else { | |
k | |
} | |
}) | |
.collect::<Vec<_>>() | |
.try_into() | |
.unwrap(), | |
) | |
let mut result = Self::identity(); | |
result.0.swap(i, j); | |
result |
No description provided.