imbl/sort.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
use crate::vector::FocusMut;
use rand_core::{RngCore, SeedableRng};
use std::cmp::Ordering;
use std::mem;
fn gen_range<R: RngCore>(rng: &mut R, min: usize, max: usize) -> usize {
let range = max - min;
min + (rng.next_u64() as usize % range)
}
// Ported from the Java version at:
// http://www.cs.princeton.edu/~rs/talks/QuicksortIsOptimal.pdf
// There are a couple of modifications made here to make it more performant on the tree structure of
// the Vector. Instead of moving of handling equal and nonequal items in a single pass we make two
// additional passes to find the exact partition places. This allows us to split the focus into
// three correctly sized parts for less than, equal to and greater than items. As a bonus this
// doesn't need to reorder the equal items to the center of the vector.
fn do_quicksort<A, F, R>(vector: FocusMut<'_, A>, cmp: &F, rng: &mut R)
where
A: Clone,
F: Fn(&A, &A) -> Ordering,
R: RngCore,
{
if vector.len() <= 1 {
return;
}
// We know there are at least 2 elements here
let pivot_index = gen_range(rng, 0, vector.len());
let (mut first, mut rest) = vector.split_at(1);
if pivot_index > 0 {
mem::swap(rest.index_mut(pivot_index - 1), first.index_mut(0));
}
// Pivot is now always in the first slice
let pivot_item = first.index(0);
// Find the exact place to put the pivot or pivot-equal items
let mut less_count = 0;
let mut equal_count = 0;
for index in 0..rest.len() {
let item = rest.index(index);
let comp = cmp(item, pivot_item);
match comp {
Ordering::Less => less_count += 1,
Ordering::Equal => equal_count += 1,
Ordering::Greater => {}
}
}
// If by accident we picked the minimum element as a pivot, we just call sort again with the
// rest of the vector.
if less_count == 0 {
do_quicksort(rest, cmp, rng);
return;
}
// We know here that there is at least one item before the pivot, so we move the minimum to the
// beginning part of the vector. First, however we swap the pivot to the start of the equal
// zone.
less_count -= 1;
equal_count += 1;
let first_item = first.index_mut(0);
mem::swap(first_item, rest.index_mut(less_count));
for index in 0..rest.len() {
if index == less_count {
// This is the position we swapped the pivot to. We can't move it from its position, and
// we know its not the minimum.
continue;
}
let rest_item = rest.index_mut(index);
if cmp(rest_item, first_item) == Ordering::Less {
mem::swap(first_item, rest_item);
}
}
// Split the vector up into less_than, equal to and greater than parts.
let (remaining, mut greater_focus) = rest.split_at(less_count + equal_count);
let (mut less_focus, mut equal_focus) = remaining.split_at(less_count);
let mut less_position = 0;
let mut equal_position = 0;
let mut greater_position = 0;
while less_position != less_focus.len() || greater_position != greater_focus.len() {
// At start of this loop, equal_position always points to an equal item
let mut equal_swap_side = None;
let equal_item = equal_focus.index(equal_position);
// Advance the less_position until we find an out of place item
while less_position != less_focus.len() {
let less_item = less_focus.index(less_position);
match cmp(less_item, equal_item) {
Ordering::Equal => {
equal_swap_side = Some(Ordering::Less);
break;
}
Ordering::Greater => {
break;
}
_ => {}
}
less_position += 1;
}
// Advance the greater until we find an out of place item
while greater_position != greater_focus.len() {
let greater_item = greater_focus.index(greater_position);
match cmp(greater_item, equal_item) {
Ordering::Less => break,
Ordering::Equal => {
equal_swap_side = Some(Ordering::Greater);
break;
}
_ => {}
}
greater_position += 1;
}
if let Some(swap_side) = equal_swap_side {
// One of the sides is equal to the pivot, advance the pivot
let item = if swap_side == Ordering::Less {
less_focus.index_mut(less_position)
} else {
greater_focus.index_mut(greater_position)
};
// We are guaranteed not to hit the end of the equal focus
while cmp(item, equal_focus.index(equal_position)) == Ordering::Equal {
equal_position += 1;
}
// Swap the equal position and the desired side, it's important to note that only the
// equals focus is guaranteed to have made progress so we don't advance the side's index
mem::swap(item, equal_focus.index_mut(equal_position));
} else if less_position != less_focus.len() && greater_position != greater_focus.len() {
// Both sides are out of place and not equal to the pivot, this can only happen if there
// is a greater item in the lesser zone and a lesser item in the greater zone. The
// solution is to swap both sides and advance both side's indices.
debug_assert_ne!(
cmp(
less_focus.index(less_position),
equal_focus.index(equal_position)
),
Ordering::Equal
);
debug_assert_ne!(
cmp(
greater_focus.index(greater_position),
equal_focus.index(equal_position)
),
Ordering::Equal
);
mem::swap(
less_focus.index_mut(less_position),
greater_focus.index_mut(greater_position),
);
less_position += 1;
greater_position += 1;
}
}
// Now we have partitioned both sides correctly, we just have to recurse now
do_quicksort(less_focus, cmp, rng);
if !greater_focus.is_empty() {
do_quicksort(greater_focus, cmp, rng);
}
}
pub(crate) fn quicksort<A, F>(vector: FocusMut<'_, A>, cmp: &F)
where
A: Clone,
F: Fn(&A, &A) -> Ordering,
{
let mut rng = rand_xoshiro::Xoshiro256Plus::seed_from_u64(0);
do_quicksort(vector, cmp, &mut rng);
}
#[cfg(test)]
mod test {
use super::*;
use crate::test::is_sorted;
use crate::vector::proptest::vector;
use ::proptest::num::i32;
use ::proptest::proptest;
proptest! {
#[test]
fn test_quicksort(ref input in vector(i32::ANY, 0..10000)) {
let mut vec = input.clone();
let len = vec.len();
if len > 1 {
quicksort(vec.focus_mut(), &Ord::cmp);
}
assert!(is_sorted(vec));
}
}
}