use std::cmp::Ordering;
pub struct TandemSorter {
indices: Box<[usize]>,
should_reset: bool,
}
macro_rules! new_fn {
( $fn:ident: $sort:expr ) => {
pub fn $fn<T>(slice: &[T], cmp: fn(&T, &T) -> Ordering) -> Self {
let mut indices: Box<[usize]> = (0..slice.len()).collect();
$sort(&mut indices, |&i, &j| cmp(&slice[i], &slice[j]));
Self {
indices,
should_reset: false,
}
}
};
}
impl TandemSorter {
new_fn!(new_stable: <[_]>::sort_by);
pub fn sort<T>(&mut self, slice: &mut [T]) {
if self.should_reset {
self.toggle_marks();
self.should_reset = false;
}
for i in 0..self.indices.len() {
let i_idx = self.indices[i];
if Self::idx_is_marked(i_idx) {
continue;
}
let mut j = i;
let mut j_idx = i_idx;
while j_idx != i {
self.indices[j] = Self::toggle_mark_idx(j_idx);
slice.swap(j, j_idx);
j = j_idx;
j_idx = self.indices[j];
}
self.indices[j] = Self::toggle_mark_idx(j_idx);
}
self.should_reset = true;
}
fn toggle_marks(&mut self) {
for idx in self.indices.iter_mut() {
*idx = Self::toggle_mark_idx(*idx);
}
}
const fn idx_is_marked(idx: usize) -> bool {
idx.leading_zeros() == 0
}
const fn toggle_mark_idx(idx: usize) -> usize {
idx ^ !(usize::MAX >> 1)
}
}
#[cfg(test)]
mod tests {
use proptest::prelude::*;
use super::TandemSorter;
proptest! {
#![proptest_config(ProptestConfig::with_cases(1000))]
#[test]
fn sort(mut actual in prop::collection::vec(0_u8..100, 0..100)) {
let mut expected_sorted = actual.clone();
expected_sorted.sort_unstable();
let mut sorter = TandemSorter::new_stable(&actual, u8::cmp);
sorter.sort(&mut actual);
assert_eq!(actual, expected_sorted);
}
}
}