use std::cmp::Ordering;
#[doc(hidden)]
#[must_use]
pub const fn compact_sort_scratch_bytes(entries: usize) -> u64 {
if entries < 2 {
return 0;
}
(entries as u64)
.saturating_mul(size_of::<usize>() as u64)
.saturating_mul(2)
}
#[doc(hidden)]
pub fn compact_sort_unstable_by<T, F>(values: &mut [T], compare: F)
where
F: FnMut(&T, &T) -> Ordering,
{
let mut values = TypedValues { values, compare };
compact_sort_erased(&mut values);
}
trait CompactValues {
fn len(&self) -> usize;
fn compare(&mut self, left: usize, right: usize) -> Ordering;
fn swap(&mut self, left: usize, right: usize);
}
struct TypedValues<'a, T, F> {
values: &'a mut [T],
compare: F,
}
impl<T, F> CompactValues for TypedValues<'_, T, F>
where
F: FnMut(&T, &T) -> Ordering,
{
fn len(&self) -> usize {
self.values.len()
}
fn compare(&mut self, left: usize, right: usize) -> Ordering {
(self.compare)(&self.values[left], &self.values[right])
}
fn swap(&mut self, left: usize, right: usize) {
self.values.swap(left, right);
}
}
#[inline(never)]
fn compact_sort_erased(values: &mut dyn CompactValues) {
let len = values.len();
if len < 2 {
return;
}
let mut positions = (0..len).collect::<Vec<_>>();
sort_positions_by(&mut positions, values);
let mut destinations = vec![0usize; len];
for (destination, source) in positions.into_iter().enumerate() {
destinations[source] = destination;
}
apply_position_permutation(&mut destinations, values);
}
#[inline(never)]
fn sort_positions_by(positions: &mut [usize], values: &mut dyn CompactValues) {
let len = positions.len();
let mut root = len / 2;
while root > 0 {
root -= 1;
sift_down_positions(positions, root, len, values);
}
let mut end = len;
while end > 1 {
end -= 1;
positions.swap(0, end);
sift_down_positions(positions, 0, end, values);
}
}
#[inline(never)]
fn sift_down_positions(
positions: &mut [usize],
mut root: usize,
end: usize,
values: &mut dyn CompactValues,
) {
loop {
let Some(left) = root.checked_mul(2).and_then(|index| index.checked_add(1)) else {
return;
};
if left >= end {
return;
}
let right = left + 1;
let child = if right < end && values.compare(positions[left], positions[right]).is_lt() {
right
} else {
left
};
if !values.compare(positions[root], positions[child]).is_lt() {
return;
}
positions.swap(root, child);
root = child;
}
}
#[inline(never)]
fn apply_position_permutation(destinations: &mut [usize], values: &mut dyn CompactValues) {
for position in 0..destinations.len() {
while destinations[position] != position {
let destination = destinations[position];
values.swap(position, destination);
destinations.swap(position, destination);
}
}
}
#[cfg(test)]
mod tests;