authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2017-12-14 19:41:35-05:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2017-12-14 19:41:35-05:00
log75ecfdf66db22942da349d4279b9ddaa8167788f
treea751adc1d750846d7ab740d87b5af0a09c949580
parentc9e01412a451419491786bf3db1713875928092c

replace quicksort with blocksort

closes #657

4 files changed, 1099 insertions(+), 60 deletions(-)

std/math/index.zig+25-6
......@@ -174,12 +174,6 @@ test "math" {
174174}
175175
176176
177pub const Cmp = enum {
178 Less,
179 Equal,
180 Greater,
181};
182
183177pub fn min(x: var, y: var) -> @typeOf(x + y) {
184178 if (x < y) x else y
185179}
......@@ -522,3 +516,28 @@ pub fn cast(comptime T: type, x: var) -> %T {
522516 return T(x);
523517 }
524518}
519
520pub fn floorPowerOfTwo(comptime T: type, value: T) -> T {
521 var x = value;
522
523 comptime var i = 1;
524 inline while(T.bit_count > i) : (i *= 2) {
525 x |= (x >> i);
526 }
527
528 return x - (x >> 1);
529}
530
531test "math.floorPowerOfTwo" {
532 testFloorPowerOfTwo();
533 comptime testFloorPowerOfTwo();
534}
535
536fn testFloorPowerOfTwo() {
537 assert(floorPowerOfTwo(u32, 63) == 32);
538 assert(floorPowerOfTwo(u32, 64) == 64);
539 assert(floorPowerOfTwo(u32, 65) == 64);
540 assert(floorPowerOfTwo(u4, 7) == 4);
541 assert(floorPowerOfTwo(u4, 8) == 8);
542 assert(floorPowerOfTwo(u4, 9) == 8);
543}
std/math/sqrt.zig+58-4
......@@ -7,12 +7,34 @@
77
88const math = @import("index.zig");
99const assert = @import("../debug.zig").assert;
10const builtin = @import("builtin");
11const TypeId = builtin.TypeId;
1012
11pub fn sqrt(x: var) -> @typeOf(x) {
13pub fn sqrt(x: var) -> (if (@typeId(@typeOf(x)) == TypeId.Int) @IntType(false, @typeOf(x).bit_count / 2) else @typeOf(x)) {
1214 const T = @typeOf(x);
13 switch (T) {
14 f32 => @inlineCall(sqrt32, x),
15 f64 => @inlineCall(sqrt64, x),
15 switch (@typeId(T)) {
16 TypeId.FloatLiteral => {
17 return T(sqrt64(x))
18 },
19 TypeId.Float => {
20 return switch (T) {
21 f32 => sqrt32(x),
22 f64 => sqrt64(x),
23 else => @compileError("sqrt not implemented for " ++ @typeName(T)),
24 };
25 },
26 TypeId.IntLiteral => comptime {
27 if (x > @maxValue(u128)) {
28 @compileError("sqrt not implemented for comptime_int greater than 128 bits");
29 }
30 if (x < 0) {
31 @compileError("sqrt on negative number");
32 }
33 return T(sqrt_int(u128, x));
34 },
35 TypeId.Int => {
36 return sqrt_int(T, x);
37 },
1638 else => @compileError("sqrt not implemented for " ++ @typeName(T)),
1739 }
1840}
......@@ -274,3 +296,35 @@ test "math.sqrt64.special" {
274296 assert(math.isNan(sqrt64(-1.0)));
275297 assert(math.isNan(sqrt64(math.nan(f64))));
276298}
299
300fn sqrt_int(comptime T: type, value: T) -> @IntType(false, T.bit_count / 2) {
301 var op = value;
302 var res: T = 0;
303 var one: T = 1 << (T.bit_count - 2);
304
305 // "one" starts at the highest power of four <= than the argument.
306 while (one > op) {
307 one >>= 2;
308 }
309
310 while (one != 0) {
311 if (op >= res + one) {
312 op -= res + one;
313 res += 2 * one;
314 }
315 res >>= 1;
316 one >>= 2;
317 }
318
319 const ResultType = @IntType(false, T.bit_count / 2);
320 return ResultType(res);
321}
322
323test "math.sqrt_int" {
324 assert(sqrt_int(u32, 3) == 1);
325 assert(sqrt_int(u32, 4) == 2);
326 assert(sqrt_int(u32, 5) == 2);
327 assert(sqrt_int(u32, 8) == 2);
328 assert(sqrt_int(u32, 9) == 3);
329 assert(sqrt_int(u32, 10) == 3);
330}
std/mem.zig+37
......@@ -527,3 +527,40 @@ pub fn max(comptime T: type, slice: []const T) -> T {
527527test "mem.max" {
528528 assert(max(u8, "abcdefg") == 'g');
529529}
530
531pub fn swap(comptime T: type, a: &T, b: &T) {
532 const tmp = *a;
533 *a = *b;
534 *b = tmp;
535}
536
537/// In-place order reversal of a slice
538pub fn reverse(comptime T: type, items: []T) {
539 var i: usize = 0;
540 const end = items.len / 2;
541 while (i < end) : (i += 1) {
542 swap(T, &items[i], &items[items.len - i - 1]);
543 }
544}
545
546test "std.mem.reverse" {
547 var arr = []i32{ 5, 3, 1, 2, 4 };
548 reverse(i32, arr[0..]);
549
550 assert(eql(i32, arr, []i32{ 4, 2, 1, 3, 5 }))
551}
552
553/// In-place rotation of the values in an array ([0 1 2 3] becomes [1 2 3 0] if we rotate by 1)
554/// Assumes 0 <= amount <= items.len
555pub fn rotate(comptime T: type, items: []T, amount: usize) {
556 reverse(T, items[0..amount]);
557 reverse(T, items[amount..]);
558 reverse(T, items);
559}
560
561test "std.mem.rotate" {
562 var arr = []i32{ 5, 3, 1, 2, 4 };
563 rotate(i32, arr[0..], 2);
564
565 assert(eql(i32, arr, []i32{ 1, 2, 4, 5, 3 }))
566}
std/sort.zig+979-50
......@@ -1,75 +1,965 @@
1const assert = @import("debug.zig").assert;
2const mem = @import("mem.zig");
3const math = @import("math/index.zig");
1const std = @import("index.zig");
2const assert = std.debug.assert;
3const mem = std.mem;
4const math = std.math;
45
5pub const Cmp = math.Cmp;
6
7/// Stable sort using O(1) space. Currently implemented as insertion sort.
8pub fn sort_stable(comptime T: type, array: []T, comptime cmp: fn(a: &const T, b: &const T)->Cmp) {
9 {var i: usize = 1; while (i < array.len) : (i += 1) {
10 const x = array[i];
6/// Stable in-place sort. O(n) best case, O(pow(n, 2)) worst case. O(1) memory (no allocator required).
7pub fn insertionSort(comptime T: type, items: []T, lessThan: fn(lhs: &const T, rhs: &const T)->bool) {
8 {var i: usize = 1; while (i < items.len) : (i += 1) {
9 const x = items[i];
1110 var j: usize = i;
12 while (j > 0 and cmp(array[j - 1], x) == Cmp.Greater) : (j -= 1) {
13 array[j] = array[j - 1];
11 while (j > 0 and lessThan(x, items[j - 1])) : (j -= 1) {
12 items[j] = items[j - 1];
1413 }
15 array[j] = x;
14 items[j] = x;
1615 }}
1716}
1817
19/// Unstable sort using O(n) stack space. Currently implemented as quicksort.
20pub fn sort(comptime T: type, array: []T, comptime cmp: fn(a: &const T, b: &const T)->Cmp) {
21 if (array.len > 0) {
22 quicksort(T, array, 0, array.len - 1, cmp);
18const Range = struct {
19 start: usize,
20 end: usize,
21
22 fn init(start: usize, end: usize) -> Range {
23 return Range { .start = start, .end = end };
24 }
25
26 fn length(self: &const Range) -> usize {
27 return self.end - self.start;
28 }
29};
30
31
32const Iterator = struct {
33 size: usize,
34 power_of_two: usize,
35 numerator: usize,
36 decimal: usize,
37 denominator: usize,
38 decimal_step: usize,
39 numerator_step: usize,
40
41 fn init(size2: usize, min_level: usize) -> Iterator {
42 const power_of_two = math.floorPowerOfTwo(usize, size2);
43 const denominator = power_of_two / min_level;
44 return Iterator {
45 .numerator = 0,
46 .decimal = 0,
47 .size = size2,
48 .power_of_two = power_of_two,
49 .denominator = denominator,
50 .decimal_step = size2 / denominator,
51 .numerator_step = size2 % denominator,
52 };
53 }
54
55 fn begin(self: &Iterator) {
56 self.numerator = 0;
57 self.decimal = 0;
58 }
59
60 fn nextRange(self: &Iterator) -> Range {
61 const start = self.decimal;
62
63 self.decimal += self.decimal_step;
64 self.numerator += self.numerator_step;
65 if (self.numerator >= self.denominator) {
66 self.numerator -= self.denominator;
67 self.decimal += 1;
68 }
69
70 return Range {.start = start, .end = self.decimal};
71 }
72
73 fn finished(self: &Iterator) -> bool {
74 return self.decimal >= self.size;
75 }
76
77 fn nextLevel(self: &Iterator) -> bool {
78 self.decimal_step += self.decimal_step;
79 self.numerator_step += self.numerator_step;
80 if (self.numerator_step >= self.denominator) {
81 self.numerator_step -= self.denominator;
82 self.decimal_step += 1;
83 }
84
85 return (self.decimal_step < self.size);
86 }
87
88 fn length(self: &Iterator) -> usize {
89 return self.decimal_step;
90 }
91};
92
93const Pull = struct {
94 from: usize,
95 to: usize,
96 count: usize,
97 range: Range,
98};
99
100/// Stable in-place sort. O(n) best case, O(n*log(n)) worst case and average case. O(1) memory (no allocator required).
101/// Currently implemented as block sort.
102pub fn sort(comptime T: type, items: []T, lessThan: fn(lhs: &const T, rhs: &const T)->bool) {
103 // Implementation ported from https://github.com/BonzaiThePenguin/WikiSort/blob/master/WikiSort.c
104 var cache: [512]T = undefined;
105
106 if (items.len < 4) {
107 if (items.len == 3) {
108 // hard coded insertion sort
109 if (lessThan(items[1], items[0])) mem.swap(T, &items[0], &items[1]);
110 if (lessThan(items[2], items[1])) {
111 mem.swap(T, &items[1], &items[2]);
112 if (lessThan(items[1], items[0])) mem.swap(T, &items[0], &items[1]);
113 }
114 } else if (items.len == 2) {
115 if (lessThan(items[1], items[0])) mem.swap(T, &items[0], &items[1]);
116 }
117 return;
118 }
119
120 // sort groups of 4-8 items at a time using an unstable sorting network,
121 // but keep track of the original item orders to force it to be stable
122 // http://pages.ripco.net/~jgamble/nw.html
123 var iterator = Iterator.init(items.len, 4);
124 while (!iterator.finished()) {
125 var order = []u8{0, 1, 2, 3, 4, 5, 6, 7};
126 const range = iterator.nextRange();
127
128 const sliced_items = items[range.start..];
129 switch (range.length()) {
130 8 => {
131 swap(T, sliced_items, lessThan, &order, 0, 1);
132 swap(T, sliced_items, lessThan, &order, 2, 3);
133 swap(T, sliced_items, lessThan, &order, 4, 5);
134 swap(T, sliced_items, lessThan, &order, 6, 7);
135 swap(T, sliced_items, lessThan, &order, 0, 2);
136 swap(T, sliced_items, lessThan, &order, 1, 3);
137 swap(T, sliced_items, lessThan, &order, 4, 6);
138 swap(T, sliced_items, lessThan, &order, 5, 7);
139 swap(T, sliced_items, lessThan, &order, 1, 2);
140 swap(T, sliced_items, lessThan, &order, 5, 6);
141 swap(T, sliced_items, lessThan, &order, 0, 4);
142 swap(T, sliced_items, lessThan, &order, 3, 7);
143 swap(T, sliced_items, lessThan, &order, 1, 5);
144 swap(T, sliced_items, lessThan, &order, 2, 6);
145 swap(T, sliced_items, lessThan, &order, 1, 4);
146 swap(T, sliced_items, lessThan, &order, 3, 6);
147 swap(T, sliced_items, lessThan, &order, 2, 4);
148 swap(T, sliced_items, lessThan, &order, 3, 5);
149 swap(T, sliced_items, lessThan, &order, 3, 4);
150 },
151 7 => {
152 swap(T, sliced_items, lessThan, &order, 1, 2);
153 swap(T, sliced_items, lessThan, &order, 3, 4);
154 swap(T, sliced_items, lessThan, &order, 5, 6);
155 swap(T, sliced_items, lessThan, &order, 0, 2);
156 swap(T, sliced_items, lessThan, &order, 3, 5);
157 swap(T, sliced_items, lessThan, &order, 4, 6);
158 swap(T, sliced_items, lessThan, &order, 0, 1);
159 swap(T, sliced_items, lessThan, &order, 4, 5);
160 swap(T, sliced_items, lessThan, &order, 2, 6);
161 swap(T, sliced_items, lessThan, &order, 0, 4);
162 swap(T, sliced_items, lessThan, &order, 1, 5);
163 swap(T, sliced_items, lessThan, &order, 0, 3);
164 swap(T, sliced_items, lessThan, &order, 2, 5);
165 swap(T, sliced_items, lessThan, &order, 1, 3);
166 swap(T, sliced_items, lessThan, &order, 2, 4);
167 swap(T, sliced_items, lessThan, &order, 2, 3);
168 },
169 6 => {
170 swap(T, sliced_items, lessThan, &order, 1, 2);
171 swap(T, sliced_items, lessThan, &order, 4, 5);
172 swap(T, sliced_items, lessThan, &order, 0, 2);
173 swap(T, sliced_items, lessThan, &order, 3, 5);
174 swap(T, sliced_items, lessThan, &order, 0, 1);
175 swap(T, sliced_items, lessThan, &order, 3, 4);
176 swap(T, sliced_items, lessThan, &order, 2, 5);
177 swap(T, sliced_items, lessThan, &order, 0, 3);
178 swap(T, sliced_items, lessThan, &order, 1, 4);
179 swap(T, sliced_items, lessThan, &order, 2, 4);
180 swap(T, sliced_items, lessThan, &order, 1, 3);
181 swap(T, sliced_items, lessThan, &order, 2, 3);
182 },
183 5 => {
184 swap(T, sliced_items, lessThan, &order, 0, 1);
185 swap(T, sliced_items, lessThan, &order, 3, 4);
186 swap(T, sliced_items, lessThan, &order, 2, 4);
187 swap(T, sliced_items, lessThan, &order, 2, 3);
188 swap(T, sliced_items, lessThan, &order, 1, 4);
189 swap(T, sliced_items, lessThan, &order, 0, 3);
190 swap(T, sliced_items, lessThan, &order, 0, 2);
191 swap(T, sliced_items, lessThan, &order, 1, 3);
192 swap(T, sliced_items, lessThan, &order, 1, 2);
193 },
194 4 => {
195 swap(T, sliced_items, lessThan, &order, 0, 1);
196 swap(T, sliced_items, lessThan, &order, 2, 3);
197 swap(T, sliced_items, lessThan, &order, 0, 2);
198 swap(T, sliced_items, lessThan, &order, 1, 3);
199 swap(T, sliced_items, lessThan, &order, 1, 2);
200 },
201 else => {},
202 }
203 }
204 if (items.len < 8) return;
205
206 // then merge sort the higher levels, which can be 8-15, 16-31, 32-63, 64-127, etc.
207 while (true) {
208 // if every A and B block will fit into the cache, use a special branch specifically for merging with the cache
209 // (we use < rather than <= since the block size might be one more than iterator.length())
210 if (iterator.length() < cache.len) {
211 // if four subarrays fit into the cache, it's faster to merge both pairs of subarrays into the cache,
212 // then merge the two merged subarrays from the cache back into the original array
213 if ((iterator.length() + 1) * 4 <= cache.len and iterator.length() * 4 <= items.len) {
214 iterator.begin();
215 while (!iterator.finished()) {
216 // merge A1 and B1 into the cache
217 var A1 = iterator.nextRange();
218 var B1 = iterator.nextRange();
219 var A2 = iterator.nextRange();
220 var B2 = iterator.nextRange();
221
222 if (lessThan(items[B1.end - 1], items[A1.start])) {
223 // the two ranges are in reverse order, so copy them in reverse order into the cache
224 mem.copy(T, cache[B1.length()..], items[A1.start..A1.end]);
225 mem.copy(T, cache[0..], items[B1.start..B1.end]);
226 } else if (lessThan(items[B1.start], items[A1.end - 1])) {
227 // these two ranges weren't already in order, so merge them into the cache
228 mergeInto(T, items, A1, B1, lessThan, cache[0..]);
229 } else {
230 // if A1, B1, A2, and B2 are all in order, skip doing anything else
231 if (!lessThan(items[B2.start], items[A2.end - 1]) and !lessThan(items[A2.start], items[B1.end - 1])) continue;
232
233 // copy A1 and B1 into the cache in the same order
234 mem.copy(T, cache[0..], items[A1.start..A1.end]);
235 mem.copy(T, cache[A1.length()..], items[B1.start..B1.end]);
236 }
237 A1 = Range.init(A1.start, B1.end);
238
239 // merge A2 and B2 into the cache
240 if (lessThan(items[B2.end - 1], items[A2.start])) {
241 // the two ranges are in reverse order, so copy them in reverse order into the cache
242 mem.copy(T, cache[A1.length() + B2.length()..], items[A2.start..A2.end]);
243 mem.copy(T, cache[A1.length()..], items[B2.start..B2.end]);
244 } else if (lessThan(items[B2.start], items[A2.end - 1])) {
245 // these two ranges weren't already in order, so merge them into the cache
246 mergeInto(T, items, A2, B2, lessThan, cache[A1.length()..]);
247 } else {
248 // copy A2 and B2 into the cache in the same order
249 mem.copy(T, cache[A1.length()..], items[A2.start..A2.end]);
250 mem.copy(T, cache[A1.length() + A2.length()..], items[B2.start..B2.end]);
251 }
252 A2 = Range.init(A2.start, B2.end);
253
254 // merge A1 and A2 from the cache into the items
255 const A3 = Range.init(0, A1.length());
256 const B3 = Range.init(A1.length(), A1.length() + A2.length());
257
258 if (lessThan(cache[B3.end - 1], cache[A3.start])) {
259 // the two ranges are in reverse order, so copy them in reverse order into the items
260 mem.copy(T, items[A1.start + A2.length()..], cache[A3.start..A3.end]);
261 mem.copy(T, items[A1.start..], cache[B3.start..B3.end]);
262 } else if (lessThan(cache[B3.start], cache[A3.end - 1])) {
263 // these two ranges weren't already in order, so merge them back into the items
264 mergeInto(T, cache[0..], A3, B3, lessThan, items[A1.start..]);
265 } else {
266 // copy A3 and B3 into the items in the same order
267 mem.copy(T, items[A1.start..], cache[A3.start..A3.end]);
268 mem.copy(T, items[A1.start + A1.length()..], cache[B3.start..B3.end]);
269 }
270 }
271
272 // we merged two levels at the same time, so we're done with this level already
273 // (iterator.nextLevel() is called again at the bottom of this outer merge loop)
274 _ = iterator.nextLevel();
275
276 } else {
277 iterator.begin();
278 while (!iterator.finished()) {
279 var A = iterator.nextRange();
280 var B = iterator.nextRange();
281
282 if (lessThan(items[B.end - 1], items[A.start])) {
283 // the two ranges are in reverse order, so a simple rotation should fix it
284 mem.rotate(T, items[A.start..B.end], A.length());
285 } else if (lessThan(items[B.start], items[A.end - 1])) {
286 // these two ranges weren't already in order, so we'll need to merge them!
287 mem.copy(T, cache[0..], items[A.start..A.end]);
288 mergeExternal(T, items, A, B, lessThan, cache[0..]);
289 }
290 }
291 }
292 } else {
293 // this is where the in-place merge logic starts!
294 // 1. pull out two internal buffers each containing √A unique values
295 // 1a. adjust block_size and buffer_size if we couldn't find enough unique values
296 // 2. loop over the A and B subarrays within this level of the merge sort
297 // 3. break A and B into blocks of size 'block_size'
298 // 4. "tag" each of the A blocks with values from the first internal buffer
299 // 5. roll the A blocks through the B blocks and drop/rotate them where they belong
300 // 6. merge each A block with any B values that follow, using the cache or the second internal buffer
301 // 7. sort the second internal buffer if it exists
302 // 8. redistribute the two internal buffers back into the items
303
304 var block_size: usize = math.sqrt(iterator.length());
305 var buffer_size = iterator.length()/block_size + 1;
306
307 // as an optimization, we really only need to pull out the internal buffers once for each level of merges
308 // after that we can reuse the same buffers over and over, then redistribute it when we're finished with this level
309 var A: Range = undefined;
310 var B: Range = undefined;
311 var index: usize = 0;
312 var last: usize = 0;
313 var count: usize = 0;
314 var find: usize = 0;
315 var start: usize = 0;
316 var pull_index: usize = 0;
317 var pull = []Pull{
318 Pull {.from = 0, .to = 0, .count = 0, .range = Range.init(0, 0),},
319 Pull {.from = 0, .to = 0, .count = 0, .range = Range.init(0, 0),},
320 };
321
322 var buffer1 = Range.init(0, 0);
323 var buffer2 = Range.init(0, 0);
324
325 // find two internal buffers of size 'buffer_size' each
326 find = buffer_size + buffer_size;
327 var find_separately = false;
328
329 if (block_size <= cache.len) {
330 // if every A block fits into the cache then we won't need the second internal buffer,
331 // so we really only need to find 'buffer_size' unique values
332 find = buffer_size;
333 } else if (find > iterator.length()) {
334 // we can't fit both buffers into the same A or B subarray, so find two buffers separately
335 find = buffer_size;
336 find_separately = true;
337 }
338
339 // we need to find either a single contiguous space containing 2√A unique values (which will be split up into two buffers of size √A each),
340 // or we need to find one buffer of < 2√A unique values, and a second buffer of √A unique values,
341 // OR if we couldn't find that many unique values, we need the largest possible buffer we can get
342
343 // in the case where it couldn't find a single buffer of at least √A unique values,
344 // all of the Merge steps must be replaced by a different merge algorithm (MergeInPlace)
345 iterator.begin();
346 while (!iterator.finished()) {
347 A = iterator.nextRange();
348 B = iterator.nextRange();
349
350 // just store information about where the values will be pulled from and to,
351 // as well as how many values there are, to create the two internal buffers
352
353 // check A for the number of unique values we need to fill an internal buffer
354 // these values will be pulled out to the start of A
355 last = A.start;
356 count = 1;
357 while (count < find) : ({last = index; count += 1}) {
358 index = findLastForward(T, items, items[last], Range.init(last + 1, A.end), lessThan, find - count);
359 if (index == A.end) break;
360 }
361 index = last;
362
363 if (count >= buffer_size) {
364 // keep track of the range within the items where we'll need to "pull out" these values to create the internal buffer
365 pull[pull_index] = Pull {
366 .range = Range.init(A.start, B.end),
367 .count = count,
368 .from = index,
369 .to = A.start,
370 };
371 pull_index = 1;
372
373 if (count == buffer_size + buffer_size) {
374 // we were able to find a single contiguous section containing 2√A unique values,
375 // so this section can be used to contain both of the internal buffers we'll need
376 buffer1 = Range.init(A.start, A.start + buffer_size);
377 buffer2 = Range.init(A.start + buffer_size, A.start + count);
378 break;
379 } else if (find == buffer_size + buffer_size) {
380 // we found a buffer that contains at least √A unique values, but did not contain the full 2√A unique values,
381 // so we still need to find a second separate buffer of at least √A unique values
382 buffer1 = Range.init(A.start, A.start + count);
383 find = buffer_size;
384 } else if (block_size <= cache.len) {
385 // we found the first and only internal buffer that we need, so we're done!
386 buffer1 = Range.init(A.start, A.start + count);
387 break;
388 } else if (find_separately) {
389 // found one buffer, but now find the other one
390 buffer1 = Range.init(A.start, A.start + count);
391 find_separately = false;
392 } else {
393 // we found a second buffer in an 'A' subarray containing √A unique values, so we're done!
394 buffer2 = Range.init(A.start, A.start + count);
395 break;
396 }
397 } else if (pull_index == 0 and count > buffer1.length()) {
398 // keep track of the largest buffer we were able to find
399 buffer1 = Range.init(A.start, A.start + count);
400 pull[pull_index] = Pull {
401 .range = Range.init(A.start, B.end),
402 .count = count,
403 .from = index,
404 .to = A.start,
405 };
406 }
407
408 // check B for the number of unique values we need to fill an internal buffer
409 // these values will be pulled out to the end of B
410 last = B.end - 1;
411 count = 1;
412 while (count < find) : ({last = index - 1; count += 1}) {
413 index = findFirstBackward(T, items, items[last], Range.init(B.start, last), lessThan, find - count);
414 if (index == B.start) break;
415 }
416 index = last;
417
418 if (count >= buffer_size) {
419 // keep track of the range within the items where we'll need to "pull out" these values to create the internal buffe
420 pull[pull_index] = Pull {
421 .range = Range.init(A.start, B.end),
422 .count = count,
423 .from = index,
424 .to = B.end,
425 };
426 pull_index = 1;
427
428 if (count == buffer_size + buffer_size) {
429 // we were able to find a single contiguous section containing 2√A unique values,
430 // so this section can be used to contain both of the internal buffers we'll need
431 buffer1 = Range.init(B.end - count, B.end - buffer_size);
432 buffer2 = Range.init(B.end - buffer_size, B.end);
433 break;
434 } else if (find == buffer_size + buffer_size) {
435 // we found a buffer that contains at least √A unique values, but did not contain the full 2√A unique values,
436 // so we still need to find a second separate buffer of at least √A unique values
437 buffer1 = Range.init(B.end - count, B.end);
438 find = buffer_size;
439 } else if (block_size <= cache.len) {
440 // we found the first and only internal buffer that we need, so we're done!
441 buffer1 = Range.init(B.end - count, B.end);
442 break;
443 } else if (find_separately) {
444 // found one buffer, but now find the other one
445 buffer1 = Range.init(B.end - count, B.end);
446 find_separately = false;
447 } else {
448 // buffer2 will be pulled out from a 'B' subarray, so if the first buffer was pulled out from the corresponding 'A' subarray,
449 // we need to adjust the end point for that A subarray so it knows to stop redistributing its values before reaching buffer2
450 if (pull[0].range.start == A.start) pull[0].range.end -= pull[1].count;
451
452 // we found a second buffer in an 'B' subarray containing √A unique values, so we're done!
453 buffer2 = Range.init(B.end - count, B.end);
454 break;
455 }
456 } else if (pull_index == 0 and count > buffer1.length()) {
457 // keep track of the largest buffer we were able to find
458 buffer1 = Range.init(B.end - count, B.end);
459 pull[pull_index] = Pull {
460 .range = Range.init(A.start, B.end),
461 .count = count,
462 .from = index,
463 .to = B.end,
464 };
465 }
466 }
467
468 // pull out the two ranges so we can use them as internal buffers
469 pull_index = 0;
470 while (pull_index < 2) : (pull_index += 1) {
471 const length = pull[pull_index].count;
472
473 if (pull[pull_index].to < pull[pull_index].from) {
474 // we're pulling the values out to the left, which means the start of an A subarray
475 index = pull[pull_index].from;
476 count = 1;
477 while (count < length) : (count += 1) {
478 index = findFirstBackward(T, items, items[index - 1], Range.init(pull[pull_index].to, pull[pull_index].from - (count - 1)), lessThan, length - count);
479 const range = Range.init(index + 1, pull[pull_index].from + 1);
480 mem.rotate(T, items[range.start..range.end], range.length() - count);
481 pull[pull_index].from = index + count;
482 }
483 } else if (pull[pull_index].to > pull[pull_index].from) {
484 // we're pulling values out to the right, which means the end of a B subarray
485 index = pull[pull_index].from + 1;
486 count = 1;
487 while (count < length) : (count += 1) {
488 index = findLastForward(T, items, items[index], Range.init(index, pull[pull_index].to), lessThan, length - count);
489 const range = Range.init(pull[pull_index].from, index - 1);
490 mem.rotate(T, items[range.start..range.end], count);
491 pull[pull_index].from = index - 1 - count;
492 }
493 }
494 }
495
496 // adjust block_size and buffer_size based on the values we were able to pull out
497 buffer_size = buffer1.length();
498 block_size = iterator.length()/buffer_size + 1;
499
500 // the first buffer NEEDS to be large enough to tag each of the evenly sized A blocks,
501 // so this was originally here to test the math for adjusting block_size above
502 // assert((iterator.length() + 1)/block_size <= buffer_size);
503
504 // now that the two internal buffers have been created, it's time to merge each A+B combination at this level of the merge sort!
505 iterator.begin();
506 while (!iterator.finished()) {
507 A = iterator.nextRange();
508 B = iterator.nextRange();
509
510 // remove any parts of A or B that are being used by the internal buffers
511 start = A.start;
512 if (start == pull[0].range.start) {
513 if (pull[0].from > pull[0].to) {
514 A.start += pull[0].count;
515
516 // if the internal buffer takes up the entire A or B subarray, then there's nothing to merge
517 // this only happens for very small subarrays, like √4 = 2, 2 * (2 internal buffers) = 4,
518 // which also only happens when cache.len is small or 0 since it'd otherwise use MergeExternal
519 if (A.length() == 0) continue;
520 } else if (pull[0].from < pull[0].to) {
521 B.end -= pull[0].count;
522 if (B.length() == 0) continue;
523 }
524 }
525 if (start == pull[1].range.start) {
526 if (pull[1].from > pull[1].to) {
527 A.start += pull[1].count;
528 if (A.length() == 0) continue;
529 } else if (pull[1].from < pull[1].to) {
530 B.end -= pull[1].count;
531 if (B.length() == 0) continue;
532 }
533 }
534
535 if (lessThan(items[B.end - 1], items[A.start])) {
536 // the two ranges are in reverse order, so a simple rotation should fix it
537 mem.rotate(T, items[A.start..B.end], A.length());
538 } else if (lessThan(items[A.end], items[A.end - 1])) {
539 // these two ranges weren't already in order, so we'll need to merge them!
540 var findA: usize = undefined;
541
542 // break the remainder of A into blocks. firstA is the uneven-sized first A block
543 var blockA = Range.init(A.start, A.end);
544 var firstA = Range.init(A.start, A.start + blockA.length() % block_size);
545
546 // swap the first value of each A block with the value in buffer1
547 var indexA = buffer1.start;
548 index = firstA.end;
549 while (index < blockA.end) : ({indexA += 1; index += block_size}) {
550 mem.swap(T, &items[indexA], &items[index]);
551 }
552
553 // start rolling the A blocks through the B blocks!
554 // whenever we leave an A block behind, we'll need to merge the previous A block with any B blocks that follow it, so track that information as well
555 var lastA = firstA;
556 var lastB = Range.init(0, 0);
557 var blockB = Range.init(B.start, B.start + math.min(block_size, B.length()));
558 blockA.start += firstA.length();
559 indexA = buffer1.start;
560
561 // if the first unevenly sized A block fits into the cache, copy it there for when we go to Merge it
562 // otherwise, if the second buffer is available, block swap the contents into that
563 if (lastA.length() <= cache.len) {
564 mem.copy(T, cache[0..], items[lastA.start..lastA.end]);
565 } else if (buffer2.length() > 0) {
566 blockSwap(T, items, lastA.start, buffer2.start, lastA.length());
567 }
568
569 if (blockA.length() > 0) {
570 while (true) {
571 // if there's a previous B block and the first value of the minimum A block is <= the last value of the previous B block,
572 // then drop that minimum A block behind. or if there are no B blocks left then keep dropping the remaining A blocks.
573 if ((lastB.length() > 0 and !lessThan(items[lastB.end - 1], items[indexA])) or blockB.length() == 0) {
574 // figure out where to split the previous B block, and rotate it at the split
575 const B_split = binaryFirst(T, items, items[indexA], lastB, lessThan);
576 const B_remaining = lastB.end - B_split;
577
578 // swap the minimum A block to the beginning of the rolling A blocks
579 var minA = blockA.start;
580 findA = minA + block_size;
581 while (findA < blockA.end) : (findA += block_size) {
582 if (lessThan(items[findA], items[minA])) {
583 minA = findA;
584 }
585 }
586 blockSwap(T, items, blockA.start, minA, block_size);
587
588 // swap the first item of the previous A block back with its original value, which is stored in buffer1
589 mem.swap(T, &items[blockA.start], &items[indexA]);
590 indexA += 1;
591
592 // locally merge the previous A block with the B values that follow it
593 // if lastA fits into the external cache we'll use that (with MergeExternal),
594 // or if the second internal buffer exists we'll use that (with MergeInternal),
595 // or failing that we'll use a strictly in-place merge algorithm (MergeInPlace)
596
597 if (lastA.length() <= cache.len) {
598 mergeExternal(T, items, lastA, Range.init(lastA.end, B_split), lessThan, cache[0..]);
599 } else if (buffer2.length() > 0) {
600 mergeInternal(T, items, lastA, Range.init(lastA.end, B_split), lessThan, buffer2);
601 } else {
602 mergeInPlace(T, items, lastA, Range.init(lastA.end, B_split), lessThan);
603 }
604
605 if (buffer2.length() > 0 or block_size <= cache.len) {
606 // copy the previous A block into the cache or buffer2, since that's where we need it to be when we go to merge it anyway
607 if (block_size <= cache.len) {
608 mem.copy(T, cache[0..], items[blockA.start..blockA.start + block_size]);
609 } else {
610 blockSwap(T, items, blockA.start, buffer2.start, block_size);
611 }
612
613 // this is equivalent to rotating, but faster
614 // the area normally taken up by the A block is either the contents of buffer2, or data we don't need anymore since we memcopied it
615 // either way, we don't need to retain the order of those items, so instead of rotating we can just block swap B to where it belongs
616 blockSwap(T, items, B_split, blockA.start + block_size - B_remaining, B_remaining);
617 } else {
618 // we are unable to use the 'buffer2' trick to speed up the rotation operation since buffer2 doesn't exist, so perform a normal rotation
619 mem.rotate(T, items[B_split..blockA.start + block_size], blockA.start - B_split);
620 }
621
622 // update the range for the remaining A blocks, and the range remaining from the B block after it was split
623 lastA = Range.init(blockA.start - B_remaining, blockA.start - B_remaining + block_size);
624 lastB = Range.init(lastA.end, lastA.end + B_remaining);
625
626 // if there are no more A blocks remaining, this step is finished!
627 blockA.start += block_size;
628 if (blockA.length() == 0)
629 break;
630
631 } else if (blockB.length() < block_size) {
632 // move the last B block, which is unevenly sized, to before the remaining A blocks, by using a rotation
633 // the cache is disabled here since it might contain the contents of the previous A block
634 mem.rotate(T, items[blockA.start..blockB.end], blockB.start - blockA.start);
635
636 lastB = Range.init(blockA.start, blockA.start + blockB.length());
637 blockA.start += blockB.length();
638 blockA.end += blockB.length();
639 blockB.end = blockB.start;
640 } else {
641 // roll the leftmost A block to the end by swapping it with the next B block
642 blockSwap(T, items, blockA.start, blockB.start, block_size);
643 lastB = Range.init(blockA.start, blockA.start + block_size);
644
645 blockA.start += block_size;
646 blockA.end += block_size;
647 blockB.start += block_size;
648
649 if (blockB.end > B.end - block_size) {
650 blockB.end = B.end;
651 } else {
652 blockB.end += block_size;
653 }
654 }
655 }
656 }
657
658 // merge the last A block with the remaining B values
659 if (lastA.length() <= cache.len) {
660 mergeExternal(T, items, lastA, Range.init(lastA.end, B.end), lessThan, cache[0..]);
661 } else if (buffer2.length() > 0) {
662 mergeInternal(T, items, lastA, Range.init(lastA.end, B.end), lessThan, buffer2);
663 } else {
664 mergeInPlace(T, items, lastA, Range.init(lastA.end, B.end), lessThan);
665 }
666 }
667 }
668
669 // when we're finished with this merge step we should have the one or two internal buffers left over, where the second buffer is all jumbled up
670 // insertion sort the second buffer, then redistribute the buffers back into the items using the opposite process used for creating the buffer
671
672 // while an unstable sort like quicksort could be applied here, in benchmarks it was consistently slightly slower than a simple insertion sort,
673 // even for tens of millions of items. this may be because insertion sort is quite fast when the data is already somewhat sorted, like it is here
674 insertionSort(T, items[buffer2.start..buffer2.end], lessThan);
675
676 pull_index = 0;
677 while (pull_index < 2) : (pull_index += 1) {
678 var unique = pull[pull_index].count * 2;
679 if (pull[pull_index].from > pull[pull_index].to) {
680 // the values were pulled out to the left, so redistribute them back to the right
681 var buffer = Range.init(pull[pull_index].range.start, pull[pull_index].range.start + pull[pull_index].count);
682 while (buffer.length() > 0) {
683 index = findFirstForward(T, items, items[buffer.start], Range.init(buffer.end, pull[pull_index].range.end), lessThan, unique);
684 const amount = index - buffer.end;
685 mem.rotate(T, items[buffer.start..index], buffer.length());
686 buffer.start += (amount + 1);
687 buffer.end += amount;
688 unique -= 2;
689 }
690 } else if (pull[pull_index].from < pull[pull_index].to) {
691 // the values were pulled out to the right, so redistribute them back to the left
692 var buffer = Range.init(pull[pull_index].range.end - pull[pull_index].count, pull[pull_index].range.end);
693 while (buffer.length() > 0) {
694 index = findLastBackward(T, items, items[buffer.end - 1], Range.init(pull[pull_index].range.start, buffer.start), lessThan, unique);
695 const amount = buffer.start - index;
696 mem.rotate(T, items[index..buffer.end], amount);
697 buffer.start -= amount;
698 buffer.end -= (amount + 1);
699 unique -= 2;
700 }
701 }
702 }
703 }
704
705 // double the size of each A and B subarray that will be merged in the next level
706 if (!iterator.nextLevel()) break;
23707 }
24708}
25709
26fn quicksort(comptime T: type, array: []T, left: usize, right: usize, comptime cmp: fn(a: &const T, b: &const T)->Cmp) {
27 var i = left;
28 var j = right;
29 const p = (i + j) / 2;
710// merge operation without a buffer
711fn mergeInPlace(comptime T: type, items: []T, A_arg: &const Range, B_arg: &const Range, lessThan: fn(&const T,&const T)->bool) {
712 if (A_arg.length() == 0 or B_arg.length() == 0) return;
713
714 // this just repeatedly binary searches into B and rotates A into position.
715 // the paper suggests using the 'rotation-based Hwang and Lin algorithm' here,
716 // but I decided to stick with this because it had better situational performance
717 //
718 // (Hwang and Lin is designed for merging subarrays of very different sizes,
719 // but WikiSort almost always uses subarrays that are roughly the same size)
720 //
721 // normally this is incredibly suboptimal, but this function is only called
722 // when none of the A or B blocks in any subarray contained 2√A unique values,
723 // which places a hard limit on the number of times this will ACTUALLY need
724 // to binary search and rotate.
725 //
726 // according to my analysis the worst case is √A rotations performed on √A items
727 // once the constant factors are removed, which ends up being O(n)
728 //
729 // again, this is NOT a general-purpose solution – it only works well in this case!
730 // kind of like how the O(n^2) insertion sort is used in some places
30731
31 while (i <= j) {
32 while (cmp(array[i], array[p]) == Cmp.Less) {
33 i += 1;
732 var A = *A_arg;
733 var B = *B_arg;
734
735 while (true) {
736 // find the first place in B where the first item in A needs to be inserted
737 const mid = binaryFirst(T, items, items[A.start], B, lessThan);
738
739 // rotate A into place
740 const amount = mid - A.end;
741 mem.rotate(T, items[A.start..mid], A.length());
742 if (B.end == mid) break;
743
744 // calculate the new A and B ranges
745 B.start = mid;
746 A = Range.init(A.start + amount, B.start);
747 A.start = binaryLast(T, items, items[A.start], A, lessThan);
748 if (A.length() == 0) break;
749 }
750}
751
752// merge operation using an internal buffer
753fn mergeInternal(comptime T: type, items: []T, A: &const Range, B: &const Range, lessThan: fn(&const T,&const T)->bool, buffer: &const Range) {
754 // whenever we find a value to add to the final array, swap it with the value that's already in that spot
755 // when this algorithm is finished, 'buffer' will contain its original contents, but in a different order
756 var A_count: usize = 0;
757 var B_count: usize = 0;
758 var insert: usize = 0;
759
760 if (B.length() > 0 and A.length() > 0) {
761 while (true) {
762 if (!lessThan(items[B.start + B_count], items[buffer.start + A_count])) {
763 mem.swap(T, &items[A.start + insert], &items[buffer.start + A_count]);
764 A_count += 1;
765 insert += 1;
766 if (A_count >= A.length()) break;
767 } else {
768 mem.swap(T, &items[A.start + insert], &items[B.start + B_count]);
769 B_count += 1;
770 insert += 1;
771 if (B_count >= B.length()) break;
772 }
34773 }
35 while (cmp(array[j], array[p]) == Cmp.Greater) {
36 j -= 1;
774 }
775
776 // swap the remainder of A into the final array
777 blockSwap(T, items, buffer.start + A_count, A.start + insert, A.length() - A_count);
778}
779
780fn blockSwap(comptime T: type, items: []T, start1: usize, start2: usize, block_size: usize) {
781 var index: usize = 0;
782 while (index < block_size) : (index += 1) {
783 mem.swap(T, &items[start1 + index], &items[start2 + index]);
784 }
785}
786
787// combine a linear search with a binary search to reduce the number of comparisons in situations
788// where have some idea as to how many unique values there are and where the next value might be
789fn findFirstForward(comptime T: type, items: []T, value: &const T, range: &const Range, lessThan: fn(&const T,&const T)->bool, unique: usize) -> usize {
790 if (range.length() == 0) return range.start;
791 const skip = math.max(range.length()/unique, usize(1));
792
793 var index = range.start + skip;
794 while (lessThan(items[index - 1], value)) : (index += skip) {
795 if (index >= range.end - skip) {
796 return binaryFirst(T, items, value, Range.init(index, range.end), lessThan);
37797 }
38 if (i <= j) {
39 const tmp = array[i];
40 array[i] = array[j];
41 array[j] = tmp;
42 i += 1;
43 if (j > 0) j -= 1;
798 }
799
800 return binaryFirst(T, items, value, Range.init(index - skip, index), lessThan);
801}
802
803fn findFirstBackward(comptime T: type, items: []T, value: &const T, range: &const Range, lessThan: fn(&const T,&const T)->bool, unique: usize) -> usize {
804 if (range.length() == 0) return range.start;
805 const skip = math.max(range.length()/unique, usize(1));
806
807 var index = range.end - skip;
808 while (index > range.start and !lessThan(items[index - 1], value)) : (index -= skip) {
809 if (index < range.start + skip) {
810 return binaryFirst(T, items, value, Range.init(range.start, index), lessThan);
44811 }
45812 }
813
814 return binaryFirst(T, items, value, Range.init(index, index + skip), lessThan);
815}
46816
47 if (left < j) quicksort(T, array, left, j, cmp);
48 if (i < right) quicksort(T, array, i, right, cmp);
817fn findLastForward(comptime T: type, items: []T, value: &const T, range: &const Range, lessThan: fn(&const T,&const T)->bool, unique: usize) -> usize {
818 if (range.length() == 0) return range.start;
819 const skip = math.max(range.length()/unique, usize(1));
820
821 var index = range.start + skip;
822 while (!lessThan(value, items[index - 1])) : (index += skip) {
823 if (index >= range.end - skip) {
824 return binaryLast(T, items, value, Range.init(index, range.end), lessThan);
825 }
826 }
827
828 return binaryLast(T, items, value, Range.init(index - skip, index), lessThan);
49829}
50830
51pub fn i32asc(a: &const i32, b: &const i32) -> Cmp {
52 return if (*a > *b) Cmp.Greater else if (*a < *b) Cmp.Less else Cmp.Equal
831fn findLastBackward(comptime T: type, items: []T, value: &const T, range: &const Range, lessThan: fn(&const T,&const T)->bool, unique: usize) -> usize {
832 if (range.length() == 0) return range.start;
833 const skip = math.max(range.length()/unique, usize(1));
834
835 var index = range.end - skip;
836 while (index > range.start and lessThan(value, items[index - 1])) : (index -= skip) {
837 if (index < range.start + skip) {
838 return binaryLast(T, items, value, Range.init(range.start, index), lessThan);
839 }
840 }
841
842 return binaryLast(T, items, value, Range.init(index, index + skip), lessThan);
53843}
54844
55pub fn i32desc(a: &const i32, b: &const i32) -> Cmp {
56 reverse(i32asc(a, b))
845fn binaryFirst(comptime T: type, items: []T, value: &const T, range: &const Range, lessThan: fn(&const T,&const T)->bool) -> usize {
846 var start = range.start;
847 var end = range.end - 1;
848 if (range.start >= range.end) return range.end;
849 while (start < end) {
850 const mid = start + (end - start)/2;
851 if (lessThan(items[mid], value)) {
852 start = mid + 1;
853 } else {
854 end = mid;
855 }
856 }
857 if (start == range.end - 1 and lessThan(items[start], value)) {
858 start += 1;
859 }
860 return start;
57861}
58862
59pub fn u8asc(a: &const u8, b: &const u8) -> Cmp {
60 if (*a > *b) Cmp.Greater else if (*a < *b) Cmp.Less else Cmp.Equal
863fn binaryLast(comptime T: type, items: []T, value: &const T, range: &const Range, lessThan: fn(&const T,&const T)->bool) -> usize {
864 var start = range.start;
865 var end = range.end - 1;
866 if (range.start >= range.end) return range.end;
867 while (start < end) {
868 const mid = start + (end - start)/2;
869 if (!lessThan(value, items[mid])) {
870 start = mid + 1;
871 } else {
872 end = mid;
873 }
874 }
875 if (start == range.end - 1 and !lessThan(value, items[start])) {
876 start += 1;
877 }
878 return start;
61879}
62880
63pub fn u8desc(a: &const u8, b: &const u8) -> Cmp {
64 reverse(u8asc(a, b))
881fn mergeInto(comptime T: type, from: []T, A: &const Range, B: &const Range, lessThan: fn(&const T,&const T)->bool, into: []T) {
882 var A_index: usize = A.start;
883 var B_index: usize = B.start;
884 const A_last = A.end;
885 const B_last = B.end;
886 var insert_index: usize = 0;
887
888 while (true) {
889 if (!lessThan(from[B_index], from[A_index])) {
890 into[insert_index] = from[A_index];
891 A_index += 1;
892 insert_index += 1;
893 if (A_index == A_last) {
894 // copy the remainder of B into the final array
895 mem.copy(T, into[insert_index..], from[B_index..B_last]);
896 break;
897 }
898 } else {
899 into[insert_index] = from[B_index];
900 B_index += 1;
901 insert_index += 1;
902 if (B_index == B_last) {
903 // copy the remainder of A into the final array
904 mem.copy(T, into[insert_index..], from[A_index..A_last]);
905 break;
906 }
907 }
908 }
65909}
66910
67fn reverse(was: Cmp) -> Cmp {
68 if (was == Cmp.Greater) Cmp.Less else if (was == Cmp.Less) Cmp.Greater else Cmp.Equal
911fn mergeExternal(comptime T: type, items: []T, A: &const Range, B: &const Range, lessThan: fn(&const T,&const T)->bool, cache: []T) {
912 // A fits into the cache, so use that instead of the internal buffer
913 var A_index: usize = 0;
914 var B_index: usize = B.start;
915 var insert_index: usize = A.start;
916 const A_last = A.length();
917 const B_last = B.end;
918
919 if (B.length() > 0 and A.length() > 0) {
920 while (true) {
921 if (!lessThan(items[B_index], cache[A_index])) {
922 items[insert_index] = cache[A_index];
923 A_index += 1;
924 insert_index += 1;
925 if (A_index == A_last) break;
926 } else {
927 items[insert_index] = items[B_index];
928 B_index += 1;
929 insert_index += 1;
930 if (B_index == B_last) break;
931 }
932 }
933 }
934
935 // copy the remainder of A into the final array
936 mem.copy(T, items[insert_index..], cache[A_index..A_last]);
69937}
70938
71// ---------------------------------------
72// tests
939fn swap(comptime T: type, items: []T, lessThan: fn(lhs: &const T, rhs: &const T)->bool, order: &[8]u8, x: usize, y: usize) {
940 if (lessThan(items[y], items[x]) or
941 ((*order)[x] > (*order)[y] and !lessThan(items[x], items[y])))
942 {
943 mem.swap(T, &items[x], &items[y]);
944 mem.swap(u8, &(*order)[x], &(*order)[y]);
945 }
946}
947
948fn i32asc(lhs: &const i32, rhs: &const i32) -> bool {
949 return *lhs < *rhs;
950}
951
952fn i32desc(lhs: &const i32, rhs: &const i32) -> bool {
953 return *rhs < *lhs;
954}
955
956fn u8asc(lhs: &const u8, rhs: &const u8) -> bool {
957 return *lhs < *rhs;
958}
959
960fn u8desc(lhs: &const u8, rhs: &const u8) -> bool {
961 return *rhs < *lhs;
962}
73963
74964test "stable sort" {
75965 testStableSort();
......@@ -113,7 +1003,7 @@ fn testStableSort() {
1131003 },
1141004 };
1151005 for (cases) |*case| {
116 sort_stable(IdAndValue, (*case)[0..], cmpByValue);
1006 insertionSort(IdAndValue, (*case)[0..], cmpByValue);
1171007 for (*case) |item, i| {
1181008 assert(item.id == expected[i].id);
1191009 assert(item.value == expected[i].value);
......@@ -121,14 +1011,14 @@ fn testStableSort() {
1211011 }
1221012}
1231013const IdAndValue = struct {
124 id: i32,
1014 id: usize,
1251015 value: i32,
1261016};
127fn cmpByValue(a: &const IdAndValue, b: &const IdAndValue) -> Cmp {
1017fn cmpByValue(a: &const IdAndValue, b: &const IdAndValue) -> bool {
1281018 return i32asc(a.value, b.value);
1291019}
1301020
131test "testSort" {
1021test "std.sort" {
1321022 const u8cases = [][]const []const u8 {
1331023 [][]const u8{"", ""},
1341024 [][]const u8{"a", "a"},
......@@ -164,7 +1054,7 @@ test "testSort" {
1641054 }
1651055}
1661056
167test "testSortDesc" {
1057test "std.sort descending" {
1681058 const rev_cases = [][]const []const i32 {
1691059 [][]const i32{[]i32{}, []i32{}},
1701060 [][]const i32{[]i32{1}, []i32{1}},
......@@ -182,3 +1072,42 @@ test "testSortDesc" {
1821072 assert(mem.eql(i32, slice, case[1]));
1831073 }
1841074}
1075
1076test "another sort case" {
1077 var arr = []i32{ 5, 3, 1, 2, 4 };
1078 sort(i32, arr[0..], i32asc);
1079
1080 assert(mem.eql(i32, arr, []i32{ 1, 2, 3, 4, 5 }))
1081}
1082
1083test "sort fuzz testing" {
1084 var rng = std.rand.Rand.init(0x12345678);
1085 const test_case_count = 10;
1086 var i: usize = 0;
1087 while (i < test_case_count) : (i += 1) {
1088 fuzzTest(&rng);
1089 }
1090}
1091
1092var fixed_buffer_mem: [100 * 1024]u8 = undefined;
1093
1094fn fuzzTest(rng: &std.rand.Rand) {
1095 const array_size = rng.range(usize, 0, 1000);
1096 var fixed_allocator = mem.FixedBufferAllocator.init(fixed_buffer_mem[0..]);
1097 var array = %%fixed_allocator.allocator.alloc(IdAndValue, array_size);
1098 // populate with random data
1099 for (array) |*item, index| {
1100 item.id = index;
1101 item.value = rng.range(i32, 0, 100);
1102 }
1103 sort(IdAndValue, array, cmpByValue);
1104
1105 var index: usize = 1;
1106 while (index < array.len) : (index += 1) {
1107 if (array[index].value == array[index - 1].value) {
1108 assert(array[index].id > array[index - 1].id);
1109 } else {
1110 assert(array[index].value > array[index - 1].value);
1111 }
1112 }
1113}