im/
sort.rs

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