Skip to content

Instantly share code, notes, and snippets.

@ExpHP
Created July 12, 2018 16:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ExpHP/0603635aee995e524e255ca4444e7df0 to your computer and use it in GitHub Desktop.
Save ExpHP/0603635aee995e524e255ca4444e7df0 to your computer and use it in GitHub Desktop.
Perm and Permute
/* ************************************************************************ **
** This file is part of rsp2, and is licensed under EITHER the MIT license **
** or the Apache 2.0 license, at your option. **
** **
** http://www.apache.org/licenses/LICENSE-2.0 **
** http://opensource.org/licenses/MIT **
** **
** Be aware that not all of rsp2 is provided under this permissive license, **
** and that the project as a whole is licensed under the GPL 3.0. **
** ************************************************************************ */
use ::std::fmt;
#[cfg(feature = "frunk")]
use ::frunk::{HCons, HNil};
/// Represents a reordering operation on atoms.
///
/// See the [`Permute`] trait for more information.
///
/// [`Permute`]: trait.Permute.html
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Perm { // Perm<Src, Dest>
inv: PermVec, // PermVec<Dest, Src> or IndexBy<Src, Vec<Dest>>
}
// This is a Perm stored in the form that's easiest to reason about.
// (documented in `Perm::from_vec`)
//
// It exists solely for clarity of implementation, because implementing
// `Perm::shift_right` as `inv: self.inv.prefix_shift_left()` says it a lot
// better than any comment I could write above the inlined method body.
// Basically, method bodies on Perm describe the relationship between
// the perm and its inverse, while method bodies on PermVec do the real work.
#[derive(Clone, PartialEq, Eq, Hash)]
struct PermVec( // PermVec<Src, Dest>
Vec<usize>, // Indexed<Dest, Vec<Src>>
);
impl fmt::Debug for PermVec {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}
#[derive(Debug, Fail)]
#[fail(display = "Tried to construct an invalid permutation.")]
pub struct InvalidPermutationError(::failure::Backtrace);
impl Perm {
pub fn eye(n: usize) -> Perm
{ Perm { inv: PermVec::eye(n) } }
pub fn len(&self) -> usize
{ self.inv.0.len() }
/// Compute the `Perm` that, when applied to the input slice, would (stably) sort it.
pub fn argsort<T: Ord>(xs: &[T]) -> Perm
{ Perm { inv: PermVec::argsort(xs).inverted() } }
/// Construct a perm. Useful for literals in unit tests.
///
/// The representation accepted by this is comparable to indexing with an
/// integer array in numpy. If the `k`th element of the permutation vector
/// is `value`, then applying the permutation will *pull* the data at index
/// `value` into index `k`.
///
/// This performs O(n log n) validation on the data to verify that it
/// satisfies the invariants of Perm. It also inverts the perm (an O(n)
/// operation).
pub fn from_vec(vec: Vec<usize>) -> Result<Perm, InvalidPermutationError>
{ Ok(Perm { inv: PermVec::from_vec(vec)? }.inverted()) }
/// Construct a perm from the vector internally used to represent it.
///
/// The format taken by this method is actually **the inverse** of the format
/// accepted by `from_vec`. If the `k`th element of the permutation vector is
/// `value`, then applying the permutation will *push* the data at index `k`
/// over to index `value`. This format is generally trickier to think about,
/// but is superior to the `from_vec` representation in terms of efficiency.
///
/// This performs O(n log n) validation on the data to verify that it
/// satisfies the invariants of `Perm`.
pub fn from_raw_inv(inv: Vec<usize>) -> Result<Perm, InvalidPermutationError>
{ Ok(Perm { inv: PermVec::from_vec(inv)? }) }
/// No-op constructor. Still performs checking in debug builds.
///
/// # Safety
///
/// `inv` must contain every element in `(0..inv.len())`,
/// or else the behavior is undefined.
pub unsafe fn from_raw_inv_unchecked(inv: Vec<usize>) -> Perm
{ Perm { inv: PermVec(inv).debug_validated() } }
/// Construct a permutation of length |a| + |b|.
///
/// The inserted elements will be shifted by this permutation's length,
/// so that they operate on an entirely independent set of data from
/// the existing elements.
pub fn append_mut(&mut self, other: &Perm)
{
// (a direct sum of inverses is the inverse of the direct sum)
self.inv.append_mut(&other.inv);
}
#[cfg(feature = "rand")]
pub fn random(n: usize) -> Perm
{
use ::rand::Rng;
let mut inv: Vec<_> = (0..n).collect();
::rand::thread_rng().shuffle(&mut inv);
Perm { inv: PermVec(inv) }
}
pub fn into_vec(self) -> Vec<usize>
{ self.inverted().inv.0 }
/// No-op destructure
pub fn into_raw_inv(self) -> Vec<usize>
{ self.inv.0 }
#[must_use = "not an in-place operation"]
pub fn inverted(&self) -> Perm
{
// (the inverse of the inverse is... you know...)
Perm { inv: self.inv.inverted() }
}
// (this might sound niche, but it's not like we can safely expose `&mut [u32]`,
// so what's the harm in having a niche method?)
//
/// Compose with the permutation that shifts elements forward (performing `self` first)
///
/// To construct the shift permutation itself, use `Perm::eye(n).shift_right(amt)`.
pub fn shift_right(self, amt: usize) -> Self
{ Perm { inv: self.inv.prefix_shift_left(amt) } }
/// Compose with the permutation that shifts elements backward (performing `self` first)
///
/// To construct the shift permutation itself, use `Perm::eye(n).shift_left(amt)`.
pub fn shift_left(self, amt: usize) -> Self
{ Perm { inv: self.inv.prefix_shift_right(amt) } }
/// Compose with the permutation that shifts elements to the right by a signed offset.
pub fn shift_signed(self, n: isize) -> Self
{
if n < 0 {
assert_ne!(n, ::std::isize::MIN, "(exasperated sigh)");
self.shift_left((-n) as usize)
} else {
self.shift_right(n as usize)
}
}
/// Apply the permutation to an index. O(1).
///
/// Calling this on the indices contained in a sparse-format data structure will
/// produce the same indices as if the corresponding dense-format data structure
/// were permuted.
pub fn permute_index(&self, i: usize) -> usize {
// F.Y.I. this method is **literally the entire reason** that we store the inverse.
self.inv.0[i]
}
/// Construct the outer product of self and `slower`, with `self`
/// being the fast (inner) index.
///
/// The resulting `Perm` will permute blocks of size `self.len()`
/// according to `slower`, and will permute elements within each
/// block by `self`.
pub fn with_outer(&self, slower: &Perm) -> Perm
{
// the inverse of the outer product is the outer product of inverses
Perm { inv: self.inv.with_outer(&slower.inv) }
}
/// Construct the outer product of self and `faster`, with `self`
/// being the slow (outer) index.
pub fn with_inner(&self, faster: &Perm) -> Perm
{ faster.with_outer(self) }
pub fn pow_unsigned(&self, mut exp: u64) -> Perm {
// Exponentiation by squaring (permutations form a monoid)
// NOTE: there's plenty of room to optimize the number of heap
// allocations here
let mut acc = Perm::eye(self.len());
let mut base = self.clone();
while exp > 0 {
if (exp & 1) == 1 {
acc = acc.permuted_by(&base);
}
base = base.clone().permuted_by(&base);
exp /= 2;
}
acc
}
pub fn pow_signed(&self, exp: i64) -> Perm {
if exp < 0 {
assert_ne!(exp, ::std::i64::MIN, "(exasperated sigh)");
self.inverted().pow_unsigned((-exp) as u64)
} else {
self.pow_unsigned(exp as u64)
}
}
}
impl PermVec {
fn eye(n: usize) -> PermVec
{ PermVec((0..n).collect()) }
fn argsort<T: Ord>(xs: &[T]) -> PermVec
{
let mut perm: Vec<_> = (0..xs.len()).collect();
perm.sort_by(|&a, &b| xs[a].cmp(&xs[b]));
PermVec(perm)
}
fn from_vec(vec: Vec<usize>) -> Result<PermVec, InvalidPermutationError>
{
if !Self::validate_data(&vec) {
return Err(InvalidPermutationError(::failure::Backtrace::new()));
}
Ok(PermVec(vec))
}
// Checks invariants required by Perm for unsafe code.
#[must_use = "doesn't assert"]
fn validate_data(xs: &[usize]) -> bool {
let mut vec = xs.to_vec();
vec.sort();
vec.into_iter().eq(0..xs.len())
}
fn debug_validated(self) -> PermVec {
debug_assert!(PermVec::validate_data(&self.0));
self
}
fn append_mut(&mut self, other: &Self)
{
let offset = self.0.len();
self.0.extend(other.0.iter().map(|&i| i + offset));
PermVec::validate_data(&self.0);
}
#[must_use = "not an in-place operation"]
fn inverted(&self) -> Self
{
let mut inv = vec![::std::usize::MAX; self.0.len()]; // [Src] -> Dest
for (i, &x) in self.0.iter().enumerate() { // i: Dest, x: Src
inv[x] = i;
}
PermVec(inv).debug_validated()
}
// The perm that does `self`, then shifts right.
#[allow(unused)]
fn postfix_shift_right(mut self, amt: usize) -> PermVec
{
let n = self.0.len();
self.0.rotate_right(amt % n);
self.debug_validated()
}
// The perm that does `self`, then shifts left.
#[allow(unused)]
fn postfix_shift_left(mut self, amt: usize) -> PermVec
{
let n = self.0.len();
self.0.rotate_left(amt % n);
self.debug_validated()
}
// The perm that shifts left, then applies `self`
fn prefix_shift_left(mut self, amt: usize) -> PermVec {
// Add amt to each value.
let n = self.0.len();
let amt = amt % n;
for x in &mut self.0 {
*x = (*x + amt) % n;
}
self.debug_validated()
}
// The perm that shifts right, then applies `self`
fn prefix_shift_right(self, amt: usize) -> PermVec {
// Subtract amt from each value.
// ...or rather, shift left by `(-amt) mod len`
//
// (technically, this puts it into the range `[1, len]` instead of `[0, len)`
// due to a silly edge case, but that doesn't matter)
let len = self.0.len();
self.prefix_shift_left(len - amt % len)
}
fn with_outer(&self, slower: &PermVec) -> PermVec
{
assert!(self.0.len().checked_mul(slower.0.len()).is_some());
let mut perm = Vec::with_capacity(self.0.len() * slower.0.len());
for &block_index in &slower.0 {
let offset = self.0.len() * block_index;
perm.extend(self.0.iter().map(|&x| x + offset));
}
PermVec(perm).debug_validated()
}
// Perm that applies self then other.
fn then(&self, other: &PermVec) -> PermVec
{
assert_eq!(self.0.len(), other.0.len(), "Incorrect permutation length");
let mut out = vec![::std::usize::MAX; self.0.len()];
for (out_i, &self_i) in other.0.iter().enumerate() {
out[out_i] = self.0[self_i];
}
PermVec(out).debug_validated()
}
}
impl Perm {
/// Flipped group operator.
///
/// `a.then(b) == b.of(a)`. The flipped order is more aligned
/// with this library's generally row-centric design.
///
/// More naturally,
/// `x.permuted_by(a).permuted_by(b) == x.permuted_by(a.then(b))`.
pub fn then(&self, other: &Perm) -> Perm
{
// The inverses compose in reverse.
Perm { inv: other.inv.then(&self.inv) }
}
/// Conventional group operator.
pub fn of(&self, other: &Perm) -> Perm
{ other.then(self) }
}
// NOTE: As a reminder to myself, I did in fact try introducing newtype indices
// at some point. `Perm` became `Perm<Src, Dest>`, an object that would
// transform data indexed by `Src` into data indexed by `Dest`.
// I found that this model was wildly successful at describing all existing
// usage of `Permute` and `Partition`, and even the lowest-level
// implementations (e.g. for Vec) worked almost entirely without
// modification after abstracting over index type.
//
// ...however, the reason I decided not to keep it was because actually
// taking *advantage* of it required pervasive modification to function
// signatures all over `rsp2-structure` and `rsp2-tasks`, increasing
// coupling between parts of rsp2 in a way that was basically unavoidable.
// `fn foo(impl IntoIndexed<I, X>)` and
// `fn bar() -> OwnedType<I, X> where (): IndexFamily<I, X>` were no substitute
// for what used to be simply `fn foo(Vec<X>)` and `fn bar() -> Vec<X>`.
//
// I believe that abstracting over index type is still useful as a conceptual
// tool for reasoning about how to use permutations. Encoding them into the
// type system, however, takes too much effort for too little gain.
/// Trait for applying a permutation operation.
///
/// In rsp2, this has a couple of roles that might *appear* to be conflated
/// together, but they really aren't. It might help to envision `Perm` as
/// being parametrized over two index types; `Perm<Src, Dest>` takes data
/// indexed by type `Src` and transforms it into data indexed by `Dest`.
///
/// As such, the `Permute` impl may appear to do different things based on
/// how the data is stored (e.g. dense data that is conceptually indexed
/// by some index type `I`, versus sparse data that conceptually *contains*
/// values of type `I`).
///
/// # Laws
///
/// All implementations of `Permute` must satisfy the following properties,
/// which give `Permute::permuted_by` the qualities of a group action.
/// (whose group operator is, incidentally, also `Permute::permuted_by`!)
///
/// * **Identity:**
/// ```ignore
/// data.permuted_by(Perm::eye(data.len())) == data
/// ```
/// * **Compatibility:**
/// ```ignore
/// data.permuted_by(a).permuted_by(b) == data.permuted_by(a.permuted_by(b))
/// ```
///
/// When envisioning `Perm` as generic over `Src` and `Dest` types, it could
/// perhaps be said that `Perm`s are the morphisms of a category. (brushing
/// aside issues of mismatched length)
pub trait Permute: Sized {
// awkward name, but it makes it makes two things clear
// beyond a shadow of a doubt:
// - The receiver gets permuted, not the argument.
// (relevant when Self is Perm)
// - The permutation is not in-place.
fn permuted_by(self, perm: &Perm) -> Self;
}
// (module to protect from lollipop model; the unsafety here
// is extremely localized)
mod unsafe_impls {
use super::*;
pub(super) fn inv_permute_to_new_vec<T>(vec: Vec<T>, inv: &PermVec) -> Vec<T> {
let mut out = Vec::with_capacity(vec.len());
inv_permute_to_mut_vec(vec, inv, &mut out);
out
}
pub(super) fn inv_permute_to_mut_vec<T>(mut vec: Vec<T>, inv: &PermVec, out: &mut Vec<T>) {
use ::std::ptr;
assert_eq!(
vec.len(), inv.0.len(),
"Incorrect permutation length",
);
out.clear();
//------------------------------------------------
// You are now entering a PANIC FREE ZONE
// Make a bunch of uninitialized elements indexable.
unsafe { out.set_len(vec.len()); }
// a perm holds indices into the data vec, so the inverse holds indices into `out`.
for (vec_i, &out_i) in inv.0.iter().enumerate() {
let tmp = unsafe { ptr::read(&vec[vec_i]) };
unsafe { ptr::write(&mut out[out_i], tmp) };
}
// Don't drop the original items, but do allow the original
// vec to fall out of scope so the memory can be freed.
unsafe { vec.set_len(0); }
// Thank you for flying with us. You may now PANIC!
//------------------------------------------------
}
}
impl<T> Permute for Vec<T> {
fn permuted_by(self, perm: &Perm) -> Vec<T>
{ self::unsafe_impls::inv_permute_to_new_vec(self, &perm.inv) }
}
// `Permute` doubles as the group operator.
// (think of it as matrix multiplication in the matrix representation)
impl Permute for PermVec {
fn permuted_by(self, perm: &Perm) -> PermVec
{ PermVec(self.0.permuted_by(perm)) }
}
impl Permute for Perm {
fn permuted_by(self, other: &Perm) -> Perm
{ self.then(other) }
}
// combinators
#[cfg(feature = "frunk")]
impl Permute for HNil {
fn permuted_by(self, _: &Perm) -> HNil
{ HNil }
}
#[cfg(feature = "frunk")]
impl<A, B> Permute for HCons<A, B>
where
A: Permute,
B: Permute,
{
fn permuted_by(self, perm: &Perm) -> HCons<A, B>
{ HCons {
head: self.head.permuted_by(perm),
tail: self.tail.permuted_by(perm),
}}
}
// rsp2-tasks needs this
impl<T: Clone> Permute for ::std::rc::Rc<[T]> {
fn permuted_by(self, perm: &Perm) -> Self
{
// this could be done with less copying...
// (though it absolutely has to make at least one full copy)
let vec = self.to_vec();
let vec = vec.permuted_by(perm);
let slice = vec.into_boxed_slice();
slice.into()
}
}
#[cfg(test)]
#[deny(unused)]
mod tests {
use super::*;
#[test]
fn inverse()
{
let perm = Perm::random(20);
let inv = perm.inverted();
assert_eq!(perm.clone().permuted_by(&inv), Perm::eye(20));
assert_eq!(inv.permuted_by(&perm), Perm::eye(20));
}
#[test]
fn inverse_is_argsort()
{
let perm = Perm::random(20);
assert_eq!(
Perm::argsort(&perm.clone().into_vec()).into_vec(),
perm.inverted().into_vec(),
);
}
#[test]
fn invalid() {
assert_matches!(
Err(InvalidPermutationError(_)),
Perm::from_vec(vec![0,1,3,3]));
assert_matches!(
Err(InvalidPermutationError(_)),
Perm::from_vec(vec![1,2,3]));
}
#[test]
#[should_panic(expected = "permutation length")]
fn incompatible() {
// another requirement for the Vec impl's safety
let _ = vec![4,2,1].permuted_by(&Perm::eye(2));
}
#[test]
fn drop_safety() {
let (drop_history, dp) = ::util::DropPusher::new_trial();
{
let vec = vec![dp(0), dp(1), dp(2), dp(3), dp(4)];
let vec2 = vec.permuted_by(&Perm::from_vec(vec![3, 1, 0, 4, 2]).unwrap());
assert_eq!(drop_history.borrow().len(), 0);
drop(vec2);
assert_eq!(drop_history.borrow().len(), 5);
}
assert_eq!(drop_history.borrow().len(), 5);
}
#[test]
fn argsort_is_stable()
{
use ::rand::Rng;
// a long vector of only two unique values; a prime target for stability checks
let n = 300;
let values: Vec<_> = (0..n).map(|_| ::rand::thread_rng().gen_range(0, 2)).collect();
let perm = Perm::argsort(&values);
let permuted_indices = (0..n).collect::<Vec<_>>().permuted_by(&perm);
let permuted_values = values.permuted_by(&perm);
let error = format!("not your lucky day, Mister one-in-{:e}", 2f64.powi(n));
let first_one = permuted_values.iter().position(|&x| x == 1).expect(&error);
let is_strictly_sorted = |xs: &[_]| xs.windows(2).all(|w| w[0] < w[1]);
assert!(is_strictly_sorted(&permuted_indices[..first_one]));
assert!(is_strictly_sorted(&permuted_indices[first_one..]));
let error = format!("DEFINITELY not your lucky day, Mister one-in-{}-factorial!!", n);
assert!(!is_strictly_sorted(&permuted_indices[..]), "{}", error);
}
#[test]
fn associativity()
{
let xy = Perm::from_vec(vec![1, 0, 2]).unwrap();
let zx = Perm::from_vec(vec![2, 1, 0]).unwrap();
let xyzx = Perm::from_vec(vec![2, 0, 1]).unwrap();
assert_eq!(xy.clone().permuted_by(&zx), xyzx);
assert_eq!(xy.then(&zx), xyzx);
assert_eq!(zx.of(&xy), xyzx);
assert_eq!(
vec![0,1,2].permuted_by(&xy).permuted_by(&zx),
vec![0,1,2].permuted_by(&xyzx),
);
assert_eq!(
vec![0,1,2].permuted_by(&xy).permuted_by(&zx),
vec![2,0,1],
);
for _ in 0..10 {
use ::rand::Rng;
let mut rng = ::rand::thread_rng();
let n = rng.gen_range(10, 20);
let s = b"abcdefghijklmnopqrstuvwxyz"[..n].to_vec();
let a = Perm::random(n);
let b = Perm::random(n);
let c = Perm::random(n);
let bc = b.clone().permuted_by(&c);
assert_eq!(
a.clone().permuted_by(&b).permuted_by(&c),
a.clone().permuted_by(&bc),
"compatibility, for Self = Perm (a.k.a. associativity)",
);
assert_eq!(
a.inv.clone().permuted_by(&b).permuted_by(&c),
a.inv.clone().permuted_by(&bc),
"compatibility, for Self = PermVec",
);
assert_eq!(
s.clone().permuted_by(&b).permuted_by(&c),
s.clone().permuted_by(&bc),
"compatibility, for Self = Vec",
);
}
}
#[test]
fn append()
{
let mut a = Perm::from_vec(vec![1, 0]).unwrap();
let b = Perm::from_vec(vec![1, 2, 0]).unwrap();
a.append_mut(&b);
assert_eq!(
vec![00, 01, /* -- */ 10, 11, 12].permuted_by(&a),
vec![01, 00, /* -- */ 11, 12, 10],
);
}
#[test]
fn outer()
{
let use_outer = |a, b| {
let a = Perm::from_vec(a).unwrap();
let b = Perm::from_vec(b).unwrap();
let xs: Vec<_> =
(0..b.len()).flat_map(|slow| {
(0..a.len()).map(move |fast| 10 * slow + fast)
}).collect();
xs.permuted_by(&a.with_outer(&b))
};
assert_eq!(
use_outer(
vec![1, 0, 2, 3],
vec![1, 2, 0],
),
vec![
11, 10, 12, 13,
21, 20, 22, 23,
01, 00, 02, 03,
],
);
// empty perms
assert_eq!(use_outer(vec![1, 0], vec![]), vec![]);
assert_eq!(use_outer(vec![], vec![1, 0]), vec![]);
}
#[test]
fn shift() {
assert_eq!(
vec![0, 1, 2, 3, 4, 5].permuted_by(&Perm::eye(6).shift_right(8)),
vec![4, 5, 0, 1, 2, 3],
);
assert_eq!(
vec![0, 1, 2, 3, 4, 5].permuted_by(&Perm::eye(6).shift_left(8)),
vec![2, 3, 4, 5, 0, 1],
);
assert_eq!(
vec![0, 1, 2, 3, 4, 5].permuted_by(&Perm::eye(6).shift_signed(8)),
vec![4, 5, 0, 1, 2, 3],
);
assert_eq!(
vec![0, 1, 2, 3, 4, 5].permuted_by(&Perm::eye(6).shift_signed(-8)),
vec![2, 3, 4, 5, 0, 1],
);
// potentially dumb edge case
assert_eq!(
vec![0, 1, 2, 3, 4, 5].permuted_by(&Perm::eye(6).shift_signed(6)),
vec![0, 1, 2, 3, 4, 5],
);
assert_eq!(
vec![0, 1, 2, 3, 4, 5].permuted_by(&Perm::eye(6).shift_signed(-6)),
vec![0, 1, 2, 3, 4, 5],
);
}
#[test]
fn pow_unsigned() {
for &len in &[0, 1, 4, 20] {
for _ in 0..5 {
let perm = Perm::random(len);
for &exp in &[0, 1, 4, 20, 21] {
let original = b"abcdefghijklmnopqrstuvwxyz"[..len as usize].to_owned();
let mut brute_force = original.clone();
for _ in 0..exp {
brute_force = brute_force.permuted_by(&perm);
}
let fast = original.permuted_by(&perm.pow_unsigned(exp));
assert_eq!(fast, brute_force);
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment