| author | |
| committer | |
| log | 3db3cf77904e664d589287602c14168a7a63f125 |
| tree | 62bec3710d6b806d54718475bf7a3673ba1a67a5 |
| parent | bfe02ff61a8861c269524c60668a3969cb053720 |
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 | 93 | return; |
| 94 | 94 | } |
| 95 | 95 | self.lfs = list; |
| 96 | sort.sort(LiteralNode, self.lfs, {}, byFreq); | |
| 96 | mem.sort(LiteralNode, self.lfs, {}, byFreq); | |
| 97 | 97 | |
| 98 | 98 | // Get the number of literals for each bit count |
| 99 | 99 | var bit_count = self.bitCounts(list, max_bits); |
| ... | ... | @@ -270,7 +270,7 @@ pub const HuffmanEncoder = struct { |
| 270 | 270 | var chunk = list[list.len - @intCast(u32, bits) ..]; |
| 271 | 271 | |
| 272 | 272 | self.lns = chunk; |
| 273 | sort.sort(LiteralNode, self.lns, {}, byLiteral); | |
| 273 | mem.sort(LiteralNode, self.lns, {}, byLiteral); | |
| 274 | 274 | |
| 275 | 275 | for (chunk) |node| { |
| 276 | 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 | 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 | 111 | for (0..probability) |i| { |
| 112 | 112 | entries[temp_states[i]] = if (i < double_state_count) Table.Fse{ |
| 113 | 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 | 124 | }; |
| 125 | 125 | } |
| 126 | 126 | |
| 127 | std.sort.sort( | |
| 127 | std.mem.sort( | |
| 128 | 128 | LiteralsSection.HuffmanTree.PrefixedSymbol, |
| 129 | 129 | weight_sorted_prefixed_symbols, |
| 130 | 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 | 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 | 32 | const min_len = sorted_kvs[0].key.len; |
| 33 | 33 | const max_len = sorted_kvs[sorted_kvs.len - 1].key.len; |
| 34 | 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 | 1211 | // Even though lld emits symbols in ascending order, this debug code |
| 1212 | 1212 | // should work for programs linked in any valid way. |
| 1213 | 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); | |
| 1215 | 1215 | |
| 1216 | 1216 | return ModuleDebugInfo{ |
| 1217 | 1217 | .base_address = undefined, |
lib/std/enums.zig+1-1| ... | ... | @@ -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 | 1318 | const min = fields[0].value; |
| 1319 | 1319 | const max = fields[fields.len - 1].value; |
| 1320 | 1320 | const fields_len = fields.len; |
lib/std/http/Headers.zig+1-1| ... | ... | @@ -191,7 +191,7 @@ pub const Headers = struct { |
| 191 | 191 | |
| 192 | 192 | /// Sorts the headers in lexicographical order. |
| 193 | 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 | 195 | headers.rebuildIndex(); |
| 196 | 196 | } |
| 197 | 197 |
lib/std/mem.zig+28| ... | ... | @@ -566,6 +566,34 @@ test "zeroInit" { |
| 566 | 566 | }, nested_baz); |
| 567 | 567 | } |
| 568 | 568 | |
| 569 | pub 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 | ||
| 578 | pub 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. | |
| 589 | pub fn sortContext(a: usize, b: usize, context: anytype) void { | |
| 590 | std.sort.insertionContext(a, b, context); | |
| 591 | } | |
| 592 | ||
| 593 | pub fn sortUnstableContext(a: usize, b: usize, context: anytype) void { | |
| 594 | std.sort.pdqContext(a, b, context); | |
| 595 | } | |
| 596 | ||
| 569 | 597 | /// Compares two slices of numbers lexicographically. O(n). |
| 570 | 598 | pub fn order(comptime T: type, lhs: []const T, rhs: []const T) math.Order { |
| 571 | 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 | 985 | for (decls, 0..) |decl, i| { |
| 986 | 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 | 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 | 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 | 164 | var sizes_bytes: [fields.len]usize = undefined; |
| 165 | 165 | var field_indexes: [fields.len]usize = undefined; |
| 166 | 166 | for (data, 0..) |elem, i| { |
| ... | ... | @@ -488,10 +488,7 @@ pub fn MultiArrayList(comptime T: type) type { |
| 488 | 488 | } |
| 489 | 489 | }; |
| 490 | 490 | |
| 491 | std.sort.sortContext(self.len, SortContext{ | |
| 492 | .sub_ctx = ctx, | |
| 493 | .slice = self.slice(), | |
| 494 | }); | |
| 491 | mem.sortContext(0, self.len, SortContext{ .sub_ctx = ctx, .slice = self.slice() }); | |
| 495 | 492 | } |
| 496 | 493 | |
| 497 | 494 | fn capacityInBytes(capacity: usize) usize { |
lib/std/net.zig+1-1| ... | ... | @@ -1082,7 +1082,7 @@ fn linuxLookupName( |
| 1082 | 1082 | key |= (MAXADDRS - @intCast(i32, i)) << DAS_ORDER_SHIFT; |
| 1083 | 1083 | addr.sortkey = key; |
| 1084 | 1084 | } |
| 1085 | std.sort.sort(LookupAddr, addrs.items, {}, addrCmpLessThan); | |
| 1085 | mem.sort(LookupAddr, addrs.items, {}, addrCmpLessThan); | |
| 1086 | 1086 | } |
| 1087 | 1087 | |
| 1088 | 1088 | const Policy = struct { |
lib/std/sort.zig+230-1241| ... | ... | @@ -4,1241 +4,152 @@ const testing = std.testing; |
| 4 | 4 | const mem = std.mem; |
| 5 | 5 | const math = std.math; |
| 6 | 6 | |
| 7 | pub fn binarySearch( | |
| 8 | comptime T: type, | |
| 9 | key: anytype, | |
| 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 | ||
| 31 | test "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 | } | |
| 7 | pub const block = @import("sort/block.zig").block; | |
| 8 | pub const pdq = @import("sort/pdq.zig").pdq; | |
| 9 | pub const pdqContext = @import("sort/pdq.zig").pdqContext; | |
| 109 | 10 | |
| 110 | 11 | /// Stable in-place sort. O(n) best case, O(pow(n, 2)) worst case. |
| 111 | 12 | /// O(1) memory (no allocator required). |
| 112 | 13 | /// Sorts in ascending order with respect to the given `lessThan` function. |
| 113 | /// This can be expressed in terms of `insertionSortContext` but the glue | |
| 114 | /// code is slightly longer than the direct implementation. | |
| 115 | pub fn insertionSort( | |
| 14 | pub fn insertion( | |
| 116 | 15 | comptime T: type, |
| 117 | 16 | items: []T, |
| 118 | 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 | 19 | ) void { |
| 121 | var i: usize = 1; | |
| 122 | while (i < items.len) : (i += 1) { | |
| 123 | const x = items[i]; | |
| 124 | var j: usize = i; | |
| 125 | while (j > 0 and lessThan(context, x, items[j - 1])) : (j -= 1) { | |
| 126 | items[j] = items[j - 1]; | |
| 20 | const Context = struct { | |
| 21 | items: []T, | |
| 22 | sub_ctx: @TypeOf(context), | |
| 23 | ||
| 24 | pub fn lessThan(ctx: @This(), a: usize, b: usize) bool { | |
| 25 | return lessThanFn(ctx.sub_ctx, ctx.items[a], ctx.items[b]); | |
| 127 | 26 | } |
| 128 | items[j] = x; | |
| 129 | } | |
| 27 | ||
| 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 | } |
| 131 | 34 | |
| 132 | 35 | /// Stable in-place sort. O(n) best case, O(pow(n, 2)) worst case. |
| 133 | 36 | /// O(1) memory (no allocator required). |
| 134 | /// Sorts in ascending order with respect to the given `context.lessThan` function. | |
| 135 | pub fn insertionSortContext(len: usize, context: anytype) void { | |
| 136 | var i: usize = 1; | |
| 137 | while (i < len) : (i += 1) { | |
| 138 | var j: usize = i; | |
| 139 | while (j > 0 and context.lessThan(j, j - 1)) : (j -= 1) { | |
| 37 | /// Sorts in ascending order with respect to the given `lessThan` function. | |
| 38 | pub fn insertionContext(a: usize, b: usize, context: anytype) void { | |
| 39 | var i = a + 1; | |
| 40 | while (i < b) : (i += 1) { | |
| 41 | var j = i; | |
| 42 | while (j > a and context.lessThan(j, j - 1)) : (j -= 1) { | |
| 140 | 43 | context.swap(j, j - 1); |
| 141 | 44 | } |
| 142 | 45 | } |
| 143 | 46 | } |
| 144 | 47 | |
| 145 | const Range = struct { | |
| 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 | ||
| 161 | const 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 | ||
| 225 | const 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. | |
| 48 | /// Unstable in-place sort. O(n*log(n)) best case, worst case and average case. | |
| 233 | 49 | /// O(1) memory (no allocator required). |
| 234 | 50 | /// Sorts in ascending order with respect to the given `lessThan` function. |
| 235 | /// Currently implemented as block sort. | |
| 236 | pub fn sort( | |
| 51 | pub fn heap( | |
| 237 | 52 | comptime T: type, |
| 238 | 53 | items: []T, |
| 239 | 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 | 56 | ) void { |
| 57 | const Context = struct { | |
| 58 | items: []T, | |
| 59 | sub_ctx: @TypeOf(context), | |
| 242 | 60 | |
| 243 | // Implementation ported from https://github.com/BonzaiThePenguin/WikiSort/blob/master/WikiSort.c | |
| 244 | var cache: [512]T = undefined; | |
| 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]); | |
| 61 | pub fn lessThan(ctx: @This(), a: usize, b: usize) bool { | |
| 62 | return lessThanFn(ctx.sub_ctx, ctx.items[a], ctx.items[b]); | |
| 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; | |
| 587 | 64 | |
| 588 | if (count >= buffer_size) { | |
| 589 | // keep track of the range within the items where we'll need to "pull out" these values to create the internal buffe | |
| 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 | } | |
| 65 | pub fn swap(ctx: @This(), a: usize, b: usize) void { | |
| 66 | return mem.swap(T, &ctx.items[a], &ctx.items[b]); | |
| 879 | 67 | } |
| 880 | ||
| 881 | // double the size of each A and B subarray that will be merged in the next level | |
| 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. | |
| 888 | pub fn sortContext(len: usize, context: anytype) void { | |
| 889 | return insertionSortContext(len, context); | |
| 890 | } | |
| 891 | ||
| 892 | // merge operation without a buffer | |
| 893 | fn 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 | |
| 942 | fn 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 | ||
| 977 | fn 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 | |
| 986 | fn 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 | ||
| 1008 | fn 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 | ||
| 1030 | fn 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 | ||
| 1052 | fn 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); | |
| 68 | }; | |
| 69 | heapContext(0, items.len, Context{ .items = items, .sub_ctx = context }); | |
| 1072 | 70 | } |
| 1073 | 71 | |
| 1074 | fn binaryFirst( | |
| 1075 | comptime T: type, | |
| 1076 | items: []T, | |
| 1077 | value: T, | |
| 1078 | range: Range, | |
| 1079 | context: anytype, | |
| 1080 | comptime lessThan: fn (@TypeOf(context), T, T) bool, | |
| 1081 | ) usize { | |
| 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 | } | |
| 72 | /// Unstable in-place sort. O(n*log(n)) best case, worst case and average case. | |
| 73 | /// O(1) memory (no allocator required). | |
| 74 | /// Sorts in ascending order with respect to the given `lessThan` function. | |
| 75 | pub fn heapContext(a: usize, b: usize, context: anytype) void { | |
| 76 | // build the heap in linear time. | |
| 77 | var i = b / 2; | |
| 78 | while (i > a) : (i -= 1) { | |
| 79 | siftDown(i - 1, b, context); | |
| 1093 | 80 | } |
| 1094 | return curr; | |
| 1095 | } | |
| 1096 | ||
| 1097 | fn 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; | |
| 1110 | 81 | |
| 1111 | size /= 2; | |
| 1112 | const mid_item = items[curr + size]; | |
| 1113 | if (!lessThan(context, value, mid_item)) { | |
| 1114 | curr += size + offset; | |
| 1115 | } | |
| 82 | // pop maximal elements from the heap. | |
| 83 | i = b; | |
| 84 | while (i > a) : (i -= 1) { | |
| 85 | context.swap(a, i - 1); | |
| 86 | siftDown(a, i - 1, context); | |
| 1116 | 87 | } |
| 1117 | return curr; | |
| 1118 | 88 | } |
| 1119 | 89 | |
| 1120 | fn mergeInto( | |
| 1121 | comptime T: type, | |
| 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 | ||
| 90 | fn siftDown(root: usize, n: usize, context: anytype) void { | |
| 91 | var node = root; | |
| 1135 | 92 | while (true) { |
| 1136 | if (!lessThan(context, from[B_index], from[A_index])) { | |
| 1137 | into[insert_index] = from[A_index]; | |
| 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 | ||
| 1160 | fn 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; | |
| 93 | var child = 2 * node + 1; | |
| 94 | if (child >= n) break; | |
| 1175 | 95 | |
| 1176 | if (B.length() > 0 and A.length() > 0) { | |
| 1177 | while (true) { | |
| 1178 | if (!lessThan(context, items[B_index], cache[A_index])) { | |
| 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 | } | |
| 96 | // choose the greater child. | |
| 97 | if (child + 1 < n and context.lessThan(child, child + 1)) { | |
| 98 | child += 1; | |
| 1189 | 99 | } |
| 1190 | } | |
| 1191 | 100 | |
| 1192 | // copy the remainder of A into the final array | |
| 1193 | const cache_a = cache[A_index..A_last]; | |
| 1194 | @memcpy(items[insert_index..][0..cache_a.len], cache_a); | |
| 1195 | } | |
| 101 | // stop if the invariant holds at `node`. | |
| 102 | if (!context.lessThan(node, child)) break; | |
| 1196 | 103 | |
| 1197 | fn swap( | |
| 1198 | comptime T: type, | |
| 1199 | items: []T, | |
| 1200 | context: anytype, | |
| 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]); | |
| 104 | // swap `node` with the greater child, | |
| 105 | // move one step down, and continue sifting. | |
| 106 | context.swap(node, child); | |
| 107 | node = child; | |
| 1209 | 108 | } |
| 1210 | 109 | } |
| 1211 | 110 | |
| 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))`. | |
| 1213 | 112 | pub fn asc(comptime T: type) fn (void, T, T) bool { |
| 1214 | const impl = struct { | |
| 1215 | fn inner(context: void, a: T, b: T) bool { | |
| 1216 | _ = context; | |
| 113 | return struct { | |
| 114 | pub fn inner(_: void, a: T, b: T) bool { | |
| 1217 | 115 | return a < b; |
| 1218 | 116 | } |
| 1219 | }; | |
| 1220 | ||
| 1221 | return impl.inner; | |
| 117 | }.inner; | |
| 1222 | 118 | } |
| 1223 | 119 | |
| 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))`. | |
| 1225 | 121 | pub fn desc(comptime T: type) fn (void, T, T) bool { |
| 1226 | const impl = struct { | |
| 1227 | fn inner(context: void, a: T, b: T) bool { | |
| 1228 | _ = context; | |
| 122 | return struct { | |
| 123 | pub fn inner(_: void, a: T, b: T) bool { | |
| 1229 | 124 | return a > b; |
| 1230 | 125 | } |
| 1231 | }; | |
| 1232 | ||
| 1233 | return impl.inner; | |
| 126 | }.inner; | |
| 1234 | 127 | } |
| 1235 | 128 | |
| 129 | const asc_u8 = asc(u8); | |
| 130 | const asc_i32 = asc(i32); | |
| 131 | const desc_u8 = desc(u8); | |
| 132 | const desc_i32 = desc(i32); | |
| 133 | ||
| 134 | const sort_funcs = &[_]fn (comptime type, anytype, anytype, comptime anytype) void{ | |
| 135 | block, | |
| 136 | pdq, | |
| 137 | insertion, | |
| 138 | heap, | |
| 139 | }; | |
| 140 | ||
| 141 | const 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 | ||
| 1236 | 151 | test "stable sort" { |
| 1237 | try testStableSort(); | |
| 1238 | comptime try testStableSort(); | |
| 1239 | } | |
| 1240 | fn testStableSort() !void { | |
| 1241 | var expected = [_]IdAndValue{ | |
| 152 | const expected = [_]IdAndValue{ | |
| 1242 | 153 | IdAndValue{ .id = 0, .value = 0 }, |
| 1243 | 154 | IdAndValue{ .id = 1, .value = 0 }, |
| 1244 | 155 | IdAndValue{ .id = 2, .value = 0 }, |
| ... | ... | @@ -1249,6 +160,7 @@ fn testStableSort() !void { |
| 1249 | 160 | IdAndValue{ .id = 1, .value = 2 }, |
| 1250 | 161 | IdAndValue{ .id = 2, .value = 2 }, |
| 1251 | 162 | }; |
| 163 | ||
| 1252 | 164 | var cases = [_][9]IdAndValue{ |
| 1253 | 165 | [_]IdAndValue{ |
| 1254 | 166 | IdAndValue{ .id = 0, .value = 0 }, |
| ... | ... | @@ -1273,26 +185,15 @@ fn testStableSort() !void { |
| 1273 | 185 | IdAndValue{ .id = 2, .value = 0 }, |
| 1274 | 186 | }, |
| 1275 | 187 | }; |
| 188 | ||
| 1276 | 189 | for (&cases) |*case| { |
| 1277 | insertionSort(IdAndValue, (case.*)[0..], {}, cmpByValue); | |
| 190 | block(IdAndValue, (case.*)[0..], {}, IdAndValue.lessThan); | |
| 1278 | 191 | for (case.*, 0..) |item, i| { |
| 1279 | 192 | try testing.expect(item.id == expected[i].id); |
| 1280 | 193 | try testing.expect(item.value == expected[i].value); |
| 1281 | 194 | } |
| 1282 | 195 | } |
| 1283 | 196 | } |
| 1284 | const IdAndValue = struct { | |
| 1285 | id: usize, | |
| 1286 | value: i32, | |
| 1287 | }; | |
| 1288 | fn cmpByValue(context: void, a: IdAndValue, b: IdAndValue) bool { | |
| 1289 | return asc_i32(context, a.value, b.value); | |
| 1290 | } | |
| 1291 | ||
| 1292 | const asc_u8 = asc(u8); | |
| 1293 | const asc_i32 = asc(i32); | |
| 1294 | const desc_u8 = desc(u8); | |
| 1295 | const desc_i32 = desc(i32); | |
| 1296 | 197 | |
| 1297 | 198 | test "sort" { |
| 1298 | 199 | const u8cases = [_][]const []const u8{ |
| ... | ... | @@ -1322,14 +223,6 @@ test "sort" { |
| 1322 | 223 | }, |
| 1323 | 224 | }; |
| 1324 | 225 | |
| 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 | 226 | const i32cases = [_][]const []const i32{ |
| 1334 | 227 | &[_][]const i32{ |
| 1335 | 228 | &[_]i32{}, |
| ... | ... | @@ -1357,12 +250,22 @@ test "sort" { |
| 1357 | 250 | }, |
| 1358 | 251 | }; |
| 1359 | 252 | |
| 1360 | for (i32cases) |case| { | |
| 1361 | var buf: [8]i32 = undefined; | |
| 1362 | const slice = buf[0..case[0].len]; | |
| 1363 | @memcpy(slice, case[0]); | |
| 1364 | sort(i32, slice, {}, asc_i32); | |
| 1365 | try testing.expect(mem.eql(i32, slice, case[1])); | |
| 253 | inline for (sort_funcs) |sortFn| { | |
| 254 | for (u8cases) |case| { | |
| 255 | var buf: [8]u8 = undefined; | |
| 256 | const slice = buf[0..case[0].len]; | |
| 257 | @memcpy(slice, case[0]); | |
| 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 | } |
| 1368 | 271 | |
| ... | ... | @@ -1394,53 +297,139 @@ test "sort descending" { |
| 1394 | 297 | }, |
| 1395 | 298 | }; |
| 1396 | 299 | |
| 1397 | for (rev_cases) |case| { | |
| 1398 | var buf: [8]i32 = undefined; | |
| 1399 | const slice = buf[0..case[0].len]; | |
| 1400 | @memcpy(slice, case[0]); | |
| 1401 | sort(i32, slice, {}, desc_i32); | |
| 1402 | try testing.expect(mem.eql(i32, slice, case[1])); | |
| 300 | inline for (sort_funcs) |sortFn| { | |
| 301 | for (rev_cases) |case| { | |
| 302 | var buf: [8]i32 = undefined; | |
| 303 | const slice = buf[0..case[0].len]; | |
| 304 | @memcpy(slice, case[0]); | |
| 305 | sortFn(i32, slice, {}, desc_i32); | |
| 306 | try testing.expect(mem.eql(i32, slice, case[1])); | |
| 307 | } | |
| 1403 | 308 | } |
| 1404 | 309 | } |
| 1405 | 310 | |
| 1406 | test "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 | ||
| 1413 | 311 | test "sort fuzz testing" { |
| 1414 | 312 | var prng = std.rand.DefaultPrng.init(0x12345678); |
| 1415 | 313 | const random = prng.random(); |
| 1416 | 314 | const test_case_count = 10; |
| 1417 | var i: usize = 0; | |
| 1418 | while (i < test_case_count) : (i += 1) { | |
| 1419 | try fuzzTest(random); | |
| 315 | ||
| 316 | inline for (sort_funcs) |sortFn| { | |
| 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 | } |
| 1422 | 331 | |
| 1423 | var fixed_buffer_mem: [100 * 1024]u8 = undefined; | |
| 332 | pub 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; | |
| 1424 | 341 | |
| 1425 | fn fuzzTest(rng: std.rand.Random) !void { | |
| 1426 | const array_size = rng.intRangeLessThan(usize, 0, 1000); | |
| 1427 | var array = try testing.allocator.alloc(IdAndValue, array_size); | |
| 1428 | defer testing.allocator.free(array); | |
| 1429 | // populate with random data | |
| 1430 | for (array, 0..) |*item, index| { | |
| 1431 | item.id = index; | |
| 1432 | item.value = rng.intRangeLessThan(i32, 0, 100); | |
| 342 | while (left < right) { | |
| 343 | // Avoid overflowing in the midpoint calculation | |
| 344 | const mid = left + (right - left) / 2; | |
| 345 | // Compare the key with the midpoint element | |
| 346 | switch (compareFn(context, key, items[mid])) { | |
| 347 | .eq => return mid, | |
| 348 | .gt => left = mid + 1, | |
| 349 | .lt => right = mid, | |
| 350 | } | |
| 1433 | 351 | } |
| 1434 | sort(IdAndValue, array, {}, cmpByValue); | |
| 1435 | 352 | |
| 1436 | var index: usize = 1; | |
| 1437 | while (index < array.len) : (index += 1) { | |
| 1438 | if (array[index].value == array[index - 1].value) { | |
| 1439 | try testing.expect(array[index].id > array[index - 1].id); | |
| 1440 | } else { | |
| 1441 | try testing.expect(array[index].value > array[index - 1].value); | |
| 353 | return null; | |
| 354 | } | |
| 355 | ||
| 356 | test "binarySearch" { | |
| 357 | const S = struct { | |
| 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 | } |
| 1445 | 434 | |
| 1446 | 435 | pub fn argMin( |
lib/std/sort/block.zig created+1066| ... | ... | @@ -0,0 +1,1066 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const sort = std.sort; | |
| 3 | const math = std.math; | |
| 4 | const mem = std.mem; | |
| 5 | ||
| 6 | const 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 | ||
| 22 | const 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 | ||
| 86 | const 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) | |
| 99 | pub 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 | |
| 749 | fn 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 | |
| 798 | fn 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 | ||
| 833 | fn 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 | |
| 842 | fn 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 | ||
| 864 | fn 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 | ||
| 886 | fn 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 | ||
| 908 | fn 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 | ||
| 930 | fn 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 | ||
| 953 | fn 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 | ||
| 976 | fn 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 | ||
| 1016 | fn 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 | ||
| 1053 | fn 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 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const sort = std.sort; | |
| 3 | const mem = std.mem; | |
| 4 | const math = std.math; | |
| 5 | const 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. | |
| 11 | pub 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 | ||
| 32 | const 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. | |
| 42 | pub 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. | |
| 138 | fn 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]`. | |
| 181 | fn 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. | |
| 204 | fn 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 | ||
| 248 | fn 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. | |
| 272 | fn 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 | ||
| 304 | fn 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 | ||
| 323 | fn 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 | 672 | } |
| 673 | 673 | } |
| 674 | 674 | // Sort the slice by package name |
| 675 | std.sort.sort(Package.Table.KV, packages, {}, struct { | |
| 675 | mem.sort(Package.Table.KV, packages, {}, struct { | |
| 676 | 676 | fn lessThan(_: void, lhs: Package.Table.KV, rhs: Package.Table.KV) bool { |
| 677 | 677 | return std.mem.lessThan(u8, lhs.key, rhs.key); |
| 678 | 678 | } |
src/Package.zig+1-1| ... | ... | @@ -672,7 +672,7 @@ fn computePackageHash( |
| 672 | 672 | } |
| 673 | 673 | } |
| 674 | 674 | |
| 675 | std.sort.sort(*HashedFile, all_files.items, {}, HashedFile.lessThan); | |
| 675 | mem.sort(*HashedFile, all_files.items, {}, HashedFile.lessThan); | |
| 676 | 676 | |
| 677 | 677 | var hasher = Manifest.Hash.init(.{}); |
| 678 | 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 | 60 | if (self.ranges.items.len == 0) |
| 61 | 61 | return false; |
| 62 | 62 | |
| 63 | std.sort.sort(Range, self.ranges.items, LessThanContext{ | |
| 63 | std.mem.sort(Range, self.ranges.items, LessThanContext{ | |
| 64 | 64 | .ty = ty, |
| 65 | 65 | .module = self.module, |
| 66 | 66 | }, lessThan); |
src/Sema.zig+1-1| ... | ... | @@ -30979,7 +30979,7 @@ fn resolveStructLayout(sema: *Sema, ty: Type) CompileError!void { |
| 30979 | 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 | 30983 | .struct_obj = struct_obj, |
| 30984 | 30984 | .sema = sema, |
| 30985 | 30985 | }, AlignSortContext.lessThan); |
src/arch/x86_64/CodeGen.zig+1-1| ... | ... | @@ -2176,7 +2176,7 @@ fn computeFrameLayout(self: *Self) !FrameLayout { |
| 2176 | 2176 | } |
| 2177 | 2177 | }; |
| 2178 | 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 | } |
| 2181 | 2181 | |
| 2182 | 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 | 770 | @setEvalBranchQuota(30_000); |
| 771 | 771 | const encodings = @import("encodings.zig"); |
| 772 | 772 | var entries = encodings.table; |
| 773 | std.sort.sort(encodings.Entry, &entries, {}, struct { | |
| 773 | std.mem.sort(encodings.Entry, &entries, {}, struct { | |
| 774 | 774 | fn lessThan(_: void, lhs: encodings.Entry, rhs: encodings.Entry) bool { |
| 775 | 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 | 1292 | fn sortFields(self: *@This(), fields_len: usize) []Payload.Fields.Field { |
| 1293 | 1293 | const Field = Payload.Fields.Field; |
| 1294 | 1294 | const slice = self.storage.anon.fields[0..fields_len]; |
| 1295 | std.sort.sort(Field, slice, {}, struct { | |
| 1295 | mem.sort(Field, slice, {}, struct { | |
| 1296 | 1296 | fn before(_: void, lhs: Field, rhs: Field) bool { |
| 1297 | 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 | 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)); | |
| 1841 | 1841 | |
| 1842 | 1842 | var buffer = std.ArrayList(u8).init(gpa); |
| 1843 | 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 | 209 | // afterwards by address in each group. Normally, dysymtab should |
| 210 | 210 | // be enough to guarantee the sort, but turns out not every compiler |
| 211 | 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); | |
| 213 | 213 | |
| 214 | 214 | var prev_sect_id: u8 = 0; |
| 215 | 215 | var section_index_lookup: ?Entry = null; |
| ... | ... | @@ -462,7 +462,7 @@ pub fn splitRegularSections(self: *Object, zld: *Zld, object_id: u32) !void { |
| 462 | 462 | sorted_sections[id] = .{ .header = sect, .id = @intCast(u8, id) }; |
| 463 | 463 | } |
| 464 | 464 | |
| 465 | std.sort.sort(SortedSection, sorted_sections, {}, sectionLessThanByAddress); | |
| 465 | mem.sort(SortedSection, sorted_sections, {}, sectionLessThanByAddress); | |
| 466 | 466 | |
| 467 | 467 | var sect_sym_index: u32 = 0; |
| 468 | 468 | for (sorted_sections) |section| { |
| ... | ... | @@ -663,7 +663,7 @@ fn parseRelocs(self: *Object, gpa: Allocator, sect_id: u8) !void { |
| 663 | 663 | if (self.getSourceRelocs(section)) |relocs| { |
| 664 | 664 | try self.relocations.ensureUnusedCapacity(gpa, relocs.len); |
| 665 | 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 | 668 | self.section_relocs_lookup.items[sect_id] = start; |
| 669 | 669 | } |
| ... | ... | @@ -901,7 +901,7 @@ pub fn parseDataInCode(self: *Object, gpa: Allocator) !void { |
| 901 | 901 | const dice = @ptrCast([*]align(1) const macho.data_in_code_entry, self.contents.ptr + cmd.dataoff)[0..ndice]; |
| 902 | 902 | try self.data_in_code.ensureTotalCapacityPrecise(gpa, dice.len); |
| 903 | 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 | } |
| 906 | 906 | |
| 907 | 907 | fn 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 | 411 | } |
| 412 | 412 | |
| 413 | 413 | var slice = common_encodings_counts.values(); |
| 414 | std.sort.sort(CommonEncWithCount, slice, {}, CommonEncWithCount.greaterThan); | |
| 414 | mem.sort(CommonEncWithCount, slice, {}, CommonEncWithCount.greaterThan); | |
| 415 | 415 | |
| 416 | 416 | var i: u7 = 0; |
| 417 | 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 | 39 | |
| 40 | 40 | const writer = rebase.buffer.writer(gpa); |
| 41 | 41 | |
| 42 | std.sort.sort(Entry, rebase.entries.items, {}, Entry.lessThan); | |
| 42 | std.mem.sort(Entry, rebase.entries.items, {}, Entry.lessThan); | |
| 43 | 43 | |
| 44 | 44 | try setTypePointer(writer); |
| 45 | 45 |
src/link/MachO/dyld_info/bind.zig+1-1| ... | ... | @@ -47,7 +47,7 @@ pub fn Bind(comptime Ctx: type, comptime Target: type) type { |
| 47 | 47 | |
| 48 | 48 | const writer = self.buffer.writer(gpa); |
| 49 | 49 | |
| 50 | std.sort.sort(Entry, self.entries.items, ctx, Entry.lessThan); | |
| 50 | std.mem.sort(Entry, self.entries.items, ctx, Entry.lessThan); | |
| 51 | 51 | |
| 52 | 52 | var start: usize = 0; |
| 53 | 53 | var seg_id: ?u8 = null; |
src/link/MachO/zld.zig+2-2| ... | ... | @@ -1441,7 +1441,7 @@ pub const Zld = struct { |
| 1441 | 1441 | } |
| 1442 | 1442 | } |
| 1443 | 1443 | |
| 1444 | std.sort.sort(Section, sections.items, {}, SortSection.lessThan); | |
| 1444 | mem.sort(Section, sections.items, {}, SortSection.lessThan); | |
| 1445 | 1445 | |
| 1446 | 1446 | self.sections.shrinkRetainingCapacity(0); |
| 1447 | 1447 | for (sections.items) |out| { |
| ... | ... | @@ -2237,7 +2237,7 @@ pub const Zld = struct { |
| 2237 | 2237 | } |
| 2238 | 2238 | } |
| 2239 | 2239 | |
| 2240 | std.sort.sort(u64, addresses.items, {}, asc_u64); | |
| 2240 | mem.sort(u64, addresses.items, {}, asc_u64); | |
| 2241 | 2241 | |
| 2242 | 2242 | var offsets = std.ArrayList(u32).init(gpa); |
| 2243 | 2243 | defer offsets.deinit(); |
src/link/Wasm.zig+5-5| ... | ... | @@ -2143,7 +2143,7 @@ fn sortDataSegments(wasm: *Wasm) !void { |
| 2143 | 2143 | } |
| 2144 | 2144 | }; |
| 2145 | 2145 | |
| 2146 | std.sort.sort([]const u8, keys, {}, SortContext.sort); | |
| 2146 | mem.sort([]const u8, keys, {}, SortContext.sort); | |
| 2147 | 2147 | for (keys) |key| { |
| 2148 | 2148 | const segment_index = wasm.data_segments.get(key).?; |
| 2149 | 2149 | new_mapping.putAssumeCapacity(key, segment_index); |
| ... | ... | @@ -2187,7 +2187,7 @@ fn setupInitFunctions(wasm: *Wasm) !void { |
| 2187 | 2187 | } |
| 2188 | 2188 | |
| 2189 | 2189 | // 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 | } |
| 2192 | 2192 | |
| 2193 | 2193 | /// Generates an atom containing the global error set' size. |
| ... | ... | @@ -3687,7 +3687,7 @@ fn writeToFile( |
| 3687 | 3687 | } |
| 3688 | 3688 | }.sort; |
| 3689 | 3689 | |
| 3690 | std.sort.sort(*Atom, sorted_atoms.items, wasm, atom_sort_fn); | |
| 3690 | mem.sort(*Atom, sorted_atoms.items, wasm, atom_sort_fn); | |
| 3691 | 3691 | |
| 3692 | 3692 | for (sorted_atoms.items) |sorted_atom| { |
| 3693 | 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 | 4050 | data_segment_index += 1; |
| 4051 | 4051 | } |
| 4052 | 4052 | |
| 4053 | std.sort.sort(Name, funcs.values(), {}, Name.lessThan); | |
| 4054 | std.sort.sort(Name, globals.items, {}, Name.lessThan); | |
| 4053 | mem.sort(Name, funcs.values(), {}, Name.lessThan); | |
| 4054 | mem.sort(Name, globals.items, {}, Name.lessThan); | |
| 4055 | 4055 | |
| 4056 | 4056 | const header_offset = try reserveCustomSectionHeader(binary_bytes); |
| 4057 | 4057 | const writer = binary_bytes.writer(); |
src/objcopy.zig+2-2| ... | ... | @@ -402,7 +402,7 @@ const BinaryElfOutput = struct { |
| 402 | 402 | } |
| 403 | 403 | } |
| 404 | 404 | |
| 405 | std.sort.sort(*BinaryElfSegment, self.segments.items, {}, segmentSortCompare); | |
| 405 | mem.sort(*BinaryElfSegment, self.segments.items, {}, segmentSortCompare); | |
| 406 | 406 | |
| 407 | 407 | for (self.segments.items, 0..) |firstSegment, i| { |
| 408 | 408 | if (firstSegment.firstSection) |firstSection| { |
| ... | ... | @@ -427,7 +427,7 @@ const BinaryElfOutput = struct { |
| 427 | 427 | } |
| 428 | 428 | } |
| 429 | 429 | |
| 430 | std.sort.sort(*BinaryElfSection, self.sections.items, {}, sectionSortCompare); | |
| 430 | mem.sort(*BinaryElfSection, self.sections.items, {}, sectionSortCompare); | |
| 431 | 431 | |
| 432 | 432 | return self; |
| 433 | 433 | } |
test/src/Cases.zig+1-1| ... | ... | @@ -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 | } |
| 612 | 612 | |
| 613 | 613 | /// 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 | 437 | const dynstr = elf_bytes[dynstr_offset..]; |
| 438 | 438 | |
| 439 | 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); | |
| 441 | 441 | |
| 442 | 442 | for (dyn_syms) |sym| { |
| 443 | 443 | const this_section = s(sym.st_shndx); |
tools/generate_JSONTestSuite.zig+1-1| ... | ... | @@ -23,7 +23,7 @@ pub fn main() !void { |
| 23 | 23 | while (try it.next()) |entry| { |
| 24 | 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 | 27 | fn lessThan(_: void, a: []const u8, b: []const u8) bool { |
| 28 | 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 | 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 | 464 | const best_contents = contents_list.popOrNull().?; |
| 465 | 465 | if (best_contents.hit_count > 1) { |
| 466 | 466 | // worth it to make it generic |
tools/update-linux-headers.zig+1-1| ... | ... | @@ -260,7 +260,7 @@ pub fn main() !void { |
| 260 | 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 | 264 | const best_contents = contents_list.popOrNull().?; |
| 265 | 265 | if (best_contents.hit_count > 1) { |
| 266 | 266 | // worth it to make it generic |
tools/update_clang_options.zig+1-1| ... | ... | @@ -646,7 +646,7 @@ pub fn main() anyerror!void { |
| 646 | 646 | } |
| 647 | 647 | // Some options have multiple matches. As an example, "-Wl,foo" matches both |
| 648 | 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); | |
| 650 | 650 | |
| 651 | 651 | var buffered_stdout = std.io.bufferedWriter(std.io.getStdOut().writer()); |
| 652 | 652 | const stdout = buffered_stdout.writer(); |
tools/update_cpu_features.zig+4-4| ... | ... | @@ -1187,8 +1187,8 @@ fn processOneTarget(job: Job) anyerror!void { |
| 1187 | 1187 | for (llvm_target.extra_cpus) |extra_cpu| { |
| 1188 | 1188 | try all_cpus.append(extra_cpu); |
| 1189 | 1189 | } |
| 1190 | std.sort.sort(Feature, all_features.items, {}, featureLessThan); | |
| 1191 | std.sort.sort(Cpu, all_cpus.items, {}, cpuLessThan); | |
| 1190 | mem.sort(Feature, all_features.items, {}, featureLessThan); | |
| 1191 | mem.sort(Cpu, all_cpus.items, {}, cpuLessThan); | |
| 1192 | 1192 | |
| 1193 | 1193 | const target_sub_path = try fs.path.join(arena, &.{ "lib", "std", "target" }); |
| 1194 | 1194 | var target_dir = try job.zig_src_dir.makeOpenPath(target_sub_path, .{}); |
| ... | ... | @@ -1283,7 +1283,7 @@ fn processOneTarget(job: Job) anyerror!void { |
| 1283 | 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); | |
| 1287 | 1287 | |
| 1288 | 1288 | if (dependencies.items.len == 0) { |
| 1289 | 1289 | try w.writeAll( |
| ... | ... | @@ -1328,7 +1328,7 @@ fn processOneTarget(job: Job) anyerror!void { |
| 1328 | 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 | 1332 | if (cpu.llvm_name) |llvm_name| { |
| 1333 | 1333 | try w.print( |
| 1334 | 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 | 303 | } |
| 304 | 304 | } |
| 305 | 305 | |
| 306 | std.sort.sort(Version, versions.items, {}, Version.lessThan); | |
| 306 | std.mem.sort(Version, versions.items, {}, Version.lessThan); | |
| 307 | 307 | |
| 308 | 308 | return versions.items; |
| 309 | 309 | } |