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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
use ;
use ptr;
use bubble;
/// Sorts a slice in place using comb sort.
///
/// Comb sort is bubble sort with a gap. A pass compares every element with the one `gap`
/// slots after it and swaps the two if they are out of order, so a small element near the end
/// (a "turtle") moves `gap` slots left in one step instead of one. The gap starts at the length
/// of the slice and shrinks by a factor of 1.3 after each pass; once it reaches 1, the slice is
/// nearly sorted and plain bubble sort finishes it.
///
/// Four refinements keep the algorithm as cheap as comb sort can be:
///
/// * **Rule of 11 (Combsort11).** A gap of 9 or 10 is replaced by 11. The sequences that go
/// through 9 or 10 leave more turtles for the final bubble sort; 11, 8, 6, 4, 3, 2 does not.
/// On random input of 10,000 elements this saves about 6% of the comparisons and narrows
/// their spread.
/// * **An adaptive finish.** The final phase is this crate's [`bubble`](fn@bubble), which
/// scans only up to the last element it moved and moves elements by hole shifting, instead
/// of the repeated full passes of classic comb sort.
/// * **Branchless swaps for small elements.** Whether a gapped pass swaps a pair is close to a
/// coin flip, which defeats the branch predictor. For elements of at most 32 bytes the swap
/// is made unconditional: both slots are rewritten from addresses chosen by the comparison,
/// which the processor turns into conditional moves. A pass is also walked in blocks of
/// `gap` pairs taken from two non-overlapping slices, which the compiler can schedule
/// freely. On random input this is 3–4.5 times faster for integers and 1.5–1.8 times for
/// strings. On already sorted input, where every branch would be predicted, it is about a
/// quarter slower, a price worth paying for a sort that is not adaptive anyway. Larger
/// elements cost more to rewrite than a misprediction does, so they are swapped only when
/// needed.
/// * **Carrying along each chain at small gaps, for elements of at most 8 bytes.** At a gap
/// of 8 or less, each pair overlaps the one `gap` slots before it, so a swap has to reach
/// memory before the next comparison can read it back, and a pass at gap 2 runs at a third
/// of the speed of one at a large gap. Instead, each chain `c, c + gap, c + 2·gap, ...` is
/// walked holding the largest element seen so far in a register, and only the smaller of it
/// and the next element is written back: the same comparisons with the same outcomes, the
/// same result, and one move per swap plus one per carried element instead of two per swap.
/// This makes the whole sort 10–50% faster for integers and pointers. It only pays when a
/// comparison is cheap: each one waits for the previous one, so larger elements, whose
/// comparisons usually read memory (strings, for one), are much faster with the
/// independent pairs of a plain pass.
///
/// The comparisons made are exactly those of comb sort with the rule of 11, followed by bubble
/// sort with early exit and last-swap bound, though not always in the same order.
///
/// # Properties
///
/// * **Unstable**: equal elements may be reordered (a gapped swap can carry an element past
/// an equal one).
/// * **In place**: `O(1)` auxiliary memory, no allocation.
/// * **Not adaptive**: the gapped passes make the same comparisons whatever the input; only
/// the final bubble sort gets cheaper on nearly sorted input.
///
/// Let `g₁ > g₂ > ... > gₖ ≥ 2` be the gaps: `k ≈ log₁.₃ n − 4` of them, each the previous one
/// divided by 1.3 and rounded down, with the rule of 11 applied. The gapped passes make exactly
/// `Σ (n − gᵢ) ≈ n·(log₁.₃ n − 7.3)` comparisons on every input; the final bubble sort adds at
/// least `n − 1`, and on random input about `3n`.
///
/// | Case | Comparisons | Moves |
/// |------------------------------------|---------------------------------|-----------------------|
/// | Best (already sorted) | `Σ (n − gᵢ) + n − 1` | `0` |
/// | Average (random) | `≈ n·(log₁.₃ n − 4.5)` | `O(n log n)` |
/// | Worst | `O(n²)` | `O(n²)` |
///
/// For `n = 1000`: 19,706 comparisons on sorted input and about 21,300 on random input,
/// against about 499,000 for bubble sort. No known input makes comb sort quadratic in
/// practice, but no `O(n log n)` bound has been proved either.
///
/// Comb sort is mainly of educational value; prefer [`slice::sort_unstable`] for real
/// workloads.
///
/// # Panics
///
/// Does not panic by itself. If the [`Ord`] implementation of `T` panics, the panic is
/// propagated and `arr` is left in an unspecified order, but it still holds every original
/// element exactly once: nothing is dropped twice or leaked.
///
/// # Examples
///
/// ```
/// use a::sort::comb;
///
/// let mut v = [5, 1, 4, 2, 8, 0];
/// comb(&mut v);
/// assert_eq!(v, [0, 1, 2, 4, 5, 8]);
///
/// // Turtles at the end move many slots per step.
/// let mut v = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];
/// comb(&mut v);
/// assert_eq!(v, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
///
/// let mut words = vec!["pear", "apple", "fig"];
/// comb(&mut words);
/// assert_eq!(words, ["apple", "fig", "pear"]);
/// ```
/// The largest element size, in bytes, for which a gapped pass swaps without branching.
///
/// Measured on random input: integers, `u128`, pairs of integers, `&str`, `String` and `Box`
/// sort 1.6–4.5 times faster branchless; elements of 64 bytes or more about 1.2–2 times slower.
const BRANCHLESS_MAX_SIZE: usize = 32;
/// The largest element size, in bytes, for which small gaps are passed by [`pass_carry`].
///
/// Measured per pass at gaps 2 to 8: 2–3 times faster than [`pass_branchless`] for 8-byte
/// integers and pointers, but slower for every 16-byte type tried (up to 1.6 times for pairs of
/// integers) and up to 4 times slower for `String`.
const CARRY_MAX_SIZE: usize = 8;
/// The largest gap passed by [`pass_carry`]. From 11 up (9 and 10 never occur), the
/// independent pairs of [`pass_branchless`], which the compiler vectorizes for integers, win.
const CARRY_MAX_GAP: usize = 8;
/// The gap after `gap`: `gap / 1.3` rounded down, with 9 and 10 replaced by 11.
/// Compares every element of `arr` with the one `gap` slots after it, in increasing order of
/// position, and swaps the two if the second is smaller, with no branch on the outcome.
/// Compares every element of `arr` with the one `gap` slots after it, in increasing order of
/// position, and swaps the two if the second is smaller.
/// Does what [`pass_branchless`] does, one chain `c, c + gap, c + 2·gap, ...` at a time.
///
/// Along a chain, a pass carries the largest element seen so far: each pair is that element
/// and the next one, and the smaller of the two stays behind. So the carried element is held
/// outside the slice, and at each step the smaller of it and the next element is written into
/// the vacant slot behind, chosen by value so that the carried element can stay in a register.
/// Chains share no element, so taking them one after the other gives the same result as the
/// interleaved order of a plain pass.
/// A vacant slot of a slice, plus the element held outside the slice that belongs there.
///
/// Writing the held element back on drop is what keeps [`pass_carry`] panic-safe: the slice is
/// whole again even if a comparison unwinds while the element is out.