authorgravatar for alichraghi@proton.meAli Chraghi <alichraghi@proton.me> 2023-05-23 15:33:12+03:30
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-05-23 17:55:59-07:00
log3db3cf77904e664d589287602c14168a7a63f125
tree62bec3710d6b806d54718475bf7a3673ba1a67a5
parentbfe02ff61a8861c269524c60668a3969cb053720

std.sort: add pdqsort and heapsort


37 files changed, 1702 insertions(+), 1291 deletions(-)

lib/std/compress/deflate/huffman_code.zig+2-2
...@@ -93,7 +93,7 @@ pub const HuffmanEncoder = struct {...@@ -93,7 +93,7 @@ pub const HuffmanEncoder = struct {
93 return;93 return;
94 }94 }
95 self.lfs = list;95 self.lfs = list;
96 sort.sort(LiteralNode, self.lfs, {}, byFreq);96 mem.sort(LiteralNode, self.lfs, {}, byFreq);
9797
98 // Get the number of literals for each bit count98 // Get the number of literals for each bit count
99 var bit_count = self.bitCounts(list, max_bits);99 var bit_count = self.bitCounts(list, max_bits);
...@@ -270,7 +270,7 @@ pub const HuffmanEncoder = struct {...@@ -270,7 +270,7 @@ pub const HuffmanEncoder = struct {
270 var chunk = list[list.len - @intCast(u32, bits) ..];270 var chunk = list[list.len - @intCast(u32, bits) ..];
271271
272 self.lns = chunk;272 self.lns = chunk;
273 sort.sort(LiteralNode, self.lns, {}, byLiteral);273 mem.sort(LiteralNode, self.lns, {}, byLiteral);
274274
275 for (chunk) |node| {275 for (chunk) |node| {
276 self.codes[node.literal] = HuffCode{276 self.codes[node.literal] = HuffCode{
lib/std/compress/zstandard/decode/fse.zig+1-1
...@@ -107,7 +107,7 @@ fn buildFseTable(values: []const u16, entries: []Table.Fse) !void {...@@ -107,7 +107,7 @@ fn buildFseTable(values: []const u16, entries: []Table.Fse) !void {
107 position &= entries.len - 1;107 position &= entries.len - 1;
108 }108 }
109 }109 }
110 std.sort.sort(u16, temp_states[0..probability], {}, std.sort.asc(u16));110 std.mem.sort(u16, temp_states[0..probability], {}, std.sort.asc(u16));
111 for (0..probability) |i| {111 for (0..probability) |i| {
112 entries[temp_states[i]] = if (i < double_state_count) Table.Fse{112 entries[temp_states[i]] = if (i < double_state_count) Table.Fse{
113 .symbol = @intCast(u8, symbol),113 .symbol = @intCast(u8, symbol),
lib/std/compress/zstandard/decode/huffman.zig+1-1
...@@ -124,7 +124,7 @@ fn assignSymbols(weight_sorted_prefixed_symbols: []LiteralsSection.HuffmanTree.P...@@ -124,7 +124,7 @@ fn assignSymbols(weight_sorted_prefixed_symbols: []LiteralsSection.HuffmanTree.P
124 };124 };
125 }125 }
126126
127 std.sort.sort(127 std.mem.sort(
128 LiteralsSection.HuffmanTree.PrefixedSymbol,128 LiteralsSection.HuffmanTree.PrefixedSymbol,
129 weight_sorted_prefixed_symbols,129 weight_sorted_prefixed_symbols,
130 weights,130 weights,
lib/std/comptime_string_map.zig+1-1
...@@ -28,7 +28,7 @@ pub fn ComptimeStringMap(comptime V: type, comptime kvs_list: anytype) type {...@@ -28,7 +28,7 @@ pub fn ComptimeStringMap(comptime V: type, comptime kvs_list: anytype) type {
28 sorted_kvs[i] = .{ .key = kv.@"0", .value = {} };28 sorted_kvs[i] = .{ .key = kv.@"0", .value = {} };
29 }29 }
30 }30 }
31 std.sort.sort(KV, &sorted_kvs, {}, lenAsc);31 mem.sort(KV, &sorted_kvs, {}, lenAsc);
32 const min_len = sorted_kvs[0].key.len;32 const min_len = sorted_kvs[0].key.len;
33 const max_len = sorted_kvs[sorted_kvs.len - 1].key.len;33 const max_len = sorted_kvs[sorted_kvs.len - 1].key.len;
34 var len_indexes: [max_len + 1]usize = undefined;34 var len_indexes: [max_len + 1]usize = undefined;
lib/std/debug.zig+1-1
...@@ -1211,7 +1211,7 @@ fn readMachODebugInfo(allocator: mem.Allocator, macho_file: File) !ModuleDebugIn...@@ -1211,7 +1211,7 @@ fn readMachODebugInfo(allocator: mem.Allocator, macho_file: File) !ModuleDebugIn
1211 // Even though lld emits symbols in ascending order, this debug code1211 // Even though lld emits symbols in ascending order, this debug code
1212 // should work for programs linked in any valid way.1212 // should work for programs linked in any valid way.
1213 // This sort is so that we can binary search later.1213 // This sort is so that we can binary search later.
1214 std.sort.sort(MachoSymbol, symbols, {}, MachoSymbol.addressLessThan);1214 mem.sort(MachoSymbol, symbols, {}, MachoSymbol.addressLessThan);
12151215
1216 return ModuleDebugInfo{1216 return ModuleDebugInfo{
1217 .base_address = undefined,1217 .base_address = undefined,
lib/std/enums.zig+1-1
...@@ -1314,7 +1314,7 @@ pub fn EnumIndexer(comptime E: type) type {...@@ -1314,7 +1314,7 @@ pub fn EnumIndexer(comptime E: type) type {
1314 }1314 }
1315 };1315 };
1316 }1316 }
1317 std.sort.sort(EnumField, &fields, {}, ascByValue);1317 std.mem.sort(EnumField, &fields, {}, ascByValue);
1318 const min = fields[0].value;1318 const min = fields[0].value;
1319 const max = fields[fields.len - 1].value;1319 const max = fields[fields.len - 1].value;
1320 const fields_len = fields.len;1320 const fields_len = fields.len;
lib/std/http/Headers.zig+1-1
...@@ -191,7 +191,7 @@ pub const Headers = struct {...@@ -191,7 +191,7 @@ pub const Headers = struct {
191191
192 /// Sorts the headers in lexicographical order.192 /// Sorts the headers in lexicographical order.
193 pub fn sort(headers: *Headers) void {193 pub fn sort(headers: *Headers) void {
194 std.sort.sort(Field, headers.list.items, {}, Field.lessThan);194 std.mem.sort(Field, headers.list.items, {}, Field.lessThan);
195 headers.rebuildIndex();195 headers.rebuildIndex();
196 }196 }
197197
lib/std/mem.zig+28
...@@ -566,6 +566,34 @@ test "zeroInit" {...@@ -566,6 +566,34 @@ test "zeroInit" {
566 }, nested_baz);566 }, nested_baz);
567}567}
568568
569pub fn sort(
570 comptime T: type,
571 items: []T,
572 context: anytype,
573 comptime lessThanFn: fn (@TypeOf(context), lhs: T, rhs: T) bool,
574) void {
575 std.sort.block(T, items, context, lessThanFn);
576}
577
578pub fn sortUnstable(
579 comptime T: type,
580 items: []T,
581 context: anytype,
582 comptime lessThanFn: fn (@TypeOf(context), lhs: T, rhs: T) bool,
583) void {
584 std.sort.pdq(T, items, context, lessThanFn);
585}
586
587/// TODO: currently this just calls `insertionSortContext`. The block sort implementation
588/// in this file needs to be adapted to use the sort context.
589pub fn sortContext(a: usize, b: usize, context: anytype) void {
590 std.sort.insertionContext(a, b, context);
591}
592
593pub fn sortUnstableContext(a: usize, b: usize, context: anytype) void {
594 std.sort.pdqContext(a, b, context);
595}
596
569/// Compares two slices of numbers lexicographically. O(n).597/// Compares two slices of numbers lexicographically. O(n).
570pub fn order(comptime T: type, lhs: []const T, rhs: []const T) math.Order {598pub fn order(comptime T: type, lhs: []const T, rhs: []const T) math.Order {
571 const n = math.min(lhs.len, rhs.len);599 const n = math.min(lhs.len, rhs.len);
lib/std/meta.zig+1-1
...@@ -985,7 +985,7 @@ pub fn declList(comptime Namespace: type, comptime Decl: type) []const *const De...@@ -985,7 +985,7 @@ pub fn declList(comptime Namespace: type, comptime Decl: type) []const *const De
985 for (decls, 0..) |decl, i| {985 for (decls, 0..) |decl, i| {
986 array[i] = &@field(Namespace, decl.name);986 array[i] = &@field(Namespace, decl.name);
987 }987 }
988 std.sort.sort(*const Decl, &array, {}, S.declNameLessThan);988 mem.sort(*const Decl, &array, {}, S.declNameLessThan);
989 return &array;989 return &array;
990 }990 }
991}991}
lib/std/multi_array_list.zig+2-5
...@@ -160,7 +160,7 @@ pub fn MultiArrayList(comptime T: type) type {...@@ -160,7 +160,7 @@ pub fn MultiArrayList(comptime T: type) type {
160 return lhs.alignment > rhs.alignment;160 return lhs.alignment > rhs.alignment;
161 }161 }
162 };162 };
163 std.sort.sort(Data, &data, {}, Sort.lessThan);163 mem.sort(Data, &data, {}, Sort.lessThan);
164 var sizes_bytes: [fields.len]usize = undefined;164 var sizes_bytes: [fields.len]usize = undefined;
165 var field_indexes: [fields.len]usize = undefined;165 var field_indexes: [fields.len]usize = undefined;
166 for (data, 0..) |elem, i| {166 for (data, 0..) |elem, i| {
...@@ -488,10 +488,7 @@ pub fn MultiArrayList(comptime T: type) type {...@@ -488,10 +488,7 @@ pub fn MultiArrayList(comptime T: type) type {
488 }488 }
489 };489 };
490490
491 std.sort.sortContext(self.len, SortContext{491 mem.sortContext(0, self.len, SortContext{ .sub_ctx = ctx, .slice = self.slice() });
492 .sub_ctx = ctx,
493 .slice = self.slice(),
494 });
495 }492 }
496493
497 fn capacityInBytes(capacity: usize) usize {494 fn capacityInBytes(capacity: usize) usize {
lib/std/net.zig+1-1
...@@ -1082,7 +1082,7 @@ fn linuxLookupName(...@@ -1082,7 +1082,7 @@ fn linuxLookupName(
1082 key |= (MAXADDRS - @intCast(i32, i)) << DAS_ORDER_SHIFT;1082 key |= (MAXADDRS - @intCast(i32, i)) << DAS_ORDER_SHIFT;
1083 addr.sortkey = key;1083 addr.sortkey = key;
1084 }1084 }
1085 std.sort.sort(LookupAddr, addrs.items, {}, addrCmpLessThan);1085 mem.sort(LookupAddr, addrs.items, {}, addrCmpLessThan);
1086}1086}
10871087
1088const Policy = struct {1088const Policy = struct {
lib/std/sort.zig+230-1241
...@@ -4,1241 +4,152 @@ const testing = std.testing;...@@ -4,1241 +4,152 @@ const testing = std.testing;
4const mem = std.mem;4const mem = std.mem;
5const math = std.math;5const math = std.math;
66
7pub fn binarySearch(7pub const block = @import("sort/block.zig").block;
8 comptime T: type,8pub const pdq = @import("sort/pdq.zig").pdq;
9 key: anytype,9pub const pdqContext = @import("sort/pdq.zig").pdqContext;
10 items: []const T,
11 context: anytype,
12 comptime compareFn: fn (context: @TypeOf(context), key: @TypeOf(key), mid_item: T) math.Order,
13) ?usize {
14 var left: usize = 0;
15 var right: usize = items.len;
16
17 while (left < right) {
18 // Avoid overflowing in the midpoint calculation
19 const mid = left + (right - left) / 2;
20 // Compare the key with the midpoint element
21 switch (compareFn(context, key, items[mid])) {
22 .eq => return mid,
23 .gt => left = mid + 1,
24 .lt => right = mid,
25 }
26 }
27
28 return null;
29}
30
31test "binarySearch" {
32 const S = struct {
33 fn order_u32(context: void, lhs: u32, rhs: u32) math.Order {
34 _ = context;
35 return math.order(lhs, rhs);
36 }
37 fn order_i32(context: void, lhs: i32, rhs: i32) math.Order {
38 _ = context;
39 return math.order(lhs, rhs);
40 }
41 };
42 try testing.expectEqual(
43 @as(?usize, null),
44 binarySearch(u32, @as(u32, 1), &[_]u32{}, {}, S.order_u32),
45 );
46 try testing.expectEqual(
47 @as(?usize, 0),
48 binarySearch(u32, @as(u32, 1), &[_]u32{1}, {}, S.order_u32),
49 );
50 try testing.expectEqual(
51 @as(?usize, null),
52 binarySearch(u32, @as(u32, 1), &[_]u32{0}, {}, S.order_u32),
53 );
54 try testing.expectEqual(
55 @as(?usize, null),
56 binarySearch(u32, @as(u32, 0), &[_]u32{1}, {}, S.order_u32),
57 );
58 try testing.expectEqual(
59 @as(?usize, 4),
60 binarySearch(u32, @as(u32, 5), &[_]u32{ 1, 2, 3, 4, 5 }, {}, S.order_u32),
61 );
62 try testing.expectEqual(
63 @as(?usize, 0),
64 binarySearch(u32, @as(u32, 2), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.order_u32),
65 );
66 try testing.expectEqual(
67 @as(?usize, 1),
68 binarySearch(i32, @as(i32, -4), &[_]i32{ -7, -4, 0, 9, 10 }, {}, S.order_i32),
69 );
70 try testing.expectEqual(
71 @as(?usize, 3),
72 binarySearch(i32, @as(i32, 98), &[_]i32{ -100, -25, 2, 98, 99, 100 }, {}, S.order_i32),
73 );
74 const R = struct {
75 b: i32,
76 e: i32,
77
78 fn r(b: i32, e: i32) @This() {
79 return @This(){ .b = b, .e = e };
80 }
81
82 fn order(context: void, key: i32, mid_item: @This()) math.Order {
83 _ = context;
84
85 if (key < mid_item.b) {
86 return .lt;
87 }
88
89 if (key > mid_item.e) {
90 return .gt;
91 }
92
93 return .eq;
94 }
95 };
96 try testing.expectEqual(
97 @as(?usize, null),
98 binarySearch(R, @as(i32, -45), &[_]R{ R.r(-100, -50), R.r(-40, -20), R.r(-10, 20), R.r(30, 40) }, {}, R.order),
99 );
100 try testing.expectEqual(
101 @as(?usize, 2),
102 binarySearch(R, @as(i32, 10), &[_]R{ R.r(-100, -50), R.r(-40, -20), R.r(-10, 20), R.r(30, 40) }, {}, R.order),
103 );
104 try testing.expectEqual(
105 @as(?usize, 1),
106 binarySearch(R, @as(i32, -20), &[_]R{ R.r(-100, -50), R.r(-40, -20), R.r(-10, 20), R.r(30, 40) }, {}, R.order),
107 );
108}
10910
110/// Stable in-place sort. O(n) best case, O(pow(n, 2)) worst case.11/// Stable in-place sort. O(n) best case, O(pow(n, 2)) worst case.
111/// O(1) memory (no allocator required).12/// O(1) memory (no allocator required).
112/// Sorts in ascending order with respect to the given `lessThan` function.13/// Sorts in ascending order with respect to the given `lessThan` function.
113/// This can be expressed in terms of `insertionSortContext` but the glue14pub fn insertion(
114/// code is slightly longer than the direct implementation.
115pub fn insertionSort(
116 comptime T: type,15 comptime T: type,
117 items: []T,16 items: []T,
118 context: anytype,17 context: anytype,
119 comptime lessThan: fn (context: @TypeOf(context), lhs: T, rhs: T) bool,18 comptime lessThanFn: fn (@TypeOf(context), lhs: T, rhs: T) bool,
120) void {19) void {
121 var i: usize = 1;20 const Context = struct {
122 while (i < items.len) : (i += 1) {21 items: []T,
123 const x = items[i];22 sub_ctx: @TypeOf(context),
124 var j: usize = i;23
125 while (j > 0 and lessThan(context, x, items[j - 1])) : (j -= 1) {24 pub fn lessThan(ctx: @This(), a: usize, b: usize) bool {
126 items[j] = items[j - 1];25 return lessThanFn(ctx.sub_ctx, ctx.items[a], ctx.items[b]);
127 }26 }
128 items[j] = x;27
129 }28 pub fn swap(ctx: @This(), a: usize, b: usize) void {
29 return mem.swap(T, &ctx.items[a], &ctx.items[b]);
30 }
31 };
32 insertionContext(0, items.len, Context{ .items = items, .sub_ctx = context });
130}33}
13134
132/// Stable in-place sort. O(n) best case, O(pow(n, 2)) worst case.35/// Stable in-place sort. O(n) best case, O(pow(n, 2)) worst case.
133/// O(1) memory (no allocator required).36/// O(1) memory (no allocator required).
134/// Sorts in ascending order with respect to the given `context.lessThan` function.37/// Sorts in ascending order with respect to the given `lessThan` function.
135pub fn insertionSortContext(len: usize, context: anytype) void {38pub fn insertionContext(a: usize, b: usize, context: anytype) void {
136 var i: usize = 1;39 var i = a + 1;
137 while (i < len) : (i += 1) {40 while (i < b) : (i += 1) {
138 var j: usize = i;41 var j = i;
139 while (j > 0 and context.lessThan(j, j - 1)) : (j -= 1) {42 while (j > a and context.lessThan(j, j - 1)) : (j -= 1) {
140 context.swap(j, j - 1);43 context.swap(j, j - 1);
141 }44 }
142 }45 }
143}46}
14447
145const Range = struct {48/// Unstable in-place sort. O(n*log(n)) best case, worst case and average case.
146 start: usize,
147 end: usize,
148
149 fn init(start: usize, end: usize) Range {
150 return Range{
151 .start = start,
152 .end = end,
153 };
154 }
155
156 fn length(self: Range) usize {
157 return self.end - self.start;
158 }
159};
160
161const Iterator = struct {
162 size: usize,
163 power_of_two: usize,
164 numerator: usize,
165 decimal: usize,
166 denominator: usize,
167 decimal_step: usize,
168 numerator_step: usize,
169
170 fn init(size2: usize, min_level: usize) Iterator {
171 const power_of_two = math.floorPowerOfTwo(usize, size2);
172 const denominator = power_of_two / min_level;
173 return Iterator{
174 .numerator = 0,
175 .decimal = 0,
176 .size = size2,
177 .power_of_two = power_of_two,
178 .denominator = denominator,
179 .decimal_step = size2 / denominator,
180 .numerator_step = size2 % denominator,
181 };
182 }
183
184 fn begin(self: *Iterator) void {
185 self.numerator = 0;
186 self.decimal = 0;
187 }
188
189 fn nextRange(self: *Iterator) Range {
190 const start = self.decimal;
191
192 self.decimal += self.decimal_step;
193 self.numerator += self.numerator_step;
194 if (self.numerator >= self.denominator) {
195 self.numerator -= self.denominator;
196 self.decimal += 1;
197 }
198
199 return Range{
200 .start = start,
201 .end = self.decimal,
202 };
203 }
204
205 fn finished(self: *Iterator) bool {
206 return self.decimal >= self.size;
207 }
208
209 fn nextLevel(self: *Iterator) bool {
210 self.decimal_step += self.decimal_step;
211 self.numerator_step += self.numerator_step;
212 if (self.numerator_step >= self.denominator) {
213 self.numerator_step -= self.denominator;
214 self.decimal_step += 1;
215 }
216
217 return (self.decimal_step < self.size);
218 }
219
220 fn length(self: *Iterator) usize {
221 return self.decimal_step;
222 }
223};
224
225const Pull = struct {
226 from: usize,
227 to: usize,
228 count: usize,
229 range: Range,
230};
231
232/// Stable in-place sort. O(n) best case, O(n*log(n)) worst case and average case.
233/// O(1) memory (no allocator required).49/// O(1) memory (no allocator required).
234/// Sorts in ascending order with respect to the given `lessThan` function.50/// Sorts in ascending order with respect to the given `lessThan` function.
235/// Currently implemented as block sort.51pub fn heap(
236pub fn sort(
237 comptime T: type,52 comptime T: type,
238 items: []T,53 items: []T,
239 context: anytype,54 context: anytype,
240 comptime lessThan: fn (context: @TypeOf(context), lhs: T, rhs: T) bool,55 comptime lessThanFn: fn (@TypeOf(context), lhs: T, rhs: T) bool,
241) void {56) void {
57 const Context = struct {
58 items: []T,
59 sub_ctx: @TypeOf(context),
24260
243 // Implementation ported from https://github.com/BonzaiThePenguin/WikiSort/blob/master/WikiSort.c61 pub fn lessThan(ctx: @This(), a: usize, b: usize) bool {
244 var cache: [512]T = undefined;62 return lessThanFn(ctx.sub_ctx, ctx.items[a], ctx.items[b]);
245
246 if (items.len < 4) {
247 if (items.len == 3) {
248 // hard coded insertion sort
249 if (lessThan(context, items[1], items[0])) mem.swap(T, &items[0], &items[1]);
250 if (lessThan(context, items[2], items[1])) {
251 mem.swap(T, &items[1], &items[2]);
252 if (lessThan(context, items[1], items[0])) mem.swap(T, &items[0], &items[1]);
253 }
254 } else if (items.len == 2) {
255 if (lessThan(context, items[1], items[0])) mem.swap(T, &items[0], &items[1]);
256 }63 }
257 return;
258 }
259
260 // sort groups of 4-8 items at a time using an unstable sorting network,
261 // but keep track of the original item orders to force it to be stable
262 // http://pages.ripco.net/~jgamble/nw.html
263 var iterator = Iterator.init(items.len, 4);
264 while (!iterator.finished()) {
265 var order = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7 };
266 const range = iterator.nextRange();
267
268 const sliced_items = items[range.start..];
269 switch (range.length()) {
270 8 => {
271 swap(T, sliced_items, context, lessThan, &order, 0, 1);
272 swap(T, sliced_items, context, lessThan, &order, 2, 3);
273 swap(T, sliced_items, context, lessThan, &order, 4, 5);
274 swap(T, sliced_items, context, lessThan, &order, 6, 7);
275 swap(T, sliced_items, context, lessThan, &order, 0, 2);
276 swap(T, sliced_items, context, lessThan, &order, 1, 3);
277 swap(T, sliced_items, context, lessThan, &order, 4, 6);
278 swap(T, sliced_items, context, lessThan, &order, 5, 7);
279 swap(T, sliced_items, context, lessThan, &order, 1, 2);
280 swap(T, sliced_items, context, lessThan, &order, 5, 6);
281 swap(T, sliced_items, context, lessThan, &order, 0, 4);
282 swap(T, sliced_items, context, lessThan, &order, 3, 7);
283 swap(T, sliced_items, context, lessThan, &order, 1, 5);
284 swap(T, sliced_items, context, lessThan, &order, 2, 6);
285 swap(T, sliced_items, context, lessThan, &order, 1, 4);
286 swap(T, sliced_items, context, lessThan, &order, 3, 6);
287 swap(T, sliced_items, context, lessThan, &order, 2, 4);
288 swap(T, sliced_items, context, lessThan, &order, 3, 5);
289 swap(T, sliced_items, context, lessThan, &order, 3, 4);
290 },
291 7 => {
292 swap(T, sliced_items, context, lessThan, &order, 1, 2);
293 swap(T, sliced_items, context, lessThan, &order, 3, 4);
294 swap(T, sliced_items, context, lessThan, &order, 5, 6);
295 swap(T, sliced_items, context, lessThan, &order, 0, 2);
296 swap(T, sliced_items, context, lessThan, &order, 3, 5);
297 swap(T, sliced_items, context, lessThan, &order, 4, 6);
298 swap(T, sliced_items, context, lessThan, &order, 0, 1);
299 swap(T, sliced_items, context, lessThan, &order, 4, 5);
300 swap(T, sliced_items, context, lessThan, &order, 2, 6);
301 swap(T, sliced_items, context, lessThan, &order, 0, 4);
302 swap(T, sliced_items, context, lessThan, &order, 1, 5);
303 swap(T, sliced_items, context, lessThan, &order, 0, 3);
304 swap(T, sliced_items, context, lessThan, &order, 2, 5);
305 swap(T, sliced_items, context, lessThan, &order, 1, 3);
306 swap(T, sliced_items, context, lessThan, &order, 2, 4);
307 swap(T, sliced_items, context, lessThan, &order, 2, 3);
308 },
309 6 => {
310 swap(T, sliced_items, context, lessThan, &order, 1, 2);
311 swap(T, sliced_items, context, lessThan, &order, 4, 5);
312 swap(T, sliced_items, context, lessThan, &order, 0, 2);
313 swap(T, sliced_items, context, lessThan, &order, 3, 5);
314 swap(T, sliced_items, context, lessThan, &order, 0, 1);
315 swap(T, sliced_items, context, lessThan, &order, 3, 4);
316 swap(T, sliced_items, context, lessThan, &order, 2, 5);
317 swap(T, sliced_items, context, lessThan, &order, 0, 3);
318 swap(T, sliced_items, context, lessThan, &order, 1, 4);
319 swap(T, sliced_items, context, lessThan, &order, 2, 4);
320 swap(T, sliced_items, context, lessThan, &order, 1, 3);
321 swap(T, sliced_items, context, lessThan, &order, 2, 3);
322 },
323 5 => {
324 swap(T, sliced_items, context, lessThan, &order, 0, 1);
325 swap(T, sliced_items, context, lessThan, &order, 3, 4);
326 swap(T, sliced_items, context, lessThan, &order, 2, 4);
327 swap(T, sliced_items, context, lessThan, &order, 2, 3);
328 swap(T, sliced_items, context, lessThan, &order, 1, 4);
329 swap(T, sliced_items, context, lessThan, &order, 0, 3);
330 swap(T, sliced_items, context, lessThan, &order, 0, 2);
331 swap(T, sliced_items, context, lessThan, &order, 1, 3);
332 swap(T, sliced_items, context, lessThan, &order, 1, 2);
333 },
334 4 => {
335 swap(T, sliced_items, context, lessThan, &order, 0, 1);
336 swap(T, sliced_items, context, lessThan, &order, 2, 3);
337 swap(T, sliced_items, context, lessThan, &order, 0, 2);
338 swap(T, sliced_items, context, lessThan, &order, 1, 3);
339 swap(T, sliced_items, context, lessThan, &order, 1, 2);
340 },
341 else => {},
342 }
343 }
344 if (items.len < 8) return;
345
346 // then merge sort the higher levels, which can be 8-15, 16-31, 32-63, 64-127, etc.
347 while (true) {
348 // if every A and B block will fit into the cache, use a special branch
349 // specifically for merging with the cache
350 // (we use < rather than <= since the block size might be one more than
351 // iterator.length())
352 if (iterator.length() < cache.len) {
353 // if four subarrays fit into the cache, it's faster to merge both
354 // pairs of subarrays into the cache,
355 // then merge the two merged subarrays from the cache back into the original array
356 if ((iterator.length() + 1) * 4 <= cache.len and iterator.length() * 4 <= items.len) {
357 iterator.begin();
358 while (!iterator.finished()) {
359 // merge A1 and B1 into the cache
360 var A1 = iterator.nextRange();
361 var B1 = iterator.nextRange();
362 var A2 = iterator.nextRange();
363 var B2 = iterator.nextRange();
364
365 if (lessThan(context, items[B1.end - 1], items[A1.start])) {
366 // the two ranges are in reverse order, so copy them in reverse order into the cache
367 const a1_items = items[A1.start..A1.end];
368 @memcpy(cache[B1.length()..][0..a1_items.len], a1_items);
369 const b1_items = items[B1.start..B1.end];
370 @memcpy(cache[0..b1_items.len], b1_items);
371 } else if (lessThan(context, items[B1.start], items[A1.end - 1])) {
372 // these two ranges weren't already in order, so merge them into the cache
373 mergeInto(T, items, A1, B1, context, lessThan, cache[0..]);
374 } else {
375 // if A1, B1, A2, and B2 are all in order, skip doing anything else
376 if (!lessThan(context, items[B2.start], items[A2.end - 1]) and !lessThan(context, items[A2.start], items[B1.end - 1])) continue;
377
378 // copy A1 and B1 into the cache in the same order
379 const a1_items = items[A1.start..A1.end];
380 @memcpy(cache[0..a1_items.len], a1_items);
381 const b1_items = items[B1.start..B1.end];
382 @memcpy(cache[A1.length()..][0..b1_items.len], b1_items);
383 }
384 A1 = Range.init(A1.start, B1.end);
385
386 // merge A2 and B2 into the cache
387 if (lessThan(context, items[B2.end - 1], items[A2.start])) {
388 // the two ranges are in reverse order, so copy them in reverse order into the cache
389 const a2_items = items[A2.start..A2.end];
390 @memcpy(cache[A1.length() + B2.length() ..][0..a2_items.len], a2_items);
391 const b2_items = items[B2.start..B2.end];
392 @memcpy(cache[A1.length()..][0..b2_items.len], b2_items);
393 } else if (lessThan(context, items[B2.start], items[A2.end - 1])) {
394 // these two ranges weren't already in order, so merge them into the cache
395 mergeInto(T, items, A2, B2, context, lessThan, cache[A1.length()..]);
396 } else {
397 // copy A2 and B2 into the cache in the same order
398 const a2_items = items[A2.start..A2.end];
399 @memcpy(cache[A1.length()..][0..a2_items.len], a2_items);
400 const b2_items = items[B2.start..B2.end];
401 @memcpy(cache[A1.length() + A2.length() ..][0..b2_items.len], b2_items);
402 }
403 A2 = Range.init(A2.start, B2.end);
404
405 // merge A1 and A2 from the cache into the items
406 const A3 = Range.init(0, A1.length());
407 const B3 = Range.init(A1.length(), A1.length() + A2.length());
408
409 if (lessThan(context, cache[B3.end - 1], cache[A3.start])) {
410 // the two ranges are in reverse order, so copy them in reverse order into the items
411 const a3_items = cache[A3.start..A3.end];
412 @memcpy(items[A1.start + A2.length() ..][0..a3_items.len], a3_items);
413 const b3_items = cache[B3.start..B3.end];
414 @memcpy(items[A1.start..][0..b3_items.len], b3_items);
415 } else if (lessThan(context, cache[B3.start], cache[A3.end - 1])) {
416 // these two ranges weren't already in order, so merge them back into the items
417 mergeInto(T, cache[0..], A3, B3, context, lessThan, items[A1.start..]);
418 } else {
419 // copy A3 and B3 into the items in the same order
420 const a3_items = cache[A3.start..A3.end];
421 @memcpy(items[A1.start..][0..a3_items.len], a3_items);
422 const b3_items = cache[B3.start..B3.end];
423 @memcpy(items[A1.start + A1.length() ..][0..b3_items.len], b3_items);
424 }
425 }
426
427 // we merged two levels at the same time, so we're done with this level already
428 // (iterator.nextLevel() is called again at the bottom of this outer merge loop)
429 _ = iterator.nextLevel();
430 } else {
431 iterator.begin();
432 while (!iterator.finished()) {
433 var A = iterator.nextRange();
434 var B = iterator.nextRange();
435
436 if (lessThan(context, items[B.end - 1], items[A.start])) {
437 // the two ranges are in reverse order, so a simple rotation should fix it
438 mem.rotate(T, items[A.start..B.end], A.length());
439 } else if (lessThan(context, items[B.start], items[A.end - 1])) {
440 // these two ranges weren't already in order, so we'll need to merge them!
441 const a_items = items[A.start..A.end];
442 @memcpy(cache[0..a_items.len], a_items);
443 mergeExternal(T, items, A, B, context, lessThan, cache[0..]);
444 }
445 }
446 }
447 } else {
448 // this is where the in-place merge logic starts!
449 // 1. pull out two internal buffers each containing √A unique values
450 // 1a. adjust block_size and buffer_size if we couldn't find enough unique values
451 // 2. loop over the A and B subarrays within this level of the merge sort
452 // 3. break A and B into blocks of size 'block_size'
453 // 4. "tag" each of the A blocks with values from the first internal buffer
454 // 5. roll the A blocks through the B blocks and drop/rotate them where they belong
455 // 6. merge each A block with any B values that follow, using the cache or the second internal buffer
456 // 7. sort the second internal buffer if it exists
457 // 8. redistribute the two internal buffers back into the items
458 var block_size: usize = math.sqrt(iterator.length());
459 var buffer_size = iterator.length() / block_size + 1;
460
461 // as an optimization, we really only need to pull out the internal buffers once for each level of merges
462 // after that we can reuse the same buffers over and over, then redistribute it when we're finished with this level
463 var A: Range = undefined;
464 var B: Range = undefined;
465 var index: usize = 0;
466 var last: usize = 0;
467 var count: usize = 0;
468 var find: usize = 0;
469 var start: usize = 0;
470 var pull_index: usize = 0;
471 var pull = [_]Pull{
472 Pull{
473 .from = 0,
474 .to = 0,
475 .count = 0,
476 .range = Range.init(0, 0),
477 },
478 Pull{
479 .from = 0,
480 .to = 0,
481 .count = 0,
482 .range = Range.init(0, 0),
483 },
484 };
485
486 var buffer1 = Range.init(0, 0);
487 var buffer2 = Range.init(0, 0);
488
489 // find two internal buffers of size 'buffer_size' each
490 find = buffer_size + buffer_size;
491 var find_separately = false;
492
493 if (block_size <= cache.len) {
494 // if every A block fits into the cache then we won't need the second internal buffer,
495 // so we really only need to find 'buffer_size' unique values
496 find = buffer_size;
497 } else if (find > iterator.length()) {
498 // we can't fit both buffers into the same A or B subarray, so find two buffers separately
499 find = buffer_size;
500 find_separately = true;
501 }
502
503 // 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),
504 // or we need to find one buffer of < 2√A unique values, and a second buffer of √A unique values,
505 // OR if we couldn't find that many unique values, we need the largest possible buffer we can get
506
507 // in the case where it couldn't find a single buffer of at least √A unique values,
508 // all of the Merge steps must be replaced by a different merge algorithm (MergeInPlace)
509 iterator.begin();
510 while (!iterator.finished()) {
511 A = iterator.nextRange();
512 B = iterator.nextRange();
513
514 // just store information about where the values will be pulled from and to,
515 // as well as how many values there are, to create the two internal buffers
516
517 // check A for the number of unique values we need to fill an internal buffer
518 // these values will be pulled out to the start of A
519 last = A.start;
520 count = 1;
521 while (count < find) : ({
522 last = index;
523 count += 1;
524 }) {
525 index = findLastForward(T, items, items[last], Range.init(last + 1, A.end), context, lessThan, find - count);
526 if (index == A.end) break;
527 }
528 index = last;
529
530 if (count >= buffer_size) {
531 // keep track of the range within the items where we'll need to "pull out" these values to create the internal buffer
532 pull[pull_index] = Pull{
533 .range = Range.init(A.start, B.end),
534 .count = count,
535 .from = index,
536 .to = A.start,
537 };
538 pull_index = 1;
539
540 if (count == buffer_size + buffer_size) {
541 // we were able to find a single contiguous section containing 2√A unique values,
542 // so this section can be used to contain both of the internal buffers we'll need
543 buffer1 = Range.init(A.start, A.start + buffer_size);
544 buffer2 = Range.init(A.start + buffer_size, A.start + count);
545 break;
546 } else if (find == buffer_size + buffer_size) {
547 // we found a buffer that contains at least √A unique values, but did not contain the full 2√A unique values,
548 // so we still need to find a second separate buffer of at least √A unique values
549 buffer1 = Range.init(A.start, A.start + count);
550 find = buffer_size;
551 } else if (block_size <= cache.len) {
552 // we found the first and only internal buffer that we need, so we're done!
553 buffer1 = Range.init(A.start, A.start + count);
554 break;
555 } else if (find_separately) {
556 // found one buffer, but now find the other one
557 buffer1 = Range.init(A.start, A.start + count);
558 find_separately = false;
559 } else {
560 // we found a second buffer in an 'A' subarray containing √A unique values, so we're done!
561 buffer2 = Range.init(A.start, A.start + count);
562 break;
563 }
564 } else if (pull_index == 0 and count > buffer1.length()) {
565 // keep track of the largest buffer we were able to find
566 buffer1 = Range.init(A.start, A.start + count);
567 pull[pull_index] = Pull{
568 .range = Range.init(A.start, B.end),
569 .count = count,
570 .from = index,
571 .to = A.start,
572 };
573 }
574
575 // check B for the number of unique values we need to fill an internal buffer
576 // these values will be pulled out to the end of B
577 last = B.end - 1;
578 count = 1;
579 while (count < find) : ({
580 last = index - 1;
581 count += 1;
582 }) {
583 index = findFirstBackward(T, items, items[last], Range.init(B.start, last), context, lessThan, find - count);
584 if (index == B.start) break;
585 }
586 index = last;
58764
588 if (count >= buffer_size) {65 pub fn swap(ctx: @This(), a: usize, b: usize) void {
589 // keep track of the range within the items where we'll need to "pull out" these values to create the internal buffe66 return mem.swap(T, &ctx.items[a], &ctx.items[b]);
590 pull[pull_index] = Pull{
591 .range = Range.init(A.start, B.end),
592 .count = count,
593 .from = index,
594 .to = B.end,
595 };
596 pull_index = 1;
597
598 if (count == buffer_size + buffer_size) {
599 // we were able to find a single contiguous section containing 2√A unique values,
600 // so this section can be used to contain both of the internal buffers we'll need
601 buffer1 = Range.init(B.end - count, B.end - buffer_size);
602 buffer2 = Range.init(B.end - buffer_size, B.end);
603 break;
604 } else if (find == buffer_size + buffer_size) {
605 // we found a buffer that contains at least √A unique values, but did not contain the full 2√A unique values,
606 // so we still need to find a second separate buffer of at least √A unique values
607 buffer1 = Range.init(B.end - count, B.end);
608 find = buffer_size;
609 } else if (block_size <= cache.len) {
610 // we found the first and only internal buffer that we need, so we're done!
611 buffer1 = Range.init(B.end - count, B.end);
612 break;
613 } else if (find_separately) {
614 // found one buffer, but now find the other one
615 buffer1 = Range.init(B.end - count, B.end);
616 find_separately = false;
617 } else {
618 // buffer2 will be pulled out from a 'B' subarray, so if the first buffer was pulled out from the corresponding 'A' subarray,
619 // we need to adjust the end point for that A subarray so it knows to stop redistributing its values before reaching buffer2
620 if (pull[0].range.start == A.start) pull[0].range.end -= pull[1].count;
621
622 // we found a second buffer in an 'B' subarray containing √A unique values, so we're done!
623 buffer2 = Range.init(B.end - count, B.end);
624 break;
625 }
626 } else if (pull_index == 0 and count > buffer1.length()) {
627 // keep track of the largest buffer we were able to find
628 buffer1 = Range.init(B.end - count, B.end);
629 pull[pull_index] = Pull{
630 .range = Range.init(A.start, B.end),
631 .count = count,
632 .from = index,
633 .to = B.end,
634 };
635 }
636 }
637
638 // pull out the two ranges so we can use them as internal buffers
639 pull_index = 0;
640 while (pull_index < 2) : (pull_index += 1) {
641 const length = pull[pull_index].count;
642
643 if (pull[pull_index].to < pull[pull_index].from) {
644 // we're pulling the values out to the left, which means the start of an A subarray
645 index = pull[pull_index].from;
646 count = 1;
647 while (count < length) : (count += 1) {
648 index = findFirstBackward(T, items, items[index - 1], Range.init(pull[pull_index].to, pull[pull_index].from - (count - 1)), context, lessThan, length - count);
649 const range = Range.init(index + 1, pull[pull_index].from + 1);
650 mem.rotate(T, items[range.start..range.end], range.length() - count);
651 pull[pull_index].from = index + count;
652 }
653 } else if (pull[pull_index].to > pull[pull_index].from) {
654 // we're pulling values out to the right, which means the end of a B subarray
655 index = pull[pull_index].from + 1;
656 count = 1;
657 while (count < length) : (count += 1) {
658 index = findLastForward(T, items, items[index], Range.init(index, pull[pull_index].to), context, lessThan, length - count);
659 const range = Range.init(pull[pull_index].from, index - 1);
660 mem.rotate(T, items[range.start..range.end], count);
661 pull[pull_index].from = index - 1 - count;
662 }
663 }
664 }
665
666 // adjust block_size and buffer_size based on the values we were able to pull out
667 buffer_size = buffer1.length();
668 block_size = iterator.length() / buffer_size + 1;
669
670 // the first buffer NEEDS to be large enough to tag each of the evenly sized A blocks,
671 // so this was originally here to test the math for adjusting block_size above
672 // assert((iterator.length() + 1)/block_size <= buffer_size);
673
674 // 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!
675 iterator.begin();
676 while (!iterator.finished()) {
677 A = iterator.nextRange();
678 B = iterator.nextRange();
679
680 // remove any parts of A or B that are being used by the internal buffers
681 start = A.start;
682 if (start == pull[0].range.start) {
683 if (pull[0].from > pull[0].to) {
684 A.start += pull[0].count;
685
686 // if the internal buffer takes up the entire A or B subarray, then there's nothing to merge
687 // this only happens for very small subarrays, like √4 = 2, 2 * (2 internal buffers) = 4,
688 // which also only happens when cache.len is small or 0 since it'd otherwise use MergeExternal
689 if (A.length() == 0) continue;
690 } else if (pull[0].from < pull[0].to) {
691 B.end -= pull[0].count;
692 if (B.length() == 0) continue;
693 }
694 }
695 if (start == pull[1].range.start) {
696 if (pull[1].from > pull[1].to) {
697 A.start += pull[1].count;
698 if (A.length() == 0) continue;
699 } else if (pull[1].from < pull[1].to) {
700 B.end -= pull[1].count;
701 if (B.length() == 0) continue;
702 }
703 }
704
705 if (lessThan(context, items[B.end - 1], items[A.start])) {
706 // the two ranges are in reverse order, so a simple rotation should fix it
707 mem.rotate(T, items[A.start..B.end], A.length());
708 } else if (lessThan(context, items[A.end], items[A.end - 1])) {
709 // these two ranges weren't already in order, so we'll need to merge them!
710 var findA: usize = undefined;
711
712 // break the remainder of A into blocks. firstA is the uneven-sized first A block
713 var blockA = Range.init(A.start, A.end);
714 var firstA = Range.init(A.start, A.start + blockA.length() % block_size);
715
716 // swap the first value of each A block with the value in buffer1
717 var indexA = buffer1.start;
718 index = firstA.end;
719 while (index < blockA.end) : ({
720 indexA += 1;
721 index += block_size;
722 }) {
723 mem.swap(T, &items[indexA], &items[index]);
724 }
725
726 // start rolling the A blocks through the B blocks!
727 // 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
728 var lastA = firstA;
729 var lastB = Range.init(0, 0);
730 var blockB = Range.init(B.start, B.start + math.min(block_size, B.length()));
731 blockA.start += firstA.length();
732 indexA = buffer1.start;
733
734 // if the first unevenly sized A block fits into the cache, copy it there for when we go to Merge it
735 // otherwise, if the second buffer is available, block swap the contents into that
736 if (lastA.length() <= cache.len) {
737 const last_a_items = items[lastA.start..lastA.end];
738 @memcpy(cache[0..last_a_items.len], last_a_items);
739 } else if (buffer2.length() > 0) {
740 blockSwap(T, items, lastA.start, buffer2.start, lastA.length());
741 }
742
743 if (blockA.length() > 0) {
744 while (true) {
745 // 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,
746 // then drop that minimum A block behind. or if there are no B blocks left then keep dropping the remaining A blocks.
747 if ((lastB.length() > 0 and !lessThan(context, items[lastB.end - 1], items[indexA])) or blockB.length() == 0) {
748 // figure out where to split the previous B block, and rotate it at the split
749 const B_split = binaryFirst(T, items, items[indexA], lastB, context, lessThan);
750 const B_remaining = lastB.end - B_split;
751
752 // swap the minimum A block to the beginning of the rolling A blocks
753 var minA = blockA.start;
754 findA = minA + block_size;
755 while (findA < blockA.end) : (findA += block_size) {
756 if (lessThan(context, items[findA], items[minA])) {
757 minA = findA;
758 }
759 }
760 blockSwap(T, items, blockA.start, minA, block_size);
761
762 // swap the first item of the previous A block back with its original value, which is stored in buffer1
763 mem.swap(T, &items[blockA.start], &items[indexA]);
764 indexA += 1;
765
766 // locally merge the previous A block with the B values that follow it
767 // if lastA fits into the external cache we'll use that (with MergeExternal),
768 // or if the second internal buffer exists we'll use that (with MergeInternal),
769 // or failing that we'll use a strictly in-place merge algorithm (MergeInPlace)
770
771 if (lastA.length() <= cache.len) {
772 mergeExternal(T, items, lastA, Range.init(lastA.end, B_split), context, lessThan, cache[0..]);
773 } else if (buffer2.length() > 0) {
774 mergeInternal(T, items, lastA, Range.init(lastA.end, B_split), context, lessThan, buffer2);
775 } else {
776 mergeInPlace(T, items, lastA, Range.init(lastA.end, B_split), context, lessThan);
777 }
778
779 if (buffer2.length() > 0 or block_size <= cache.len) {
780 // 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
781 if (block_size <= cache.len) {
782 @memcpy(cache[0..block_size], items[blockA.start..][0..block_size]);
783 } else {
784 blockSwap(T, items, blockA.start, buffer2.start, block_size);
785 }
786
787 // this is equivalent to rotating, but faster
788 // 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
789 // 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
790 blockSwap(T, items, B_split, blockA.start + block_size - B_remaining, B_remaining);
791 } else {
792 // we are unable to use the 'buffer2' trick to speed up the rotation operation since buffer2 doesn't exist, so perform a normal rotation
793 mem.rotate(T, items[B_split .. blockA.start + block_size], blockA.start - B_split);
794 }
795
796 // update the range for the remaining A blocks, and the range remaining from the B block after it was split
797 lastA = Range.init(blockA.start - B_remaining, blockA.start - B_remaining + block_size);
798 lastB = Range.init(lastA.end, lastA.end + B_remaining);
799
800 // if there are no more A blocks remaining, this step is finished!
801 blockA.start += block_size;
802 if (blockA.length() == 0) break;
803 } else if (blockB.length() < block_size) {
804 // move the last B block, which is unevenly sized, to before the remaining A blocks, by using a rotation
805 // the cache is disabled here since it might contain the contents of the previous A block
806 mem.rotate(T, items[blockA.start..blockB.end], blockB.start - blockA.start);
807
808 lastB = Range.init(blockA.start, blockA.start + blockB.length());
809 blockA.start += blockB.length();
810 blockA.end += blockB.length();
811 blockB.end = blockB.start;
812 } else {
813 // roll the leftmost A block to the end by swapping it with the next B block
814 blockSwap(T, items, blockA.start, blockB.start, block_size);
815 lastB = Range.init(blockA.start, blockA.start + block_size);
816
817 blockA.start += block_size;
818 blockA.end += block_size;
819 blockB.start += block_size;
820
821 if (blockB.end > B.end - block_size) {
822 blockB.end = B.end;
823 } else {
824 blockB.end += block_size;
825 }
826 }
827 }
828 }
829
830 // merge the last A block with the remaining B values
831 if (lastA.length() <= cache.len) {
832 mergeExternal(T, items, lastA, Range.init(lastA.end, B.end), context, lessThan, cache[0..]);
833 } else if (buffer2.length() > 0) {
834 mergeInternal(T, items, lastA, Range.init(lastA.end, B.end), context, lessThan, buffer2);
835 } else {
836 mergeInPlace(T, items, lastA, Range.init(lastA.end, B.end), context, lessThan);
837 }
838 }
839 }
840
841 // when we're finished with this merge step we should have the one
842 // or two internal buffers left over, where the second buffer is all jumbled up
843 // insertion sort the second buffer, then redistribute the buffers
844 // back into the items using the opposite process used for creating the buffer
845
846 // while an unstable sort like quicksort could be applied here, in benchmarks
847 // it was consistently slightly slower than a simple insertion sort,
848 // even for tens of millions of items. this may be because insertion
849 // sort is quite fast when the data is already somewhat sorted, like it is here
850 insertionSort(T, items[buffer2.start..buffer2.end], context, lessThan);
851
852 pull_index = 0;
853 while (pull_index < 2) : (pull_index += 1) {
854 var unique = pull[pull_index].count * 2;
855 if (pull[pull_index].from > pull[pull_index].to) {
856 // the values were pulled out to the left, so redistribute them back to the right
857 var buffer = Range.init(pull[pull_index].range.start, pull[pull_index].range.start + pull[pull_index].count);
858 while (buffer.length() > 0) {
859 index = findFirstForward(T, items, items[buffer.start], Range.init(buffer.end, pull[pull_index].range.end), context, lessThan, unique);
860 const amount = index - buffer.end;
861 mem.rotate(T, items[buffer.start..index], buffer.length());
862 buffer.start += (amount + 1);
863 buffer.end += amount;
864 unique -= 2;
865 }
866 } else if (pull[pull_index].from < pull[pull_index].to) {
867 // the values were pulled out to the right, so redistribute them back to the left
868 var buffer = Range.init(pull[pull_index].range.end - pull[pull_index].count, pull[pull_index].range.end);
869 while (buffer.length() > 0) {
870 index = findLastBackward(T, items, items[buffer.end - 1], Range.init(pull[pull_index].range.start, buffer.start), context, lessThan, unique);
871 const amount = buffer.start - index;
872 mem.rotate(T, items[index..buffer.end], amount);
873 buffer.start -= amount;
874 buffer.end -= (amount + 1);
875 unique -= 2;
876 }
877 }
878 }
879 }67 }
88068 };
881 // double the size of each A and B subarray that will be merged in the next level69 heapContext(0, items.len, Context{ .items = items, .sub_ctx = context });
882 if (!iterator.nextLevel()) break;
883 }
884}
885
886/// TODO currently this just calls `insertionSortContext`. The block sort implementation
887/// in this file needs to be adapted to use the sort context.
888pub fn sortContext(len: usize, context: anytype) void {
889 return insertionSortContext(len, context);
890}
891
892// merge operation without a buffer
893fn mergeInPlace(
894 comptime T: type,
895 items: []T,
896 A_arg: Range,
897 B_arg: Range,
898 context: anytype,
899 comptime lessThan: fn (@TypeOf(context), T, T) bool,
900) void {
901 if (A_arg.length() == 0 or B_arg.length() == 0) return;
902
903 // this just repeatedly binary searches into B and rotates A into position.
904 // the paper suggests using the 'rotation-based Hwang and Lin algorithm' here,
905 // but I decided to stick with this because it had better situational performance
906 //
907 // (Hwang and Lin is designed for merging subarrays of very different sizes,
908 // but WikiSort almost always uses subarrays that are roughly the same size)
909 //
910 // normally this is incredibly suboptimal, but this function is only called
911 // when none of the A or B blocks in any subarray contained 2√A unique values,
912 // which places a hard limit on the number of times this will ACTUALLY need
913 // to binary search and rotate.
914 //
915 // according to my analysis the worst case is √A rotations performed on √A items
916 // once the constant factors are removed, which ends up being O(n)
917 //
918 // again, this is NOT a general-purpose solution – it only works well in this case!
919 // kind of like how the O(n^2) insertion sort is used in some places
920
921 var A = A_arg;
922 var B = B_arg;
923
924 while (true) {
925 // find the first place in B where the first item in A needs to be inserted
926 const mid = binaryFirst(T, items, items[A.start], B, context, lessThan);
927
928 // rotate A into place
929 const amount = mid - A.end;
930 mem.rotate(T, items[A.start..mid], A.length());
931 if (B.end == mid) break;
932
933 // calculate the new A and B ranges
934 B.start = mid;
935 A = Range.init(A.start + amount, B.start);
936 A.start = binaryLast(T, items, items[A.start], A, context, lessThan);
937 if (A.length() == 0) break;
938 }
939}
940
941// merge operation using an internal buffer
942fn mergeInternal(
943 comptime T: type,
944 items: []T,
945 A: Range,
946 B: Range,
947 context: anytype,
948 comptime lessThan: fn (@TypeOf(context), T, T) bool,
949 buffer: Range,
950) void {
951 // whenever we find a value to add to the final array, swap it with the value that's already in that spot
952 // when this algorithm is finished, 'buffer' will contain its original contents, but in a different order
953 var A_count: usize = 0;
954 var B_count: usize = 0;
955 var insert: usize = 0;
956
957 if (B.length() > 0 and A.length() > 0) {
958 while (true) {
959 if (!lessThan(context, items[B.start + B_count], items[buffer.start + A_count])) {
960 mem.swap(T, &items[A.start + insert], &items[buffer.start + A_count]);
961 A_count += 1;
962 insert += 1;
963 if (A_count >= A.length()) break;
964 } else {
965 mem.swap(T, &items[A.start + insert], &items[B.start + B_count]);
966 B_count += 1;
967 insert += 1;
968 if (B_count >= B.length()) break;
969 }
970 }
971 }
972
973 // swap the remainder of A into the final array
974 blockSwap(T, items, buffer.start + A_count, A.start + insert, A.length() - A_count);
975}
976
977fn blockSwap(comptime T: type, items: []T, start1: usize, start2: usize, block_size: usize) void {
978 var index: usize = 0;
979 while (index < block_size) : (index += 1) {
980 mem.swap(T, &items[start1 + index], &items[start2 + index]);
981 }
982}
983
984// combine a linear search with a binary search to reduce the number of comparisons in situations
985// where have some idea as to how many unique values there are and where the next value might be
986fn findFirstForward(
987 comptime T: type,
988 items: []T,
989 value: T,
990 range: Range,
991 context: anytype,
992 comptime lessThan: fn (@TypeOf(context), T, T) bool,
993 unique: usize,
994) usize {
995 if (range.length() == 0) return range.start;
996 const skip = math.max(range.length() / unique, @as(usize, 1));
997
998 var index = range.start + skip;
999 while (lessThan(context, items[index - 1], value)) : (index += skip) {
1000 if (index >= range.end - skip) {
1001 return binaryFirst(T, items, value, Range.init(index, range.end), context, lessThan);
1002 }
1003 }
1004
1005 return binaryFirst(T, items, value, Range.init(index - skip, index), context, lessThan);
1006}
1007
1008fn findFirstBackward(
1009 comptime T: type,
1010 items: []T,
1011 value: T,
1012 range: Range,
1013 context: anytype,
1014 comptime lessThan: fn (@TypeOf(context), T, T) bool,
1015 unique: usize,
1016) usize {
1017 if (range.length() == 0) return range.start;
1018 const skip = math.max(range.length() / unique, @as(usize, 1));
1019
1020 var index = range.end - skip;
1021 while (index > range.start and !lessThan(context, items[index - 1], value)) : (index -= skip) {
1022 if (index < range.start + skip) {
1023 return binaryFirst(T, items, value, Range.init(range.start, index), context, lessThan);
1024 }
1025 }
1026
1027 return binaryFirst(T, items, value, Range.init(index, index + skip), context, lessThan);
1028}
1029
1030fn findLastForward(
1031 comptime T: type,
1032 items: []T,
1033 value: T,
1034 range: Range,
1035 context: anytype,
1036 comptime lessThan: fn (@TypeOf(context), T, T) bool,
1037 unique: usize,
1038) usize {
1039 if (range.length() == 0) return range.start;
1040 const skip = math.max(range.length() / unique, @as(usize, 1));
1041
1042 var index = range.start + skip;
1043 while (!lessThan(context, value, items[index - 1])) : (index += skip) {
1044 if (index >= range.end - skip) {
1045 return binaryLast(T, items, value, Range.init(index, range.end), context, lessThan);
1046 }
1047 }
1048
1049 return binaryLast(T, items, value, Range.init(index - skip, index), context, lessThan);
1050}
1051
1052fn findLastBackward(
1053 comptime T: type,
1054 items: []T,
1055 value: T,
1056 range: Range,
1057 context: anytype,
1058 comptime lessThan: fn (@TypeOf(context), T, T) bool,
1059 unique: usize,
1060) usize {
1061 if (range.length() == 0) return range.start;
1062 const skip = math.max(range.length() / unique, @as(usize, 1));
1063
1064 var index = range.end - skip;
1065 while (index > range.start and lessThan(context, value, items[index - 1])) : (index -= skip) {
1066 if (index < range.start + skip) {
1067 return binaryLast(T, items, value, Range.init(range.start, index), context, lessThan);
1068 }
1069 }
1070
1071 return binaryLast(T, items, value, Range.init(index, index + skip), context, lessThan);
1072}70}
107371
1074fn binaryFirst(72/// Unstable in-place sort. O(n*log(n)) best case, worst case and average case.
1075 comptime T: type,73/// O(1) memory (no allocator required).
1076 items: []T,74/// Sorts in ascending order with respect to the given `lessThan` function.
1077 value: T,75pub fn heapContext(a: usize, b: usize, context: anytype) void {
1078 range: Range,76 // build the heap in linear time.
1079 context: anytype,77 var i = b / 2;
1080 comptime lessThan: fn (@TypeOf(context), T, T) bool,78 while (i > a) : (i -= 1) {
1081) usize {79 siftDown(i - 1, b, context);
1082 var curr = range.start;
1083 var size = range.length();
1084 if (range.start >= range.end) return range.end;
1085 while (size > 0) {
1086 const offset = size % 2;
1087
1088 size /= 2;
1089 const mid_item = items[curr + size];
1090 if (lessThan(context, mid_item, value)) {
1091 curr += size + offset;
1092 }
1093 }80 }
1094 return curr;
1095}
1096
1097fn binaryLast(
1098 comptime T: type,
1099 items: []T,
1100 value: T,
1101 range: Range,
1102 context: anytype,
1103 comptime lessThan: fn (@TypeOf(context), T, T) bool,
1104) usize {
1105 var curr = range.start;
1106 var size = range.length();
1107 if (range.start >= range.end) return range.end;
1108 while (size > 0) {
1109 const offset = size % 2;
111081
1111 size /= 2;82 // pop maximal elements from the heap.
1112 const mid_item = items[curr + size];83 i = b;
1113 if (!lessThan(context, value, mid_item)) {84 while (i > a) : (i -= 1) {
1114 curr += size + offset;85 context.swap(a, i - 1);
1115 }86 siftDown(a, i - 1, context);
1116 }87 }
1117 return curr;
1118}88}
111989
1120fn mergeInto(90fn siftDown(root: usize, n: usize, context: anytype) void {
1121 comptime T: type,91 var node = root;
1122 from: []T,
1123 A: Range,
1124 B: Range,
1125 context: anytype,
1126 comptime lessThan: fn (@TypeOf(context), T, T) bool,
1127 into: []T,
1128) void {
1129 var A_index: usize = A.start;
1130 var B_index: usize = B.start;
1131 const A_last = A.end;
1132 const B_last = B.end;
1133 var insert_index: usize = 0;
1134
1135 while (true) {92 while (true) {
1136 if (!lessThan(context, from[B_index], from[A_index])) {93 var child = 2 * node + 1;
1137 into[insert_index] = from[A_index];94 if (child >= n) break;
1138 A_index += 1;
1139 insert_index += 1;
1140 if (A_index == A_last) {
1141 // copy the remainder of B into the final array
1142 const from_b = from[B_index..B_last];
1143 @memcpy(into[insert_index..][0..from_b.len], from_b);
1144 break;
1145 }
1146 } else {
1147 into[insert_index] = from[B_index];
1148 B_index += 1;
1149 insert_index += 1;
1150 if (B_index == B_last) {
1151 // copy the remainder of A into the final array
1152 const from_a = from[A_index..A_last];
1153 @memcpy(into[insert_index..][0..from_a.len], from_a);
1154 break;
1155 }
1156 }
1157 }
1158}
1159
1160fn mergeExternal(
1161 comptime T: type,
1162 items: []T,
1163 A: Range,
1164 B: Range,
1165 context: anytype,
1166 comptime lessThan: fn (@TypeOf(context), T, T) bool,
1167 cache: []T,
1168) void {
1169 // A fits into the cache, so use that instead of the internal buffer
1170 var A_index: usize = 0;
1171 var B_index: usize = B.start;
1172 var insert_index: usize = A.start;
1173 const A_last = A.length();
1174 const B_last = B.end;
117595
1176 if (B.length() > 0 and A.length() > 0) {96 // choose the greater child.
1177 while (true) {97 if (child + 1 < n and context.lessThan(child, child + 1)) {
1178 if (!lessThan(context, items[B_index], cache[A_index])) {98 child += 1;
1179 items[insert_index] = cache[A_index];
1180 A_index += 1;
1181 insert_index += 1;
1182 if (A_index == A_last) break;
1183 } else {
1184 items[insert_index] = items[B_index];
1185 B_index += 1;
1186 insert_index += 1;
1187 if (B_index == B_last) break;
1188 }
1189 }99 }
1190 }
1191100
1192 // copy the remainder of A into the final array101 // stop if the invariant holds at `node`.
1193 const cache_a = cache[A_index..A_last];102 if (!context.lessThan(node, child)) break;
1194 @memcpy(items[insert_index..][0..cache_a.len], cache_a);
1195}
1196103
1197fn swap(104 // swap `node` with the greater child,
1198 comptime T: type,105 // move one step down, and continue sifting.
1199 items: []T,106 context.swap(node, child);
1200 context: anytype,107 node = child;
1201 comptime lessThan: fn (@TypeOf(context), lhs: T, rhs: T) bool,
1202 order: *[8]u8,
1203 x: usize,
1204 y: usize,
1205) void {
1206 if (lessThan(context, items[y], items[x]) or ((order.*)[x] > (order.*)[y] and !lessThan(context, items[x], items[y]))) {
1207 mem.swap(T, &items[x], &items[y]);
1208 mem.swap(u8, &(order.*)[x], &(order.*)[y]);
1209 }108 }
1210}109}
1211110
1212/// Use to generate a comparator function for a given type. e.g. `sort(u8, slice, {}, comptime asc(u8))`.111/// Use to generate a comparator function for a given type. e.g. `sort(u8, slice, {}, asc(u8))`.
1213pub fn asc(comptime T: type) fn (void, T, T) bool {112pub fn asc(comptime T: type) fn (void, T, T) bool {
1214 const impl = struct {113 return struct {
1215 fn inner(context: void, a: T, b: T) bool {114 pub fn inner(_: void, a: T, b: T) bool {
1216 _ = context;
1217 return a < b;115 return a < b;
1218 }116 }
1219 };117 }.inner;
1220
1221 return impl.inner;
1222}118}
1223119
1224/// Use to generate a comparator function for a given type. e.g. `sort(u8, slice, {}, comptime desc(u8))`.120/// Use to generate a comparator function for a given type. e.g. `sort(u8, slice, {}, desc(u8))`.
1225pub fn desc(comptime T: type) fn (void, T, T) bool {121pub fn desc(comptime T: type) fn (void, T, T) bool {
1226 const impl = struct {122 return struct {
1227 fn inner(context: void, a: T, b: T) bool {123 pub fn inner(_: void, a: T, b: T) bool {
1228 _ = context;
1229 return a > b;124 return a > b;
1230 }125 }
1231 };126 }.inner;
1232
1233 return impl.inner;
1234}127}
1235128
129const asc_u8 = asc(u8);
130const asc_i32 = asc(i32);
131const desc_u8 = desc(u8);
132const desc_i32 = desc(i32);
133
134const sort_funcs = &[_]fn (comptime type, anytype, anytype, comptime anytype) void{
135 block,
136 pdq,
137 insertion,
138 heap,
139};
140
141const IdAndValue = struct {
142 id: usize,
143 value: i32,
144
145 fn lessThan(context: void, a: IdAndValue, b: IdAndValue) bool {
146 _ = context;
147 return a.value < b.value;
148 }
149};
150
1236test "stable sort" {151test "stable sort" {
1237 try testStableSort();152 const expected = [_]IdAndValue{
1238 comptime try testStableSort();
1239}
1240fn testStableSort() !void {
1241 var expected = [_]IdAndValue{
1242 IdAndValue{ .id = 0, .value = 0 },153 IdAndValue{ .id = 0, .value = 0 },
1243 IdAndValue{ .id = 1, .value = 0 },154 IdAndValue{ .id = 1, .value = 0 },
1244 IdAndValue{ .id = 2, .value = 0 },155 IdAndValue{ .id = 2, .value = 0 },
...@@ -1249,6 +160,7 @@ fn testStableSort() !void {...@@ -1249,6 +160,7 @@ fn testStableSort() !void {
1249 IdAndValue{ .id = 1, .value = 2 },160 IdAndValue{ .id = 1, .value = 2 },
1250 IdAndValue{ .id = 2, .value = 2 },161 IdAndValue{ .id = 2, .value = 2 },
1251 };162 };
163
1252 var cases = [_][9]IdAndValue{164 var cases = [_][9]IdAndValue{
1253 [_]IdAndValue{165 [_]IdAndValue{
1254 IdAndValue{ .id = 0, .value = 0 },166 IdAndValue{ .id = 0, .value = 0 },
...@@ -1273,26 +185,15 @@ fn testStableSort() !void {...@@ -1273,26 +185,15 @@ fn testStableSort() !void {
1273 IdAndValue{ .id = 2, .value = 0 },185 IdAndValue{ .id = 2, .value = 0 },
1274 },186 },
1275 };187 };
188
1276 for (&cases) |*case| {189 for (&cases) |*case| {
1277 insertionSort(IdAndValue, (case.*)[0..], {}, cmpByValue);190 block(IdAndValue, (case.*)[0..], {}, IdAndValue.lessThan);
1278 for (case.*, 0..) |item, i| {191 for (case.*, 0..) |item, i| {
1279 try testing.expect(item.id == expected[i].id);192 try testing.expect(item.id == expected[i].id);
1280 try testing.expect(item.value == expected[i].value);193 try testing.expect(item.value == expected[i].value);
1281 }194 }
1282 }195 }
1283}196}
1284const IdAndValue = struct {
1285 id: usize,
1286 value: i32,
1287};
1288fn cmpByValue(context: void, a: IdAndValue, b: IdAndValue) bool {
1289 return asc_i32(context, a.value, b.value);
1290}
1291
1292const asc_u8 = asc(u8);
1293const asc_i32 = asc(i32);
1294const desc_u8 = desc(u8);
1295const desc_i32 = desc(i32);
1296197
1297test "sort" {198test "sort" {
1298 const u8cases = [_][]const []const u8{199 const u8cases = [_][]const []const u8{
...@@ -1322,14 +223,6 @@ test "sort" {...@@ -1322,14 +223,6 @@ test "sort" {
1322 },223 },
1323 };224 };
1324225
1325 for (u8cases) |case| {
1326 var buf: [8]u8 = undefined;
1327 const slice = buf[0..case[0].len];
1328 @memcpy(slice, case[0]);
1329 sort(u8, slice, {}, asc_u8);
1330 try testing.expect(mem.eql(u8, slice, case[1]));
1331 }
1332
1333 const i32cases = [_][]const []const i32{226 const i32cases = [_][]const []const i32{
1334 &[_][]const i32{227 &[_][]const i32{
1335 &[_]i32{},228 &[_]i32{},
...@@ -1357,12 +250,22 @@ test "sort" {...@@ -1357,12 +250,22 @@ test "sort" {
1357 },250 },
1358 };251 };
1359252
1360 for (i32cases) |case| {253 inline for (sort_funcs) |sortFn| {
1361 var buf: [8]i32 = undefined;254 for (u8cases) |case| {
1362 const slice = buf[0..case[0].len];255 var buf: [8]u8 = undefined;
1363 @memcpy(slice, case[0]);256 const slice = buf[0..case[0].len];
1364 sort(i32, slice, {}, asc_i32);257 @memcpy(slice, case[0]);
1365 try testing.expect(mem.eql(i32, slice, case[1]));258 sortFn(u8, slice, {}, asc_u8);
259 try testing.expect(mem.eql(u8, slice, case[1]));
260 }
261
262 for (i32cases) |case| {
263 var buf: [8]i32 = undefined;
264 const slice = buf[0..case[0].len];
265 @memcpy(slice, case[0]);
266 sortFn(i32, slice, {}, asc_i32);
267 try testing.expect(mem.eql(i32, slice, case[1]));
268 }
1366 }269 }
1367}270}
1368271
...@@ -1394,53 +297,139 @@ test "sort descending" {...@@ -1394,53 +297,139 @@ test "sort descending" {
1394 },297 },
1395 };298 };
1396299
1397 for (rev_cases) |case| {300 inline for (sort_funcs) |sortFn| {
1398 var buf: [8]i32 = undefined;301 for (rev_cases) |case| {
1399 const slice = buf[0..case[0].len];302 var buf: [8]i32 = undefined;
1400 @memcpy(slice, case[0]);303 const slice = buf[0..case[0].len];
1401 sort(i32, slice, {}, desc_i32);304 @memcpy(slice, case[0]);
1402 try testing.expect(mem.eql(i32, slice, case[1]));305 sortFn(i32, slice, {}, desc_i32);
306 try testing.expect(mem.eql(i32, slice, case[1]));
307 }
1403 }308 }
1404}309}
1405310
1406test "another sort case" {
1407 var arr = [_]i32{ 5, 3, 1, 2, 4 };
1408 sort(i32, arr[0..], {}, asc_i32);
1409
1410 try testing.expect(mem.eql(i32, &arr, &[_]i32{ 1, 2, 3, 4, 5 }));
1411}
1412
1413test "sort fuzz testing" {311test "sort fuzz testing" {
1414 var prng = std.rand.DefaultPrng.init(0x12345678);312 var prng = std.rand.DefaultPrng.init(0x12345678);
1415 const random = prng.random();313 const random = prng.random();
1416 const test_case_count = 10;314 const test_case_count = 10;
1417 var i: usize = 0;315
1418 while (i < test_case_count) : (i += 1) {316 inline for (sort_funcs) |sortFn| {
1419 try fuzzTest(random);317 var i: usize = 0;
318 while (i < test_case_count) : (i += 1) {
319 const array_size = random.intRangeLessThan(usize, 0, 1000);
320 var array = try testing.allocator.alloc(i32, array_size);
321 defer testing.allocator.free(array);
322 // populate with random data
323 for (array) |*item| {
324 item.* = random.intRangeLessThan(i32, 0, 100);
325 }
326 sortFn(i32, array, {}, asc_i32);
327 try testing.expect(isSorted(i32, array, {}, asc_i32));
328 }
1420 }329 }
1421}330}
1422331
1423var fixed_buffer_mem: [100 * 1024]u8 = undefined;332pub fn binarySearch(
333 comptime T: type,
334 key: anytype,
335 items: []const T,
336 context: anytype,
337 comptime compareFn: fn (context: @TypeOf(context), key: @TypeOf(key), mid_item: T) math.Order,
338) ?usize {
339 var left: usize = 0;
340 var right: usize = items.len;
1424341
1425fn fuzzTest(rng: std.rand.Random) !void {342 while (left < right) {
1426 const array_size = rng.intRangeLessThan(usize, 0, 1000);343 // Avoid overflowing in the midpoint calculation
1427 var array = try testing.allocator.alloc(IdAndValue, array_size);344 const mid = left + (right - left) / 2;
1428 defer testing.allocator.free(array);345 // Compare the key with the midpoint element
1429 // populate with random data346 switch (compareFn(context, key, items[mid])) {
1430 for (array, 0..) |*item, index| {347 .eq => return mid,
1431 item.id = index;348 .gt => left = mid + 1,
1432 item.value = rng.intRangeLessThan(i32, 0, 100);349 .lt => right = mid,
350 }
1433 }351 }
1434 sort(IdAndValue, array, {}, cmpByValue);
1435352
1436 var index: usize = 1;353 return null;
1437 while (index < array.len) : (index += 1) {354}
1438 if (array[index].value == array[index - 1].value) {355
1439 try testing.expect(array[index].id > array[index - 1].id);356test "binarySearch" {
1440 } else {357 const S = struct {
1441 try testing.expect(array[index].value > array[index - 1].value);358 fn order_u32(context: void, lhs: u32, rhs: u32) math.Order {
359 _ = context;
360 return math.order(lhs, rhs);
1442 }361 }
1443 }362 fn order_i32(context: void, lhs: i32, rhs: i32) math.Order {
363 _ = context;
364 return math.order(lhs, rhs);
365 }
366 };
367 try testing.expectEqual(
368 @as(?usize, null),
369 binarySearch(u32, @as(u32, 1), &[_]u32{}, {}, S.order_u32),
370 );
371 try testing.expectEqual(
372 @as(?usize, 0),
373 binarySearch(u32, @as(u32, 1), &[_]u32{1}, {}, S.order_u32),
374 );
375 try testing.expectEqual(
376 @as(?usize, null),
377 binarySearch(u32, @as(u32, 1), &[_]u32{0}, {}, S.order_u32),
378 );
379 try testing.expectEqual(
380 @as(?usize, null),
381 binarySearch(u32, @as(u32, 0), &[_]u32{1}, {}, S.order_u32),
382 );
383 try testing.expectEqual(
384 @as(?usize, 4),
385 binarySearch(u32, @as(u32, 5), &[_]u32{ 1, 2, 3, 4, 5 }, {}, S.order_u32),
386 );
387 try testing.expectEqual(
388 @as(?usize, 0),
389 binarySearch(u32, @as(u32, 2), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.order_u32),
390 );
391 try testing.expectEqual(
392 @as(?usize, 1),
393 binarySearch(i32, @as(i32, -4), &[_]i32{ -7, -4, 0, 9, 10 }, {}, S.order_i32),
394 );
395 try testing.expectEqual(
396 @as(?usize, 3),
397 binarySearch(i32, @as(i32, 98), &[_]i32{ -100, -25, 2, 98, 99, 100 }, {}, S.order_i32),
398 );
399 const R = struct {
400 b: i32,
401 e: i32,
402
403 fn r(b: i32, e: i32) @This() {
404 return @This(){ .b = b, .e = e };
405 }
406
407 fn order(context: void, key: i32, mid_item: @This()) math.Order {
408 _ = context;
409
410 if (key < mid_item.b) {
411 return .lt;
412 }
413
414 if (key > mid_item.e) {
415 return .gt;
416 }
417
418 return .eq;
419 }
420 };
421 try testing.expectEqual(
422 @as(?usize, null),
423 binarySearch(R, @as(i32, -45), &[_]R{ R.r(-100, -50), R.r(-40, -20), R.r(-10, 20), R.r(30, 40) }, {}, R.order),
424 );
425 try testing.expectEqual(
426 @as(?usize, 2),
427 binarySearch(R, @as(i32, 10), &[_]R{ R.r(-100, -50), R.r(-40, -20), R.r(-10, 20), R.r(30, 40) }, {}, R.order),
428 );
429 try testing.expectEqual(
430 @as(?usize, 1),
431 binarySearch(R, @as(i32, -20), &[_]R{ R.r(-100, -50), R.r(-40, -20), R.r(-10, 20), R.r(30, 40) }, {}, R.order),
432 );
1444}433}
1445434
1446pub fn argMin(435pub fn argMin(
lib/std/sort/block.zig created+1066
...@@ -0,0 +1,1066 @@
1const std = @import("../std.zig");
2const sort = std.sort;
3const math = std.math;
4const mem = std.mem;
5
6const Range = struct {
7 start: usize,
8 end: usize,
9
10 fn init(start: usize, end: usize) Range {
11 return Range{
12 .start = start,
13 .end = end,
14 };
15 }
16
17 fn length(self: Range) usize {
18 return self.end - self.start;
19 }
20};
21
22const Iterator = struct {
23 size: usize,
24 power_of_two: usize,
25 numerator: usize,
26 decimal: usize,
27 denominator: usize,
28 decimal_step: usize,
29 numerator_step: usize,
30
31 fn init(size2: usize, min_level: usize) Iterator {
32 const power_of_two = math.floorPowerOfTwo(usize, size2);
33 const denominator = power_of_two / min_level;
34 return Iterator{
35 .numerator = 0,
36 .decimal = 0,
37 .size = size2,
38 .power_of_two = power_of_two,
39 .denominator = denominator,
40 .decimal_step = size2 / denominator,
41 .numerator_step = size2 % denominator,
42 };
43 }
44
45 fn begin(self: *Iterator) void {
46 self.numerator = 0;
47 self.decimal = 0;
48 }
49
50 fn nextRange(self: *Iterator) Range {
51 const start = self.decimal;
52
53 self.decimal += self.decimal_step;
54 self.numerator += self.numerator_step;
55 if (self.numerator >= self.denominator) {
56 self.numerator -= self.denominator;
57 self.decimal += 1;
58 }
59
60 return Range{
61 .start = start,
62 .end = self.decimal,
63 };
64 }
65
66 fn finished(self: *Iterator) bool {
67 return self.decimal >= self.size;
68 }
69
70 fn nextLevel(self: *Iterator) bool {
71 self.decimal_step += self.decimal_step;
72 self.numerator_step += self.numerator_step;
73 if (self.numerator_step >= self.denominator) {
74 self.numerator_step -= self.denominator;
75 self.decimal_step += 1;
76 }
77
78 return (self.decimal_step < self.size);
79 }
80
81 fn length(self: *Iterator) usize {
82 return self.decimal_step;
83 }
84};
85
86const Pull = struct {
87 from: usize,
88 to: usize,
89 count: usize,
90 range: Range,
91};
92
93/// Stable in-place sort. O(n) best case, O(n*log(n)) worst case and average case.
94/// O(1) memory (no allocator required).
95/// Sorts in ascending order with respect to the given `lessThan` function.
96///
97/// NOTE: the algorithm only work when the comparison is less-than or greater-than
98/// (See https://github.com/ziglang/zig/issues/8289)
99pub fn block(
100 comptime T: type,
101 items: []T,
102 context: anytype,
103 comptime lessThan: fn (@TypeOf(context), lhs: T, rhs: T) bool,
104) void {
105
106 // Implementation ported from https://github.com/BonzaiThePenguin/WikiSort/blob/master/WikiSort.c
107 var cache: [512]T = undefined;
108
109 if (items.len < 4) {
110 if (items.len == 3) {
111 // hard coded insertion sort
112 if (lessThan(context, items[1], items[0])) mem.swap(T, &items[0], &items[1]);
113 if (lessThan(context, items[2], items[1])) {
114 mem.swap(T, &items[1], &items[2]);
115 if (lessThan(context, items[1], items[0])) mem.swap(T, &items[0], &items[1]);
116 }
117 } else if (items.len == 2) {
118 if (lessThan(context, items[1], items[0])) mem.swap(T, &items[0], &items[1]);
119 }
120 return;
121 }
122
123 // sort groups of 4-8 items at a time using an unstable sorting network,
124 // but keep track of the original item orders to force it to be stable
125 // http://pages.ripco.net/~jgamble/nw.html
126 var iterator = Iterator.init(items.len, 4);
127 while (!iterator.finished()) {
128 var order = [_]u8{ 0, 1, 2, 3, 4, 5, 6, 7 };
129 const range = iterator.nextRange();
130
131 const sliced_items = items[range.start..];
132 switch (range.length()) {
133 8 => {
134 swap(T, sliced_items, &order, 0, 1, context, lessThan);
135 swap(T, sliced_items, &order, 2, 3, context, lessThan);
136 swap(T, sliced_items, &order, 4, 5, context, lessThan);
137 swap(T, sliced_items, &order, 6, 7, context, lessThan);
138 swap(T, sliced_items, &order, 0, 2, context, lessThan);
139 swap(T, sliced_items, &order, 1, 3, context, lessThan);
140 swap(T, sliced_items, &order, 4, 6, context, lessThan);
141 swap(T, sliced_items, &order, 5, 7, context, lessThan);
142 swap(T, sliced_items, &order, 1, 2, context, lessThan);
143 swap(T, sliced_items, &order, 5, 6, context, lessThan);
144 swap(T, sliced_items, &order, 0, 4, context, lessThan);
145 swap(T, sliced_items, &order, 3, 7, context, lessThan);
146 swap(T, sliced_items, &order, 1, 5, context, lessThan);
147 swap(T, sliced_items, &order, 2, 6, context, lessThan);
148 swap(T, sliced_items, &order, 1, 4, context, lessThan);
149 swap(T, sliced_items, &order, 3, 6, context, lessThan);
150 swap(T, sliced_items, &order, 2, 4, context, lessThan);
151 swap(T, sliced_items, &order, 3, 5, context, lessThan);
152 swap(T, sliced_items, &order, 3, 4, context, lessThan);
153 },
154 7 => {
155 swap(T, sliced_items, &order, 1, 2, context, lessThan);
156 swap(T, sliced_items, &order, 3, 4, context, lessThan);
157 swap(T, sliced_items, &order, 5, 6, context, lessThan);
158 swap(T, sliced_items, &order, 0, 2, context, lessThan);
159 swap(T, sliced_items, &order, 3, 5, context, lessThan);
160 swap(T, sliced_items, &order, 4, 6, context, lessThan);
161 swap(T, sliced_items, &order, 0, 1, context, lessThan);
162 swap(T, sliced_items, &order, 4, 5, context, lessThan);
163 swap(T, sliced_items, &order, 2, 6, context, lessThan);
164 swap(T, sliced_items, &order, 0, 4, context, lessThan);
165 swap(T, sliced_items, &order, 1, 5, context, lessThan);
166 swap(T, sliced_items, &order, 0, 3, context, lessThan);
167 swap(T, sliced_items, &order, 2, 5, context, lessThan);
168 swap(T, sliced_items, &order, 1, 3, context, lessThan);
169 swap(T, sliced_items, &order, 2, 4, context, lessThan);
170 swap(T, sliced_items, &order, 2, 3, context, lessThan);
171 },
172 6 => {
173 swap(T, sliced_items, &order, 1, 2, context, lessThan);
174 swap(T, sliced_items, &order, 4, 5, context, lessThan);
175 swap(T, sliced_items, &order, 0, 2, context, lessThan);
176 swap(T, sliced_items, &order, 3, 5, context, lessThan);
177 swap(T, sliced_items, &order, 0, 1, context, lessThan);
178 swap(T, sliced_items, &order, 3, 4, context, lessThan);
179 swap(T, sliced_items, &order, 2, 5, context, lessThan);
180 swap(T, sliced_items, &order, 0, 3, context, lessThan);
181 swap(T, sliced_items, &order, 1, 4, context, lessThan);
182 swap(T, sliced_items, &order, 2, 4, context, lessThan);
183 swap(T, sliced_items, &order, 1, 3, context, lessThan);
184 swap(T, sliced_items, &order, 2, 3, context, lessThan);
185 },
186 5 => {
187 swap(T, sliced_items, &order, 0, 1, context, lessThan);
188 swap(T, sliced_items, &order, 3, 4, context, lessThan);
189 swap(T, sliced_items, &order, 2, 4, context, lessThan);
190 swap(T, sliced_items, &order, 2, 3, context, lessThan);
191 swap(T, sliced_items, &order, 1, 4, context, lessThan);
192 swap(T, sliced_items, &order, 0, 3, context, lessThan);
193 swap(T, sliced_items, &order, 0, 2, context, lessThan);
194 swap(T, sliced_items, &order, 1, 3, context, lessThan);
195 swap(T, sliced_items, &order, 1, 2, context, lessThan);
196 },
197 4 => {
198 swap(T, sliced_items, &order, 0, 1, context, lessThan);
199 swap(T, sliced_items, &order, 2, 3, context, lessThan);
200 swap(T, sliced_items, &order, 0, 2, context, lessThan);
201 swap(T, sliced_items, &order, 1, 3, context, lessThan);
202 swap(T, sliced_items, &order, 1, 2, context, lessThan);
203 },
204 else => {},
205 }
206 }
207 if (items.len < 8) return;
208
209 // then merge sort the higher levels, which can be 8-15, 16-31, 32-63, 64-127, etc.
210 while (true) {
211 // if every A and B block will fit into the cache, use a special branch
212 // specifically for merging with the cache
213 // (we use < rather than <= since the block size might be one more than
214 // iterator.length())
215 if (iterator.length() < cache.len) {
216 // if four subarrays fit into the cache, it's faster to merge both
217 // pairs of subarrays into the cache,
218 // then merge the two merged subarrays from the cache back into the original array
219 if ((iterator.length() + 1) * 4 <= cache.len and iterator.length() * 4 <= items.len) {
220 iterator.begin();
221 while (!iterator.finished()) {
222 // merge A1 and B1 into the cache
223 var A1 = iterator.nextRange();
224 var B1 = iterator.nextRange();
225 var A2 = iterator.nextRange();
226 var B2 = iterator.nextRange();
227
228 if (lessThan(context, items[B1.end - 1], items[A1.start])) {
229 // the two ranges are in reverse order, so copy them in reverse order into the cache
230 const a1_items = items[A1.start..A1.end];
231 @memcpy(cache[B1.length()..][0..a1_items.len], a1_items);
232 const b1_items = items[B1.start..B1.end];
233 @memcpy(cache[0..b1_items.len], b1_items);
234 } else if (lessThan(context, items[B1.start], items[A1.end - 1])) {
235 // these two ranges weren't already in order, so merge them into the cache
236 mergeInto(T, items, A1, B1, cache[0..], context, lessThan);
237 } else {
238 // if A1, B1, A2, and B2 are all in order, skip doing anything else
239 if (!lessThan(context, items[B2.start], items[A2.end - 1]) and !lessThan(context, items[A2.start], items[B1.end - 1])) continue;
240
241 // copy A1 and B1 into the cache in the same order
242 const a1_items = items[A1.start..A1.end];
243 @memcpy(cache[0..a1_items.len], a1_items);
244 const b1_items = items[B1.start..B1.end];
245 @memcpy(cache[A1.length()..][0..b1_items.len], b1_items);
246 }
247 A1 = Range.init(A1.start, B1.end);
248
249 // merge A2 and B2 into the cache
250 if (lessThan(context, items[B2.end - 1], items[A2.start])) {
251 // the two ranges are in reverse order, so copy them in reverse order into the cache
252 const a2_items = items[A2.start..A2.end];
253 @memcpy(cache[A1.length() + B2.length() ..][0..a2_items.len], a2_items);
254 const b2_items = items[B2.start..B2.end];
255 @memcpy(cache[A1.length()..][0..b2_items.len], b2_items);
256 } else if (lessThan(context, items[B2.start], items[A2.end - 1])) {
257 // these two ranges weren't already in order, so merge them into the cache
258 mergeInto(T, items, A2, B2, cache[A1.length()..], context, lessThan);
259 } else {
260 // copy A2 and B2 into the cache in the same order
261 const a2_items = items[A2.start..A2.end];
262 @memcpy(cache[A1.length()..][0..a2_items.len], a2_items);
263 const b2_items = items[B2.start..B2.end];
264 @memcpy(cache[A1.length() + A2.length() ..][0..b2_items.len], b2_items);
265 }
266 A2 = Range.init(A2.start, B2.end);
267
268 // merge A1 and A2 from the cache into the items
269 const A3 = Range.init(0, A1.length());
270 const B3 = Range.init(A1.length(), A1.length() + A2.length());
271
272 if (lessThan(context, cache[B3.end - 1], cache[A3.start])) {
273 // the two ranges are in reverse order, so copy them in reverse order into the items
274 const a3_items = cache[A3.start..A3.end];
275 @memcpy(items[A1.start + A2.length() ..][0..a3_items.len], a3_items);
276 const b3_items = cache[B3.start..B3.end];
277 @memcpy(items[A1.start..][0..b3_items.len], b3_items);
278 } else if (lessThan(context, cache[B3.start], cache[A3.end - 1])) {
279 // these two ranges weren't already in order, so merge them back into the items
280 mergeInto(T, cache[0..], A3, B3, items[A1.start..], context, lessThan);
281 } else {
282 // copy A3 and B3 into the items in the same order
283 const a3_items = cache[A3.start..A3.end];
284 @memcpy(items[A1.start..][0..a3_items.len], a3_items);
285 const b3_items = cache[B3.start..B3.end];
286 @memcpy(items[A1.start + A1.length() ..][0..b3_items.len], b3_items);
287 }
288 }
289
290 // we merged two levels at the same time, so we're done with this level already
291 // (iterator.nextLevel() is called again at the bottom of this outer merge loop)
292 _ = iterator.nextLevel();
293 } else {
294 iterator.begin();
295 while (!iterator.finished()) {
296 var A = iterator.nextRange();
297 var B = iterator.nextRange();
298
299 if (lessThan(context, items[B.end - 1], items[A.start])) {
300 // the two ranges are in reverse order, so a simple rotation should fix it
301 mem.rotate(T, items[A.start..B.end], A.length());
302 } else if (lessThan(context, items[B.start], items[A.end - 1])) {
303 // these two ranges weren't already in order, so we'll need to merge them!
304 const a_items = items[A.start..A.end];
305 @memcpy(cache[0..a_items.len], a_items);
306 mergeExternal(T, items, A, B, cache[0..], context, lessThan);
307 }
308 }
309 }
310 } else {
311 // this is where the in-place merge logic starts!
312 // 1. pull out two internal buffers each containing √A unique values
313 // 1a. adjust block_size and buffer_size if we couldn't find enough unique values
314 // 2. loop over the A and B subarrays within this level of the merge sort
315 // 3. break A and B into blocks of size 'block_size'
316 // 4. "tag" each of the A blocks with values from the first internal buffer
317 // 5. roll the A blocks through the B blocks and drop/rotate them where they belong
318 // 6. merge each A block with any B values that follow, using the cache or the second internal buffer
319 // 7. sort the second internal buffer if it exists
320 // 8. redistribute the two internal buffers back into the items
321 var block_size: usize = math.sqrt(iterator.length());
322 var buffer_size = iterator.length() / block_size + 1;
323
324 // as an optimization, we really only need to pull out the internal buffers once for each level of merges
325 // after that we can reuse the same buffers over and over, then redistribute it when we're finished with this level
326 var A: Range = undefined;
327 var B: Range = undefined;
328 var index: usize = 0;
329 var last: usize = 0;
330 var count: usize = 0;
331 var find: usize = 0;
332 var start: usize = 0;
333 var pull_index: usize = 0;
334 var pull = [_]Pull{
335 Pull{
336 .from = 0,
337 .to = 0,
338 .count = 0,
339 .range = Range.init(0, 0),
340 },
341 Pull{
342 .from = 0,
343 .to = 0,
344 .count = 0,
345 .range = Range.init(0, 0),
346 },
347 };
348
349 var buffer1 = Range.init(0, 0);
350 var buffer2 = Range.init(0, 0);
351
352 // find two internal buffers of size 'buffer_size' each
353 find = buffer_size + buffer_size;
354 var find_separately = false;
355
356 if (block_size <= cache.len) {
357 // if every A block fits into the cache then we won't need the second internal buffer,
358 // so we really only need to find 'buffer_size' unique values
359 find = buffer_size;
360 } else if (find > iterator.length()) {
361 // we can't fit both buffers into the same A or B subarray, so find two buffers separately
362 find = buffer_size;
363 find_separately = true;
364 }
365
366 // 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),
367 // or we need to find one buffer of < 2√A unique values, and a second buffer of √A unique values,
368 // OR if we couldn't find that many unique values, we need the largest possible buffer we can get
369
370 // in the case where it couldn't find a single buffer of at least √A unique values,
371 // all of the Merge steps must be replaced by a different merge algorithm (MergeInPlace)
372 iterator.begin();
373 while (!iterator.finished()) {
374 A = iterator.nextRange();
375 B = iterator.nextRange();
376
377 // just store information about where the values will be pulled from and to,
378 // as well as how many values there are, to create the two internal buffers
379
380 // check A for the number of unique values we need to fill an internal buffer
381 // these values will be pulled out to the start of A
382 last = A.start;
383 count = 1;
384 while (count < find) : ({
385 last = index;
386 count += 1;
387 }) {
388 index = findLastForward(T, items, items[last], Range.init(last + 1, A.end), find - count, context, lessThan);
389 if (index == A.end) break;
390 }
391 index = last;
392
393 if (count >= buffer_size) {
394 // keep track of the range within the items where we'll need to "pull out" these values to create the internal buffer
395 pull[pull_index] = Pull{
396 .range = Range.init(A.start, B.end),
397 .count = count,
398 .from = index,
399 .to = A.start,
400 };
401 pull_index = 1;
402
403 if (count == buffer_size + buffer_size) {
404 // we were able to find a single contiguous section containing 2√A unique values,
405 // so this section can be used to contain both of the internal buffers we'll need
406 buffer1 = Range.init(A.start, A.start + buffer_size);
407 buffer2 = Range.init(A.start + buffer_size, A.start + count);
408 break;
409 } else if (find == buffer_size + buffer_size) {
410 // we found a buffer that contains at least √A unique values, but did not contain the full 2√A unique values,
411 // so we still need to find a second separate buffer of at least √A unique values
412 buffer1 = Range.init(A.start, A.start + count);
413 find = buffer_size;
414 } else if (block_size <= cache.len) {
415 // we found the first and only internal buffer that we need, so we're done!
416 buffer1 = Range.init(A.start, A.start + count);
417 break;
418 } else if (find_separately) {
419 // found one buffer, but now find the other one
420 buffer1 = Range.init(A.start, A.start + count);
421 find_separately = false;
422 } else {
423 // we found a second buffer in an 'A' subarray containing √A unique values, so we're done!
424 buffer2 = Range.init(A.start, A.start + count);
425 break;
426 }
427 } else if (pull_index == 0 and count > buffer1.length()) {
428 // keep track of the largest buffer we were able to find
429 buffer1 = Range.init(A.start, A.start + count);
430 pull[pull_index] = Pull{
431 .range = Range.init(A.start, B.end),
432 .count = count,
433 .from = index,
434 .to = A.start,
435 };
436 }
437
438 // check B for the number of unique values we need to fill an internal buffer
439 // these values will be pulled out to the end of B
440 last = B.end - 1;
441 count = 1;
442 while (count < find) : ({
443 last = index - 1;
444 count += 1;
445 }) {
446 index = findFirstBackward(T, items, items[last], Range.init(B.start, last), find - count, context, lessThan);
447 if (index == B.start) break;
448 }
449 index = last;
450
451 if (count >= buffer_size) {
452 // keep track of the range within the items where we'll need to "pull out" these values to create the internal buffe
453 pull[pull_index] = Pull{
454 .range = Range.init(A.start, B.end),
455 .count = count,
456 .from = index,
457 .to = B.end,
458 };
459 pull_index = 1;
460
461 if (count == buffer_size + buffer_size) {
462 // we were able to find a single contiguous section containing 2√A unique values,
463 // so this section can be used to contain both of the internal buffers we'll need
464 buffer1 = Range.init(B.end - count, B.end - buffer_size);
465 buffer2 = Range.init(B.end - buffer_size, B.end);
466 break;
467 } else if (find == buffer_size + buffer_size) {
468 // we found a buffer that contains at least √A unique values, but did not contain the full 2√A unique values,
469 // so we still need to find a second separate buffer of at least √A unique values
470 buffer1 = Range.init(B.end - count, B.end);
471 find = buffer_size;
472 } else if (block_size <= cache.len) {
473 // we found the first and only internal buffer that we need, so we're done!
474 buffer1 = Range.init(B.end - count, B.end);
475 break;
476 } else if (find_separately) {
477 // found one buffer, but now find the other one
478 buffer1 = Range.init(B.end - count, B.end);
479 find_separately = false;
480 } else {
481 // buffer2 will be pulled out from a 'B' subarray, so if the first buffer was pulled out from the corresponding 'A' subarray,
482 // we need to adjust the end point for that A subarray so it knows to stop redistributing its values before reaching buffer2
483 if (pull[0].range.start == A.start) pull[0].range.end -= pull[1].count;
484
485 // we found a second buffer in an 'B' subarray containing √A unique values, so we're done!
486 buffer2 = Range.init(B.end - count, B.end);
487 break;
488 }
489 } else if (pull_index == 0 and count > buffer1.length()) {
490 // keep track of the largest buffer we were able to find
491 buffer1 = Range.init(B.end - count, B.end);
492 pull[pull_index] = Pull{
493 .range = Range.init(A.start, B.end),
494 .count = count,
495 .from = index,
496 .to = B.end,
497 };
498 }
499 }
500
501 // pull out the two ranges so we can use them as internal buffers
502 pull_index = 0;
503 while (pull_index < 2) : (pull_index += 1) {
504 const length = pull[pull_index].count;
505
506 if (pull[pull_index].to < pull[pull_index].from) {
507 // we're pulling the values out to the left, which means the start of an A subarray
508 index = pull[pull_index].from;
509 count = 1;
510 while (count < length) : (count += 1) {
511 index = findFirstBackward(T, items, items[index - 1], Range.init(pull[pull_index].to, pull[pull_index].from - (count - 1)), length - count, context, lessThan);
512 const range = Range.init(index + 1, pull[pull_index].from + 1);
513 mem.rotate(T, items[range.start..range.end], range.length() - count);
514 pull[pull_index].from = index + count;
515 }
516 } else if (pull[pull_index].to > pull[pull_index].from) {
517 // we're pulling values out to the right, which means the end of a B subarray
518 index = pull[pull_index].from + 1;
519 count = 1;
520 while (count < length) : (count += 1) {
521 index = findLastForward(T, items, items[index], Range.init(index, pull[pull_index].to), length - count, context, lessThan);
522 const range = Range.init(pull[pull_index].from, index - 1);
523 mem.rotate(T, items[range.start..range.end], count);
524 pull[pull_index].from = index - 1 - count;
525 }
526 }
527 }
528
529 // adjust block_size and buffer_size based on the values we were able to pull out
530 buffer_size = buffer1.length();
531 block_size = iterator.length() / buffer_size + 1;
532
533 // the first buffer NEEDS to be large enough to tag each of the evenly sized A blocks,
534 // so this was originally here to test the math for adjusting block_size above
535 // assert((iterator.length() + 1)/block_size <= buffer_size);
536
537 // 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!
538 iterator.begin();
539 while (!iterator.finished()) {
540 A = iterator.nextRange();
541 B = iterator.nextRange();
542
543 // remove any parts of A or B that are being used by the internal buffers
544 start = A.start;
545 if (start == pull[0].range.start) {
546 if (pull[0].from > pull[0].to) {
547 A.start += pull[0].count;
548
549 // if the internal buffer takes up the entire A or B subarray, then there's nothing to merge
550 // this only happens for very small subarrays, like √4 = 2, 2 * (2 internal buffers) = 4,
551 // which also only happens when cache.len is small or 0 since it'd otherwise use MergeExternal
552 if (A.length() == 0) continue;
553 } else if (pull[0].from < pull[0].to) {
554 B.end -= pull[0].count;
555 if (B.length() == 0) continue;
556 }
557 }
558 if (start == pull[1].range.start) {
559 if (pull[1].from > pull[1].to) {
560 A.start += pull[1].count;
561 if (A.length() == 0) continue;
562 } else if (pull[1].from < pull[1].to) {
563 B.end -= pull[1].count;
564 if (B.length() == 0) continue;
565 }
566 }
567
568 if (lessThan(context, items[B.end - 1], items[A.start])) {
569 // the two ranges are in reverse order, so a simple rotation should fix it
570 mem.rotate(T, items[A.start..B.end], A.length());
571 } else if (lessThan(context, items[A.end], items[A.end - 1])) {
572 // these two ranges weren't already in order, so we'll need to merge them!
573 var findA: usize = undefined;
574
575 // break the remainder of A into blocks. firstA is the uneven-sized first A block
576 var blockA = Range.init(A.start, A.end);
577 var firstA = Range.init(A.start, A.start + blockA.length() % block_size);
578
579 // swap the first value of each A block with the value in buffer1
580 var indexA = buffer1.start;
581 index = firstA.end;
582 while (index < blockA.end) : ({
583 indexA += 1;
584 index += block_size;
585 }) {
586 mem.swap(T, &items[indexA], &items[index]);
587 }
588
589 // start rolling the A blocks through the B blocks!
590 // 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
591 var lastA = firstA;
592 var lastB = Range.init(0, 0);
593 var blockB = Range.init(B.start, B.start + math.min(block_size, B.length()));
594 blockA.start += firstA.length();
595 indexA = buffer1.start;
596
597 // if the first unevenly sized A block fits into the cache, copy it there for when we go to Merge it
598 // otherwise, if the second buffer is available, block swap the contents into that
599 if (lastA.length() <= cache.len) {
600 const last_a_items = items[lastA.start..lastA.end];
601 @memcpy(cache[0..last_a_items.len], last_a_items);
602 } else if (buffer2.length() > 0) {
603 blockSwap(T, items, lastA.start, buffer2.start, lastA.length());
604 }
605
606 if (blockA.length() > 0) {
607 while (true) {
608 // 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,
609 // then drop that minimum A block behind. or if there are no B blocks left then keep dropping the remaining A blocks.
610 if ((lastB.length() > 0 and !lessThan(context, items[lastB.end - 1], items[indexA])) or blockB.length() == 0) {
611 // figure out where to split the previous B block, and rotate it at the split
612 const B_split = binaryFirst(T, items, items[indexA], lastB, context, lessThan);
613 const B_remaining = lastB.end - B_split;
614
615 // swap the minimum A block to the beginning of the rolling A blocks
616 var minA = blockA.start;
617 findA = minA + block_size;
618 while (findA < blockA.end) : (findA += block_size) {
619 if (lessThan(context, items[findA], items[minA])) {
620 minA = findA;
621 }
622 }
623 blockSwap(T, items, blockA.start, minA, block_size);
624
625 // swap the first item of the previous A block back with its original value, which is stored in buffer1
626 mem.swap(T, &items[blockA.start], &items[indexA]);
627 indexA += 1;
628
629 // locally merge the previous A block with the B values that follow it
630 // if lastA fits into the external cache we'll use that (with MergeExternal),
631 // or if the second internal buffer exists we'll use that (with MergeInternal),
632 // or failing that we'll use a strictly in-place merge algorithm (MergeInPlace)
633
634 if (lastA.length() <= cache.len) {
635 mergeExternal(T, items, lastA, Range.init(lastA.end, B_split), cache[0..], context, lessThan);
636 } else if (buffer2.length() > 0) {
637 mergeInternal(T, items, lastA, Range.init(lastA.end, B_split), buffer2, context, lessThan);
638 } else {
639 mergeInPlace(T, items, lastA, Range.init(lastA.end, B_split), context, lessThan);
640 }
641
642 if (buffer2.length() > 0 or block_size <= cache.len) {
643 // 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
644 if (block_size <= cache.len) {
645 @memcpy(cache[0..block_size], items[blockA.start..][0..block_size]);
646 } else {
647 blockSwap(T, items, blockA.start, buffer2.start, block_size);
648 }
649
650 // this is equivalent to rotating, but faster
651 // 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
652 // 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
653 blockSwap(T, items, B_split, blockA.start + block_size - B_remaining, B_remaining);
654 } else {
655 // we are unable to use the 'buffer2' trick to speed up the rotation operation since buffer2 doesn't exist, so perform a normal rotation
656 mem.rotate(T, items[B_split .. blockA.start + block_size], blockA.start - B_split);
657 }
658
659 // update the range for the remaining A blocks, and the range remaining from the B block after it was split
660 lastA = Range.init(blockA.start - B_remaining, blockA.start - B_remaining + block_size);
661 lastB = Range.init(lastA.end, lastA.end + B_remaining);
662
663 // if there are no more A blocks remaining, this step is finished!
664 blockA.start += block_size;
665 if (blockA.length() == 0) break;
666 } else if (blockB.length() < block_size) {
667 // move the last B block, which is unevenly sized, to before the remaining A blocks, by using a rotation
668 // the cache is disabled here since it might contain the contents of the previous A block
669 mem.rotate(T, items[blockA.start..blockB.end], blockB.start - blockA.start);
670
671 lastB = Range.init(blockA.start, blockA.start + blockB.length());
672 blockA.start += blockB.length();
673 blockA.end += blockB.length();
674 blockB.end = blockB.start;
675 } else {
676 // roll the leftmost A block to the end by swapping it with the next B block
677 blockSwap(T, items, blockA.start, blockB.start, block_size);
678 lastB = Range.init(blockA.start, blockA.start + block_size);
679
680 blockA.start += block_size;
681 blockA.end += block_size;
682 blockB.start += block_size;
683
684 if (blockB.end > B.end - block_size) {
685 blockB.end = B.end;
686 } else {
687 blockB.end += block_size;
688 }
689 }
690 }
691 }
692
693 // merge the last A block with the remaining B values
694 if (lastA.length() <= cache.len) {
695 mergeExternal(T, items, lastA, Range.init(lastA.end, B.end), cache[0..], context, lessThan);
696 } else if (buffer2.length() > 0) {
697 mergeInternal(T, items, lastA, Range.init(lastA.end, B.end), buffer2, context, lessThan);
698 } else {
699 mergeInPlace(T, items, lastA, Range.init(lastA.end, B.end), context, lessThan);
700 }
701 }
702 }
703
704 // when we're finished with this merge step we should have the one
705 // or two internal buffers left over, where the second buffer is all jumbled up
706 // insertion sort the second buffer, then redistribute the buffers
707 // back into the items using the opposite process used for creating the buffer
708
709 // while an unstable sort like quicksort could be applied here, in benchmarks
710 // it was consistently slightly slower than a simple insertion sort,
711 // even for tens of millions of items. this may be because insertion
712 // sort is quite fast when the data is already somewhat sorted, like it is here
713 sort.insertion(T, items[buffer2.start..buffer2.end], context, lessThan);
714
715 pull_index = 0;
716 while (pull_index < 2) : (pull_index += 1) {
717 var unique = pull[pull_index].count * 2;
718 if (pull[pull_index].from > pull[pull_index].to) {
719 // the values were pulled out to the left, so redistribute them back to the right
720 var buffer = Range.init(pull[pull_index].range.start, pull[pull_index].range.start + pull[pull_index].count);
721 while (buffer.length() > 0) {
722 index = findFirstForward(T, items, items[buffer.start], Range.init(buffer.end, pull[pull_index].range.end), unique, context, lessThan);
723 const amount = index - buffer.end;
724 mem.rotate(T, items[buffer.start..index], buffer.length());
725 buffer.start += (amount + 1);
726 buffer.end += amount;
727 unique -= 2;
728 }
729 } else if (pull[pull_index].from < pull[pull_index].to) {
730 // the values were pulled out to the right, so redistribute them back to the left
731 var buffer = Range.init(pull[pull_index].range.end - pull[pull_index].count, pull[pull_index].range.end);
732 while (buffer.length() > 0) {
733 index = findLastBackward(T, items, items[buffer.end - 1], Range.init(pull[pull_index].range.start, buffer.start), unique, context, lessThan);
734 const amount = buffer.start - index;
735 mem.rotate(T, items[index..buffer.end], amount);
736 buffer.start -= amount;
737 buffer.end -= (amount + 1);
738 unique -= 2;
739 }
740 }
741 }
742 }
743
744 // double the size of each A and B subarray that will be merged in the next level
745 if (!iterator.nextLevel()) break;
746 }
747}
748// merge operation without a buffer
749fn mergeInPlace(
750 comptime T: type,
751 items: []T,
752 A_arg: Range,
753 B_arg: Range,
754 context: anytype,
755 comptime lessThan: fn (@TypeOf(context), lhs: T, rhs: T) bool,
756) void {
757 if (A_arg.length() == 0 or B_arg.length() == 0) return;
758
759 // this just repeatedly binary searches into B and rotates A into position.
760 // the paper suggests using the 'rotation-based Hwang and Lin algorithm' here,
761 // but I decided to stick with this because it had better situational performance
762 //
763 // (Hwang and Lin is designed for merging subarrays of very different sizes,
764 // but WikiSort almost always uses subarrays that are roughly the same size)
765 //
766 // normally this is incredibly suboptimal, but this function is only called
767 // when none of the A or B blocks in any subarray contained 2√A unique values,
768 // which places a hard limit on the number of times this will ACTUALLY need
769 // to binary search and rotate.
770 //
771 // according to my analysis the worst case is √A rotations performed on √A items
772 // once the constant factors are removed, which ends up being O(n)
773 //
774 // again, this is NOT a general-purpose solution – it only works well in this case!
775 // kind of like how the O(n^2) insertion sort is used in some places
776
777 var A = A_arg;
778 var B = B_arg;
779
780 while (true) {
781 // find the first place in B where the first item in A needs to be inserted
782 const mid = binaryFirst(T, items, items[A.start], B, context, lessThan);
783
784 // rotate A into place
785 const amount = mid - A.end;
786 mem.rotate(T, items[A.start..mid], A.length());
787 if (B.end == mid) break;
788
789 // calculate the new A and B ranges
790 B.start = mid;
791 A = Range.init(A.start + amount, B.start);
792 A.start = binaryLast(T, items, items[A.start], A, context, lessThan);
793 if (A.length() == 0) break;
794 }
795}
796
797// merge operation using an internal buffer
798fn mergeInternal(
799 comptime T: type,
800 items: []T,
801 A: Range,
802 B: Range,
803 buffer: Range,
804 context: anytype,
805 comptime lessThan: fn (@TypeOf(context), lhs: T, rhs: T) bool,
806) void {
807 // whenever we find a value to add to the final array, swap it with the value that's already in that spot
808 // when this algorithm is finished, 'buffer' will contain its original contents, but in a different order
809 var A_count: usize = 0;
810 var B_count: usize = 0;
811 var insert: usize = 0;
812
813 if (B.length() > 0 and A.length() > 0) {
814 while (true) {
815 if (!lessThan(context, items[B.start + B_count], items[buffer.start + A_count])) {
816 mem.swap(T, &items[A.start + insert], &items[buffer.start + A_count]);
817 A_count += 1;
818 insert += 1;
819 if (A_count >= A.length()) break;
820 } else {
821 mem.swap(T, &items[A.start + insert], &items[B.start + B_count]);
822 B_count += 1;
823 insert += 1;
824 if (B_count >= B.length()) break;
825 }
826 }
827 }
828
829 // swap the remainder of A into the final array
830 blockSwap(T, items, buffer.start + A_count, A.start + insert, A.length() - A_count);
831}
832
833fn blockSwap(comptime T: type, items: []T, start1: usize, start2: usize, block_size: usize) void {
834 var index: usize = 0;
835 while (index < block_size) : (index += 1) {
836 mem.swap(T, &items[start1 + index], &items[start2 + index]);
837 }
838}
839
840// combine a linear search with a binary search to reduce the number of comparisons in situations
841// where have some idea as to how many unique values there are and where the next value might be
842fn findFirstForward(
843 comptime T: type,
844 items: []T,
845 value: T,
846 range: Range,
847 unique: usize,
848 context: anytype,
849 comptime lessThan: fn (@TypeOf(context), lhs: T, rhs: T) bool,
850) usize {
851 if (range.length() == 0) return range.start;
852 const skip = math.max(range.length() / unique, @as(usize, 1));
853
854 var index = range.start + skip;
855 while (lessThan(context, items[index - 1], value)) : (index += skip) {
856 if (index >= range.end - skip) {
857 return binaryFirst(T, items, value, Range.init(index, range.end), context, lessThan);
858 }
859 }
860
861 return binaryFirst(T, items, value, Range.init(index - skip, index), context, lessThan);
862}
863
864fn findFirstBackward(
865 comptime T: type,
866 items: []T,
867 value: T,
868 range: Range,
869 unique: usize,
870 context: anytype,
871 comptime lessThan: fn (@TypeOf(context), lhs: T, rhs: T) bool,
872) usize {
873 if (range.length() == 0) return range.start;
874 const skip = math.max(range.length() / unique, @as(usize, 1));
875
876 var index = range.end - skip;
877 while (index > range.start and !lessThan(context, items[index - 1], value)) : (index -= skip) {
878 if (index < range.start + skip) {
879 return binaryFirst(T, items, value, Range.init(range.start, index), context, lessThan);
880 }
881 }
882
883 return binaryFirst(T, items, value, Range.init(index, index + skip), context, lessThan);
884}
885
886fn findLastForward(
887 comptime T: type,
888 items: []T,
889 value: T,
890 range: Range,
891 unique: usize,
892 context: anytype,
893 comptime lessThan: fn (@TypeOf(context), lhs: T, rhs: T) bool,
894) usize {
895 if (range.length() == 0) return range.start;
896 const skip = math.max(range.length() / unique, @as(usize, 1));
897
898 var index = range.start + skip;
899 while (!lessThan(context, value, items[index - 1])) : (index += skip) {
900 if (index >= range.end - skip) {
901 return binaryLast(T, items, value, Range.init(index, range.end), context, lessThan);
902 }
903 }
904
905 return binaryLast(T, items, value, Range.init(index - skip, index), context, lessThan);
906}
907
908fn findLastBackward(
909 comptime T: type,
910 items: []T,
911 value: T,
912 range: Range,
913 unique: usize,
914 context: anytype,
915 comptime lessThan: fn (@TypeOf(context), lhs: T, rhs: T) bool,
916) usize {
917 if (range.length() == 0) return range.start;
918 const skip = math.max(range.length() / unique, @as(usize, 1));
919
920 var index = range.end - skip;
921 while (index > range.start and lessThan(context, value, items[index - 1])) : (index -= skip) {
922 if (index < range.start + skip) {
923 return binaryLast(T, items, value, Range.init(range.start, index), context, lessThan);
924 }
925 }
926
927 return binaryLast(T, items, value, Range.init(index, index + skip), context, lessThan);
928}
929
930fn binaryFirst(
931 comptime T: type,
932 items: []T,
933 value: T,
934 range: Range,
935 context: anytype,
936 comptime lessThan: fn (@TypeOf(context), lhs: T, rhs: T) bool,
937) usize {
938 var curr = range.start;
939 var size = range.length();
940 if (range.start >= range.end) return range.end;
941 while (size > 0) {
942 const offset = size % 2;
943
944 size /= 2;
945 const mid_item = items[curr + size];
946 if (lessThan(context, mid_item, value)) {
947 curr += size + offset;
948 }
949 }
950 return curr;
951}
952
953fn binaryLast(
954 comptime T: type,
955 items: []T,
956 value: T,
957 range: Range,
958 context: anytype,
959 comptime lessThan: fn (@TypeOf(context), lhs: T, rhs: T) bool,
960) usize {
961 var curr = range.start;
962 var size = range.length();
963 if (range.start >= range.end) return range.end;
964 while (size > 0) {
965 const offset = size % 2;
966
967 size /= 2;
968 const mid_item = items[curr + size];
969 if (!lessThan(context, value, mid_item)) {
970 curr += size + offset;
971 }
972 }
973 return curr;
974}
975
976fn mergeInto(
977 comptime T: type,
978 from: []T,
979 A: Range,
980 B: Range,
981 into: []T,
982 context: anytype,
983 comptime lessThan: fn (@TypeOf(context), lhs: T, rhs: T) bool,
984) void {
985 var A_index: usize = A.start;
986 var B_index: usize = B.start;
987 const A_last = A.end;
988 const B_last = B.end;
989 var insert_index: usize = 0;
990
991 while (true) {
992 if (!lessThan(context, from[B_index], from[A_index])) {
993 into[insert_index] = from[A_index];
994 A_index += 1;
995 insert_index += 1;
996 if (A_index == A_last) {
997 // copy the remainder of B into the final array
998 const from_b = from[B_index..B_last];
999 @memcpy(into[insert_index..][0..from_b.len], from_b);
1000 break;
1001 }
1002 } else {
1003 into[insert_index] = from[B_index];
1004 B_index += 1;
1005 insert_index += 1;
1006 if (B_index == B_last) {
1007 // copy the remainder of A into the final array
1008 const from_a = from[A_index..A_last];
1009 @memcpy(into[insert_index..][0..from_a.len], from_a);
1010 break;
1011 }
1012 }
1013 }
1014}
1015
1016fn mergeExternal(
1017 comptime T: type,
1018 items: []T,
1019 A: Range,
1020 B: Range,
1021 cache: []T,
1022 context: anytype,
1023 comptime lessThan: fn (@TypeOf(context), lhs: T, rhs: T) bool,
1024) void {
1025 // A fits into the cache, so use that instead of the internal buffer
1026 var A_index: usize = 0;
1027 var B_index: usize = B.start;
1028 var insert_index: usize = A.start;
1029 const A_last = A.length();
1030 const B_last = B.end;
1031
1032 if (B.length() > 0 and A.length() > 0) {
1033 while (true) {
1034 if (!lessThan(context, items[B_index], cache[A_index])) {
1035 items[insert_index] = cache[A_index];
1036 A_index += 1;
1037 insert_index += 1;
1038 if (A_index == A_last) break;
1039 } else {
1040 items[insert_index] = items[B_index];
1041 B_index += 1;
1042 insert_index += 1;
1043 if (B_index == B_last) break;
1044 }
1045 }
1046 }
1047
1048 // copy the remainder of A into the final array
1049 const cache_a = cache[A_index..A_last];
1050 @memcpy(items[insert_index..][0..cache_a.len], cache_a);
1051}
1052
1053fn swap(
1054 comptime T: type,
1055 items: []T,
1056 order: *[8]u8,
1057 x: usize,
1058 y: usize,
1059 context: anytype,
1060 comptime lessThan: fn (@TypeOf(context), lhs: T, rhs: T) bool,
1061) void {
1062 if (lessThan(context, items[y], items[x]) or ((order.*)[x] > (order.*)[y] and !lessThan(context, items[x], items[y]))) {
1063 mem.swap(T, &items[x], &items[y]);
1064 mem.swap(u8, &(order.*)[x], &(order.*)[y]);
1065 }
1066}
lib/std/sort/pdq.zig created+331
...@@ -0,0 +1,331 @@
1const std = @import("../std.zig");
2const sort = std.sort;
3const mem = std.mem;
4const math = std.math;
5const testing = std.testing;
6
7/// Unstable in-place sort. n best case, n*log(n) worst case and average case.
8/// log(n) memory (no allocator required).
9///
10/// Sorts in ascending order with respect to the given `lessThan` function.
11pub fn pdq(
12 comptime T: type,
13 items: []T,
14 context: anytype,
15 comptime lessThanFn: fn (context: @TypeOf(context), lhs: T, rhs: T) bool,
16) void {
17 const Context = struct {
18 items: []T,
19 sub_ctx: @TypeOf(context),
20
21 pub fn lessThan(ctx: @This(), a: usize, b: usize) bool {
22 return lessThanFn(ctx.sub_ctx, ctx.items[a], ctx.items[b]);
23 }
24
25 pub fn swap(ctx: @This(), a: usize, b: usize) void {
26 return mem.swap(T, &ctx.items[a], &ctx.items[b]);
27 }
28 };
29 pdqContext(0, items.len, Context{ .items = items, .sub_ctx = context });
30}
31
32const Hint = enum {
33 increasing,
34 decreasing,
35 unknown,
36};
37
38/// Unstable in-place sort. O(n) best case, O(n*log(n)) worst case and average case.
39/// O(log(n)) memory (no allocator required).
40///
41/// Sorts in ascending order with respect to the given `lessThan` function.
42pub fn pdqContext(a: usize, b: usize, context: anytype) void {
43 // slices of up to this length get sorted using insertion sort.
44 const max_insertion = 24;
45 // number of allowed imbalanced partitions before switching to heap sort.
46 const max_limit = std.math.floorPowerOfTwo(usize, b) + 1;
47
48 // set upper bound on stack memory usage.
49 const Range = struct { a: usize, b: usize, limit: usize };
50 const stack_size = math.log2(math.maxInt(usize) + 1);
51 var stack: [stack_size]Range = undefined;
52 var range = Range{ .a = a, .b = b, .limit = max_limit };
53 var top: usize = 0;
54
55 while (true) {
56 var was_balanced = true;
57 var was_partitioned = true;
58
59 while (true) {
60 const len = range.b - range.a;
61
62 // very short slices get sorted using insertion sort.
63 if (len <= max_insertion) {
64 break sort.insertionContext(range.a, range.b, context);
65 }
66
67 // if too many bad pivot choices were made, simply fall back to heapsort in order to
68 // guarantee O(n*log(n)) worst-case.
69 if (range.limit == 0) {
70 break sort.heapContext(range.a, range.b, context);
71 }
72
73 // if the last partitioning was imbalanced, try breaking patterns in the slice by shuffling
74 // some elements around. Hopefully we'll choose a better pivot this time.
75 if (!was_balanced) {
76 breakPatterns(range.a, range.b, context);
77 range.limit -= 1;
78 }
79
80 // choose a pivot and try guessing whether the slice is already sorted.
81 var pivot: usize = 0;
82 var hint = chosePivot(range.a, range.b, &pivot, context);
83
84 if (hint == .decreasing) {
85 // The maximum number of swaps was performed, so items are likely
86 // in reverse order. Reverse it to make sorting faster.
87 reverseRange(range.a, range.b, context);
88 pivot = (range.b - 1) - (pivot - range.a);
89 hint = .increasing;
90 }
91
92 // if the last partitioning was decently balanced and didn't shuffle elements, and if pivot
93 // selection predicts the slice is likely already sorted...
94 if (was_balanced and was_partitioned and hint == .increasing) {
95 // try identifying several out-of-order elements and shifting them to correct
96 // positions. If the slice ends up being completely sorted, we're done.
97 if (partialInsertionSort(range.a, range.b, context)) break;
98 }
99
100 // if the chosen pivot is equal to the predecessor, then it's the smallest element in the
101 // slice. Partition the slice into elements equal to and elements greater than the pivot.
102 // This case is usually hit when the slice contains many duplicate elements.
103 if (range.a > 0 and !context.lessThan(range.a - 1, pivot)) {
104 range.a = partitionEqual(range.a, range.b, pivot, context);
105 continue;
106 }
107
108 // partition the slice.
109 var mid = pivot;
110 was_partitioned = partition(range.a, range.b, &mid, context);
111
112 const left_len = mid - range.a;
113 const right_len = range.b - mid;
114 const balanced_threshold = len / 8;
115 if (left_len < right_len) {
116 was_balanced = left_len >= balanced_threshold;
117 stack[top] = .{ .a = range.a, .b = mid, .limit = range.limit };
118 top += 1;
119 range.a = mid + 1;
120 } else {
121 was_balanced = right_len >= balanced_threshold;
122 stack[top] = .{ .a = mid + 1, .b = range.b, .limit = range.limit };
123 top += 1;
124 range.b = mid;
125 }
126 }
127
128 top = math.sub(usize, top, 1) catch break;
129 range = stack[top];
130 }
131}
132
133/// partitions `items[a..b]` into elements smaller than `items[pivot]`,
134/// followed by elements greater than or equal to `items[pivot]`.
135///
136/// sets the new pivot.
137/// returns `true` if already partitioned.
138fn partition(a: usize, b: usize, pivot: *usize, context: anytype) bool {
139 // move pivot to the first place
140 context.swap(a, pivot.*);
141
142 var i = a + 1;
143 var j = b - 1;
144
145 while (i <= j and context.lessThan(i, a)) i += 1;
146 while (i <= j and !context.lessThan(j, a)) j -= 1;
147
148 // check if items are already partitioned (no item to swap)
149 if (i > j) {
150 // put pivot back to the middle
151 context.swap(j, a);
152 pivot.* = j;
153 return true;
154 }
155
156 context.swap(i, j);
157 i += 1;
158 j -= 1;
159
160 while (true) {
161 while (i <= j and context.lessThan(i, a)) i += 1;
162 while (i <= j and !context.lessThan(j, a)) j -= 1;
163 if (i > j) break;
164
165 context.swap(i, j);
166 i += 1;
167 j -= 1;
168 }
169
170 // TODO: Enable the BlockQuicksort optimization
171
172 context.swap(j, a);
173 pivot.* = j;
174 return false;
175}
176
177/// partitions items into elements equal to `items[pivot]`
178/// followed by elements greater than `items[pivot]`.
179///
180/// it assumed that `items[a..b]` does not contain elements smaller than the `items[pivot]`.
181fn partitionEqual(a: usize, b: usize, pivot: usize, context: anytype) usize {
182 // move pivot to the first place
183 context.swap(a, pivot);
184
185 var i = a + 1;
186 var j = b - 1;
187
188 while (true) {
189 while (i <= j and !context.lessThan(a, i)) i += 1;
190 while (i <= j and context.lessThan(a, j)) j -= 1;
191 if (i > j) break;
192
193 context.swap(i, j);
194 i += 1;
195 j -= 1;
196 }
197
198 return i;
199}
200
201/// partially sorts a slice by shifting several out-of-order elements around.
202///
203/// returns `true` if the slice is sorted at the end. This function is `O(n)` worst-case.
204fn partialInsertionSort(a: usize, b: usize, context: anytype) bool {
205 @setCold(true);
206
207 // maximum number of adjacent out-of-order pairs that will get shifted
208 const max_steps = 5;
209 // if the slice is shorter than this, don't shift any elements
210 const shortest_shifting = 50;
211
212 var i = a + 1;
213 for (0..max_steps) |_| {
214 // find the next pair of adjacent out-of-order elements.
215 while (i < b and !context.lessThan(i, i - 1)) i += 1;
216
217 // are we done?
218 if (i == b) return true;
219
220 // don't shift elements on short arrays, that has a performance cost.
221 if (b - a < shortest_shifting) return false;
222
223 // swap the found pair of elements. This puts them in correct order.
224 context.swap(i, i - 1);
225
226 // shift the smaller element to the left.
227 if (i - a >= 2) {
228 var j = i - 1;
229 while (j >= 1) : (j -= 1) {
230 if (!context.lessThan(j, j - 1)) break;
231 context.swap(j, j - 1);
232 }
233 }
234
235 // shift the greater element to the right.
236 if (b - i >= 2) {
237 var j = i + 1;
238 while (j < b) : (j += 1) {
239 if (!context.lessThan(j, j - 1)) break;
240 context.swap(j, j - 1);
241 }
242 }
243 }
244
245 return false;
246}
247
248fn breakPatterns(a: usize, b: usize, context: anytype) void {
249 @setCold(true);
250
251 const len = b - a;
252 if (len < 8) return;
253
254 var rand = @intCast(u64, len);
255 const modulus = math.ceilPowerOfTwoAssert(u64, len);
256
257 var i = a + (len / 4) * 2 - 1;
258 while (i <= a + (len / 4) * 2 + 1) : (i += 1) {
259 // xorshift64
260 rand ^= rand << 13;
261 rand ^= rand >> 7;
262 rand ^= rand << 17;
263
264 var other = @intCast(usize, rand & (modulus - 1));
265 if (other >= len) other -= len;
266 context.swap(i, a + other);
267 }
268}
269
270/// choses a pivot in `items[a..b]`.
271/// swaps likely_sorted when `items[a..b]` seems to be already sorted.
272fn chosePivot(a: usize, b: usize, pivot: *usize, context: anytype) Hint {
273 // minimum length for using the Tukey's ninther method
274 const shortest_ninther = 50;
275 // max_swaps is the maximum number of swaps allowed in this function
276 const max_swaps = 4 * 3;
277
278 var len = b - a;
279 var i = a + len / 4 * 1;
280 var j = a + len / 4 * 2;
281 var k = a + len / 4 * 3;
282 var swaps: usize = 0;
283
284 if (len >= 8) {
285 if (len >= shortest_ninther) {
286 // find medians in the neighborhoods of `i`, `j` and `k`
287 i = sort3(i - 1, i, i + 1, &swaps, context);
288 j = sort3(j - 1, j, j + 1, &swaps, context);
289 k = sort3(k - 1, k, k + 1, &swaps, context);
290 }
291
292 // find the median among `i`, `j` and `k`
293 j = sort3(i, j, k, &swaps, context);
294 }
295
296 pivot.* = j;
297 return switch (swaps) {
298 0 => .increasing,
299 max_swaps => .decreasing,
300 else => .unknown,
301 };
302}
303
304fn sort3(a: usize, b: usize, c: usize, swaps: *usize, context: anytype) usize {
305 if (context.lessThan(b, a)) {
306 swaps.* += 1;
307 context.swap(b, a);
308 }
309
310 if (context.lessThan(c, b)) {
311 swaps.* += 1;
312 context.swap(c, b);
313 }
314
315 if (context.lessThan(b, a)) {
316 swaps.* += 1;
317 context.swap(b, a);
318 }
319
320 return b;
321}
322
323fn reverseRange(a: usize, b: usize, context: anytype) void {
324 var i = a;
325 var j = b - 1;
326 while (i < j) {
327 context.swap(i, j);
328 i += 1;
329 j -= 1;
330 }
331}
src/Compilation.zig+1-1
...@@ -672,7 +672,7 @@ fn addPackageTableToCacheHash(...@@ -672,7 +672,7 @@ fn addPackageTableToCacheHash(
672 }672 }
673 }673 }
674 // Sort the slice by package name674 // Sort the slice by package name
675 std.sort.sort(Package.Table.KV, packages, {}, struct {675 mem.sort(Package.Table.KV, packages, {}, struct {
676 fn lessThan(_: void, lhs: Package.Table.KV, rhs: Package.Table.KV) bool {676 fn lessThan(_: void, lhs: Package.Table.KV, rhs: Package.Table.KV) bool {
677 return std.mem.lessThan(u8, lhs.key, rhs.key);677 return std.mem.lessThan(u8, lhs.key, rhs.key);
678 }678 }
src/Package.zig+1-1
...@@ -672,7 +672,7 @@ fn computePackageHash(...@@ -672,7 +672,7 @@ fn computePackageHash(
672 }672 }
673 }673 }
674674
675 std.sort.sort(*HashedFile, all_files.items, {}, HashedFile.lessThan);675 mem.sort(*HashedFile, all_files.items, {}, HashedFile.lessThan);
676676
677 var hasher = Manifest.Hash.init(.{});677 var hasher = Manifest.Hash.init(.{});
678 var any_failures = false;678 var any_failures = false;
src/RangeSet.zig+1-1
...@@ -60,7 +60,7 @@ pub fn spans(self: *RangeSet, first: Value, last: Value, ty: Type) !bool {...@@ -60,7 +60,7 @@ pub fn spans(self: *RangeSet, first: Value, last: Value, ty: Type) !bool {
60 if (self.ranges.items.len == 0)60 if (self.ranges.items.len == 0)
61 return false;61 return false;
6262
63 std.sort.sort(Range, self.ranges.items, LessThanContext{63 std.mem.sort(Range, self.ranges.items, LessThanContext{
64 .ty = ty,64 .ty = ty,
65 .module = self.module,65 .module = self.module,
66 }, lessThan);66 }, lessThan);
src/Sema.zig+1-1
...@@ -30979,7 +30979,7 @@ fn resolveStructLayout(sema: *Sema, ty: Type) CompileError!void {...@@ -30979,7 +30979,7 @@ fn resolveStructLayout(sema: *Sema, ty: Type) CompileError!void {
30979 ctx.struct_obj.fields.values()[b].ty.abiAlignment(target);30979 ctx.struct_obj.fields.values()[b].ty.abiAlignment(target);
30980 }30980 }
30981 };30981 };
30982 std.sort.sort(u32, optimized_order, AlignSortContext{30982 mem.sort(u32, optimized_order, AlignSortContext{
30983 .struct_obj = struct_obj,30983 .struct_obj = struct_obj,
30984 .sema = sema,30984 .sema = sema,
30985 }, AlignSortContext.lessThan);30985 }, AlignSortContext.lessThan);
src/arch/x86_64/CodeGen.zig+1-1
...@@ -2176,7 +2176,7 @@ fn computeFrameLayout(self: *Self) !FrameLayout {...@@ -2176,7 +2176,7 @@ fn computeFrameLayout(self: *Self) !FrameLayout {
2176 }2176 }
2177 };2177 };
2178 const sort_context = SortContext{ .frame_align = frame_align };2178 const sort_context = SortContext{ .frame_align = frame_align };
2179 std.sort.sort(FrameIndex, stack_frame_order, sort_context, SortContext.lessThan);2179 mem.sort(FrameIndex, stack_frame_order, sort_context, SortContext.lessThan);
2180 }2180 }
21812181
2182 const call_frame_align = frame_align[@enumToInt(FrameIndex.call_frame)];2182 const call_frame_align = frame_align[@enumToInt(FrameIndex.call_frame)];
src/arch/x86_64/Encoding.zig+1-1
...@@ -770,7 +770,7 @@ const mnemonic_to_encodings_map = init: {...@@ -770,7 +770,7 @@ const mnemonic_to_encodings_map = init: {
770 @setEvalBranchQuota(30_000);770 @setEvalBranchQuota(30_000);
771 const encodings = @import("encodings.zig");771 const encodings = @import("encodings.zig");
772 var entries = encodings.table;772 var entries = encodings.table;
773 std.sort.sort(encodings.Entry, &entries, {}, struct {773 std.mem.sort(encodings.Entry, &entries, {}, struct {
774 fn lessThan(_: void, lhs: encodings.Entry, rhs: encodings.Entry) bool {774 fn lessThan(_: void, lhs: encodings.Entry, rhs: encodings.Entry) bool {
775 return @enumToInt(lhs[0]) < @enumToInt(rhs[0]);775 return @enumToInt(lhs[0]) < @enumToInt(rhs[0]);
776 }776 }
src/codegen/c/type.zig+1-1
...@@ -1292,7 +1292,7 @@ pub const CType = extern union {...@@ -1292,7 +1292,7 @@ pub const CType = extern union {
1292 fn sortFields(self: *@This(), fields_len: usize) []Payload.Fields.Field {1292 fn sortFields(self: *@This(), fields_len: usize) []Payload.Fields.Field {
1293 const Field = Payload.Fields.Field;1293 const Field = Payload.Fields.Field;
1294 const slice = self.storage.anon.fields[0..fields_len];1294 const slice = self.storage.anon.fields[0..fields_len];
1295 std.sort.sort(Field, slice, {}, struct {1295 mem.sort(Field, slice, {}, struct {
1296 fn before(_: void, lhs: Field, rhs: Field) bool {1296 fn before(_: void, lhs: Field, rhs: Field) bool {
1297 return lhs.alignas.@"align" > rhs.alignas.@"align";1297 return lhs.alignas.@"align" > rhs.alignas.@"align";
1298 }1298 }
src/link/Coff.zig+1-1
...@@ -1837,7 +1837,7 @@ fn writeBaseRelocations(self: *Coff) !void {...@@ -1837,7 +1837,7 @@ fn writeBaseRelocations(self: *Coff) !void {
1837 pages.appendAssumeCapacity(page.*);1837 pages.appendAssumeCapacity(page.*);
1838 }1838 }
1839 }1839 }
1840 std.sort.sort(u32, pages.items, {}, std.sort.asc(u32));1840 mem.sort(u32, pages.items, {}, std.sort.asc(u32));
18411841
1842 var buffer = std.ArrayList(u8).init(gpa);1842 var buffer = std.ArrayList(u8).init(gpa);
1843 defer buffer.deinit();1843 defer buffer.deinit();
src/link/MachO/Object.zig+4-4
...@@ -209,7 +209,7 @@ pub fn parse(self: *Object, allocator: Allocator, cpu_arch: std.Target.Cpu.Arch)...@@ -209,7 +209,7 @@ pub fn parse(self: *Object, allocator: Allocator, cpu_arch: std.Target.Cpu.Arch)
209 // afterwards by address in each group. Normally, dysymtab should209 // afterwards by address in each group. Normally, dysymtab should
210 // be enough to guarantee the sort, but turns out not every compiler210 // be enough to guarantee the sort, but turns out not every compiler
211 // is kind enough to specify the symbols in the correct order.211 // is kind enough to specify the symbols in the correct order.
212 sort.sort(SymbolAtIndex, sorted_all_syms.items, self, SymbolAtIndex.lessThan);212 mem.sort(SymbolAtIndex, sorted_all_syms.items, self, SymbolAtIndex.lessThan);
213213
214 var prev_sect_id: u8 = 0;214 var prev_sect_id: u8 = 0;
215 var section_index_lookup: ?Entry = null;215 var section_index_lookup: ?Entry = null;
...@@ -462,7 +462,7 @@ pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void {...@@ -462,7 +462,7 @@ pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void {
462 sorted_sections[id] = .{ .header = sect, .id = @intCast(u8, id) };462 sorted_sections[id] = .{ .header = sect, .id = @intCast(u8, id) };
463 }463 }
464464
465 std.sort.sort(SortedSection, sorted_sections, {}, sectionLessThanByAddress);465 mem.sort(SortedSection, sorted_sections, {}, sectionLessThanByAddress);
466466
467 var sect_sym_index: u32 = 0;467 var sect_sym_index: u32 = 0;
468 for (sorted_sections) |section| {468 for (sorted_sections) |section| {
...@@ -663,7 +663,7 @@ fn parseRelocs(self: *Object, gpa: Allocator, sect_id: u8) !void {...@@ -663,7 +663,7 @@ fn parseRelocs(self: *Object, gpa: Allocator, sect_id: u8) !void {
663 if (self.getSourceRelocs(section)) |relocs| {663 if (self.getSourceRelocs(section)) |relocs| {
664 try self.relocations.ensureUnusedCapacity(gpa, relocs.len);664 try self.relocations.ensureUnusedCapacity(gpa, relocs.len);
665 self.relocations.appendUnalignedSliceAssumeCapacity(relocs);665 self.relocations.appendUnalignedSliceAssumeCapacity(relocs);
666 std.sort.sort(macho.relocation_info, self.relocations.items[start..], {}, relocGreaterThan);666 mem.sort(macho.relocation_info, self.relocations.items[start..], {}, relocGreaterThan);
667 }667 }
668 self.section_relocs_lookup.items[sect_id] = start;668 self.section_relocs_lookup.items[sect_id] = start;
669}669}
...@@ -901,7 +901,7 @@ pub fn parseDataInCode(self: *Object, gpa: Allocator) !void {...@@ -901,7 +901,7 @@ pub fn parseDataInCode(self: *Object, gpa: Allocator) !void {
901 const dice = @ptrCast([*]align(1) const macho.data_in_code_entry, self.contents.ptr + cmd.dataoff)[0..ndice];901 const dice = @ptrCast([*]align(1) const macho.data_in_code_entry, self.contents.ptr + cmd.dataoff)[0..ndice];
902 try self.data_in_code.ensureTotalCapacityPrecise(gpa, dice.len);902 try self.data_in_code.ensureTotalCapacityPrecise(gpa, dice.len);
903 self.data_in_code.appendUnalignedSliceAssumeCapacity(dice);903 self.data_in_code.appendUnalignedSliceAssumeCapacity(dice);
904 std.sort.sort(macho.data_in_code_entry, self.data_in_code.items, {}, diceLessThan);904 mem.sort(macho.data_in_code_entry, self.data_in_code.items, {}, diceLessThan);
905}905}
906906
907fn diceLessThan(ctx: void, lhs: macho.data_in_code_entry, rhs: macho.data_in_code_entry) bool {907fn diceLessThan(ctx: void, lhs: macho.data_in_code_entry, rhs: macho.data_in_code_entry) bool {
src/link/MachO/UnwindInfo.zig+1-1
...@@ -411,7 +411,7 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {...@@ -411,7 +411,7 @@ pub fn collect(info: *UnwindInfo, zld: *Zld) !void {
411 }411 }
412412
413 var slice = common_encodings_counts.values();413 var slice = common_encodings_counts.values();
414 std.sort.sort(CommonEncWithCount, slice, {}, CommonEncWithCount.greaterThan);414 mem.sort(CommonEncWithCount, slice, {}, CommonEncWithCount.greaterThan);
415415
416 var i: u7 = 0;416 var i: u7 = 0;
417 while (i < slice.len) : (i += 1) {417 while (i < slice.len) : (i += 1) {
src/link/MachO/dyld_info/Rebase.zig+1-1
...@@ -39,7 +39,7 @@ pub fn finalize(rebase: *Rebase, gpa: Allocator) !void {...@@ -39,7 +39,7 @@ pub fn finalize(rebase: *Rebase, gpa: Allocator) !void {
3939
40 const writer = rebase.buffer.writer(gpa);40 const writer = rebase.buffer.writer(gpa);
4141
42 std.sort.sort(Entry, rebase.entries.items, {}, Entry.lessThan);42 std.mem.sort(Entry, rebase.entries.items, {}, Entry.lessThan);
4343
44 try setTypePointer(writer);44 try setTypePointer(writer);
4545
src/link/MachO/dyld_info/bind.zig+1-1
...@@ -47,7 +47,7 @@ pub fn Bind(comptime Ctx: type, comptime Target: type) type {...@@ -47,7 +47,7 @@ pub fn Bind(comptime Ctx: type, comptime Target: type) type {
4747
48 const writer = self.buffer.writer(gpa);48 const writer = self.buffer.writer(gpa);
4949
50 std.sort.sort(Entry, self.entries.items, ctx, Entry.lessThan);50 std.mem.sort(Entry, self.entries.items, ctx, Entry.lessThan);
5151
52 var start: usize = 0;52 var start: usize = 0;
53 var seg_id: ?u8 = null;53 var seg_id: ?u8 = null;
src/link/MachO/zld.zig+2-2
...@@ -1441,7 +1441,7 @@ pub const Zld = struct {...@@ -1441,7 +1441,7 @@ pub const Zld = struct {
1441 }1441 }
1442 }1442 }
14431443
1444 std.sort.sort(Section, sections.items, {}, SortSection.lessThan);1444 mem.sort(Section, sections.items, {}, SortSection.lessThan);
14451445
1446 self.sections.shrinkRetainingCapacity(0);1446 self.sections.shrinkRetainingCapacity(0);
1447 for (sections.items) |out| {1447 for (sections.items) |out| {
...@@ -2237,7 +2237,7 @@ pub const Zld = struct {...@@ -2237,7 +2237,7 @@ pub const Zld = struct {
2237 }2237 }
2238 }2238 }
22392239
2240 std.sort.sort(u64, addresses.items, {}, asc_u64);2240 mem.sort(u64, addresses.items, {}, asc_u64);
22412241
2242 var offsets = std.ArrayList(u32).init(gpa);2242 var offsets = std.ArrayList(u32).init(gpa);
2243 defer offsets.deinit();2243 defer offsets.deinit();
src/link/Wasm.zig+5-5
...@@ -2143,7 +2143,7 @@ fn sortDataSegments(wasm: *Wasm) !void {...@@ -2143,7 +2143,7 @@ fn sortDataSegments(wasm: *Wasm) !void {
2143 }2143 }
2144 };2144 };
21452145
2146 std.sort.sort([]const u8, keys, {}, SortContext.sort);2146 mem.sort([]const u8, keys, {}, SortContext.sort);
2147 for (keys) |key| {2147 for (keys) |key| {
2148 const segment_index = wasm.data_segments.get(key).?;2148 const segment_index = wasm.data_segments.get(key).?;
2149 new_mapping.putAssumeCapacity(key, segment_index);2149 new_mapping.putAssumeCapacity(key, segment_index);
...@@ -2187,7 +2187,7 @@ fn setupInitFunctions(wasm: *Wasm) !void {...@@ -2187,7 +2187,7 @@ fn setupInitFunctions(wasm: *Wasm) !void {
2187 }2187 }
21882188
2189 // sort the initfunctions based on their priority2189 // sort the initfunctions based on their priority
2190 std.sort.sort(InitFuncLoc, wasm.init_funcs.items, {}, InitFuncLoc.lessThan);2190 mem.sort(InitFuncLoc, wasm.init_funcs.items, {}, InitFuncLoc.lessThan);
2191}2191}
21922192
2193/// Generates an atom containing the global error set' size.2193/// Generates an atom containing the global error set' size.
...@@ -3687,7 +3687,7 @@ fn writeToFile(...@@ -3687,7 +3687,7 @@ fn writeToFile(
3687 }3687 }
3688 }.sort;3688 }.sort;
36893689
3690 std.sort.sort(*Atom, sorted_atoms.items, wasm, atom_sort_fn);3690 mem.sort(*Atom, sorted_atoms.items, wasm, atom_sort_fn);
36913691
3692 for (sorted_atoms.items) |sorted_atom| {3692 for (sorted_atoms.items) |sorted_atom| {
3693 try leb.writeULEB128(binary_writer, sorted_atom.size);3693 try leb.writeULEB128(binary_writer, sorted_atom.size);
...@@ -4050,8 +4050,8 @@ fn emitNameSection(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), arena: std.mem...@@ -4050,8 +4050,8 @@ fn emitNameSection(wasm: *Wasm, binary_bytes: *std.ArrayList(u8), arena: std.mem
4050 data_segment_index += 1;4050 data_segment_index += 1;
4051 }4051 }
40524052
4053 std.sort.sort(Name, funcs.values(), {}, Name.lessThan);4053 mem.sort(Name, funcs.values(), {}, Name.lessThan);
4054 std.sort.sort(Name, globals.items, {}, Name.lessThan);4054 mem.sort(Name, globals.items, {}, Name.lessThan);
40554055
4056 const header_offset = try reserveCustomSectionHeader(binary_bytes);4056 const header_offset = try reserveCustomSectionHeader(binary_bytes);
4057 const writer = binary_bytes.writer();4057 const writer = binary_bytes.writer();
src/objcopy.zig+2-2
...@@ -402,7 +402,7 @@ const BinaryElfOutput = struct {...@@ -402,7 +402,7 @@ const BinaryElfOutput = struct {
402 }402 }
403 }403 }
404404
405 std.sort.sort(*BinaryElfSegment, self.segments.items, {}, segmentSortCompare);405 mem.sort(*BinaryElfSegment, self.segments.items, {}, segmentSortCompare);
406406
407 for (self.segments.items, 0..) |firstSegment, i| {407 for (self.segments.items, 0..) |firstSegment, i| {
408 if (firstSegment.firstSection) |firstSection| {408 if (firstSegment.firstSection) |firstSection| {
...@@ -427,7 +427,7 @@ const BinaryElfOutput = struct {...@@ -427,7 +427,7 @@ const BinaryElfOutput = struct {
427 }427 }
428 }428 }
429429
430 std.sort.sort(*BinaryElfSection, self.sections.items, {}, sectionSortCompare);430 mem.sort(*BinaryElfSection, self.sections.items, {}, sectionSortCompare);
431431
432 return self;432 return self;
433 }433 }
test/src/Cases.zig+1-1
...@@ -607,7 +607,7 @@ fn sortTestFilenames(filenames: [][]const u8) void {...@@ -607,7 +607,7 @@ fn sortTestFilenames(filenames: [][]const u8) void {
607 };607 };
608 }608 }
609 };609 };
610 std.sort.sort([]const u8, filenames, Context{}, Context.lessThan);610 std.mem.sort([]const u8, filenames, Context{}, Context.lessThan);
611}611}
612612
613/// Iterates a set of filenames extracting batches that are either incremental613/// Iterates a set of filenames extracting batches that are either incremental
tools/gen_stubs.zig+1-1
...@@ -437,7 +437,7 @@ fn parseElf(parse: Parse, comptime is_64: bool, comptime endian: builtin.Endian)...@@ -437,7 +437,7 @@ fn parseElf(parse: Parse, comptime is_64: bool, comptime endian: builtin.Endian)
437 const dynstr = elf_bytes[dynstr_offset..];437 const dynstr = elf_bytes[dynstr_offset..];
438438
439 // Sort the list by address, ascending.439 // Sort the list by address, ascending.
440 std.sort.sort(Sym, @alignCast(8, dyn_syms), {}, S.symbolAddrLessThan);440 mem.sort(Sym, @alignCast(8, dyn_syms), {}, S.symbolAddrLessThan);
441441
442 for (dyn_syms) |sym| {442 for (dyn_syms) |sym| {
443 const this_section = s(sym.st_shndx);443 const this_section = s(sym.st_shndx);
tools/generate_JSONTestSuite.zig+1-1
...@@ -23,7 +23,7 @@ pub fn main() !void {...@@ -23,7 +23,7 @@ pub fn main() !void {
23 while (try it.next()) |entry| {23 while (try it.next()) |entry| {
24 try names.append(try allocator.dupe(u8, entry.name));24 try names.append(try allocator.dupe(u8, entry.name));
25 }25 }
26 std.sort.sort([]const u8, names.items, {}, (struct {26 std.mem.sort([]const u8, names.items, {}, (struct {
27 fn lessThan(_: void, a: []const u8, b: []const u8) bool {27 fn lessThan(_: void, a: []const u8, b: []const u8) bool {
28 return std.mem.lessThan(u8, a, b);28 return std.mem.lessThan(u8, a, b);
29 }29 }
tools/process_headers.zig+1-1
...@@ -460,7 +460,7 @@ pub fn main() !void {...@@ -460,7 +460,7 @@ pub fn main() !void {
460 try contents_list.append(contents);460 try contents_list.append(contents);
461 }461 }
462 }462 }
463 std.sort.sort(*Contents, contents_list.items, {}, Contents.hitCountLessThan);463 std.mem.sort(*Contents, contents_list.items, {}, Contents.hitCountLessThan);
464 const best_contents = contents_list.popOrNull().?;464 const best_contents = contents_list.popOrNull().?;
465 if (best_contents.hit_count > 1) {465 if (best_contents.hit_count > 1) {
466 // worth it to make it generic466 // worth it to make it generic
tools/update-linux-headers.zig+1-1
...@@ -260,7 +260,7 @@ pub fn main() !void {...@@ -260,7 +260,7 @@ pub fn main() !void {
260 try contents_list.append(contents);260 try contents_list.append(contents);
261 }261 }
262 }262 }
263 std.sort.sort(*Contents, contents_list.items, {}, Contents.hitCountLessThan);263 std.mem.sort(*Contents, contents_list.items, {}, Contents.hitCountLessThan);
264 const best_contents = contents_list.popOrNull().?;264 const best_contents = contents_list.popOrNull().?;
265 if (best_contents.hit_count > 1) {265 if (best_contents.hit_count > 1) {
266 // worth it to make it generic266 // worth it to make it generic
tools/update_clang_options.zig+1-1
...@@ -646,7 +646,7 @@ pub fn main() anyerror!void {...@@ -646,7 +646,7 @@ pub fn main() anyerror!void {
646 }646 }
647 // Some options have multiple matches. As an example, "-Wl,foo" matches both647 // Some options have multiple matches. As an example, "-Wl,foo" matches both
648 // "W" and "Wl,". So we sort this list in order of descending priority.648 // "W" and "Wl,". So we sort this list in order of descending priority.
649 std.sort.sort(*json.ObjectMap, all_objects.items, {}, objectLessThan);649 std.mem.sort(*json.ObjectMap, all_objects.items, {}, objectLessThan);
650650
651 var buffered_stdout = std.io.bufferedWriter(std.io.getStdOut().writer());651 var buffered_stdout = std.io.bufferedWriter(std.io.getStdOut().writer());
652 const stdout = buffered_stdout.writer();652 const stdout = buffered_stdout.writer();
tools/update_cpu_features.zig+4-4
...@@ -1187,8 +1187,8 @@ fn processOneTarget(job: Job) anyerror!void {...@@ -1187,8 +1187,8 @@ fn processOneTarget(job: Job) anyerror!void {
1187 for (llvm_target.extra_cpus) |extra_cpu| {1187 for (llvm_target.extra_cpus) |extra_cpu| {
1188 try all_cpus.append(extra_cpu);1188 try all_cpus.append(extra_cpu);
1189 }1189 }
1190 std.sort.sort(Feature, all_features.items, {}, featureLessThan);1190 mem.sort(Feature, all_features.items, {}, featureLessThan);
1191 std.sort.sort(Cpu, all_cpus.items, {}, cpuLessThan);1191 mem.sort(Cpu, all_cpus.items, {}, cpuLessThan);
11921192
1193 const target_sub_path = try fs.path.join(arena, &.{ "lib", "std", "target" });1193 const target_sub_path = try fs.path.join(arena, &.{ "lib", "std", "target" });
1194 var target_dir = try job.zig_src_dir.makeOpenPath(target_sub_path, .{});1194 var target_dir = try job.zig_src_dir.makeOpenPath(target_sub_path, .{});
...@@ -1283,7 +1283,7 @@ fn processOneTarget(job: Job) anyerror!void {...@@ -1283,7 +1283,7 @@ fn processOneTarget(job: Job) anyerror!void {
1283 try dependencies.append(key.*);1283 try dependencies.append(key.*);
1284 }1284 }
1285 }1285 }
1286 std.sort.sort([]const u8, dependencies.items, {}, asciiLessThan);1286 mem.sort([]const u8, dependencies.items, {}, asciiLessThan);
12871287
1288 if (dependencies.items.len == 0) {1288 if (dependencies.items.len == 0) {
1289 try w.writeAll(1289 try w.writeAll(
...@@ -1328,7 +1328,7 @@ fn processOneTarget(job: Job) anyerror!void {...@@ -1328,7 +1328,7 @@ fn processOneTarget(job: Job) anyerror!void {
1328 try cpu_features.append(key.*);1328 try cpu_features.append(key.*);
1329 }1329 }
1330 }1330 }
1331 std.sort.sort([]const u8, cpu_features.items, {}, asciiLessThan);1331 mem.sort([]const u8, cpu_features.items, {}, asciiLessThan);
1332 if (cpu.llvm_name) |llvm_name| {1332 if (cpu.llvm_name) |llvm_name| {
1333 try w.print(1333 try w.print(
1334 \\ pub const {} = CpuModel{{1334 \\ pub const {} = CpuModel{{
tools/update_spirv_features.zig+1-1
...@@ -303,7 +303,7 @@ fn gatherVersions(allocator: Allocator, registry: g.CoreRegistry) ![]const Versi...@@ -303,7 +303,7 @@ fn gatherVersions(allocator: Allocator, registry: g.CoreRegistry) ![]const Versi
303 }303 }
304 }304 }
305305
306 std.sort.sort(Version, versions.items, {}, Version.lessThan);306 std.mem.sort(Version, versions.items, {}, Version.lessThan);
307307
308 return versions.items;308 return versions.items;
309}309}