Rollup merge of #107522 - Sp00ph:introselect, r=Amanieu

Add Median of Medians fallback to introselect

Fixes #102451.

This PR is a follow up to #106997. It adds a Fast Deterministic Selection implementation as a fallback to the introselect algorithm used by `select_nth_unstable`. This allows it to guarantee O(n) worst case running time, while maintaining good performance in all cases.

This would fix #102451, which was opened because the `select_nth_unstable` docs falsely claimed that it had O(n) worst case performance, even though it was actually quadratic in the worst case. #106997 improved the worst case complexity to O(n log n) by using heapsort as a fallback, and this PR further improves it to O(n) (this would also make #106933 unnecessary).
It also improves the actual runtime if the fallback gets called: Using a pathological input of size `1 << 19` (see the playground link in #102451), calculating the median is roughly 3x faster using fast deterministic selection as a fallback than it is using heapsort.

The downside to this is less code reuse between the sorting and selection algorithms, but I don't think it's that bad. The additional algorithms are ~250 LOC with no `unsafe` blocks (I tried using unsafe to avoid bounds checks but it didn't noticeably improve the performance).
I also let it fuzz for a while against the current `select_nth_unstable` implementation to ensure correctness, and it seems to still fulfill all the necessary postconditions.

cc `@scottmcm` who reviewed #106997
This commit is contained in:
Michael Goulet
2023-05-25 13:57:59 -07:00
committed by GitHub
3 changed files with 311 additions and 140 deletions

View File

@@ -42,6 +42,7 @@ mod index;
mod iter;
mod raw;
mod rotate;
mod select;
mod specialize;
#[unstable(feature = "str_internals", issue = "none")]
@@ -3034,7 +3035,7 @@ impl<T> [T] {
where
T: Ord,
{
sort::partition_at_index(self, index, T::lt)
select::partition_at_index(self, index, T::lt)
}
/// Reorder the slice with a comparator function such that the element at `index` is at its
@@ -3089,7 +3090,7 @@ impl<T> [T] {
where
F: FnMut(&T, &T) -> Ordering,
{
sort::partition_at_index(self, index, |a: &T, b: &T| compare(a, b) == Less)
select::partition_at_index(self, index, |a: &T, b: &T| compare(a, b) == Less)
}
/// Reorder the slice with a key extraction function such that the element at `index` is at its
@@ -3145,7 +3146,7 @@ impl<T> [T] {
F: FnMut(&T) -> K,
K: Ord,
{
sort::partition_at_index(self, index, |a: &T, b: &T| f(a).lt(&f(b)))
select::partition_at_index(self, index, |a: &T, b: &T| f(a).lt(&f(b)))
}
/// Moves all consecutive repeated elements to the end of the slice according to the