use core::cmp::Ordering;
#[inline(never)]
pub(crate) fn sort_by<T>(items: &mut [T], compare: &mut dyn FnMut(&T, &T) -> Ordering) {
let order = {
let items = &*items;
stable_order(items.len(), &mut |a, b| compare(&items[a], &items[b]))
};
permute(items, order);
}
#[inline(never)]
fn stable_order(len: usize, compare: &mut dyn FnMut(usize, usize) -> Ordering) -> Vec<usize> {
let mut order: Vec<usize> = (0..len).collect();
let mut merged = order.clone();
let mut width = 1;
while width < len {
let mut start = 0;
while start < len {
let mid = (start + width).min(len);
let end = (start + 2 * width).min(len);
let (mut left, mut right) = (start, mid);
for slot in &mut merged[start..end] {
let take_left =
right == end || (left < mid && compare(order[left], order[right]).is_le());
if take_left {
*slot = order[left];
left += 1;
} else {
*slot = order[right];
right += 1;
}
}
start = end;
}
core::mem::swap(&mut order, &mut merged);
width *= 2;
}
order
}
fn permute<T>(items: &mut [T], mut order: Vec<usize>) {
const DONE: usize = usize::MAX;
for start in 0..items.len() {
if order[start] == DONE {
continue;
}
let mut at = start;
loop {
let from = order[at];
order[at] = DONE;
if from == start {
break;
}
items.swap(at, from);
at = from;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sorted<T: Clone>(items: &[T], mut compare: impl FnMut(&T, &T) -> Ordering) -> Vec<T> {
let mut out = items.to_vec();
sort_by(&mut out, &mut compare);
out
}
#[test]
fn agrees_with_the_standard_stable_sort() {
let mut seed = 0x2545_f491_4f6c_dd1d_u64;
for len in 0..70 {
let items: Vec<(u8, usize)> = (0..len)
.map(|position| {
seed ^= seed << 13;
seed ^= seed >> 7;
seed ^= seed << 17;
((seed % 5) as u8, position)
})
.collect();
let mut expected = items.clone();
expected.sort_by_key(|item| item.0);
assert_eq!(sorted(&items, |a, b| a.0.cmp(&b.0)), expected, "{len}");
}
}
#[test]
fn sorts_items_that_are_not_copy() {
let items = ["pear", "apple", "fig", "apple"].map(String::from);
assert_eq!(
sorted(&items, |a, b| b.len().cmp(&a.len())),
["apple", "apple", "pear", "fig"]
);
}
}