Skip to content

Commit

Permalink
std.sort: add pdqsort and heapsort
Browse files Browse the repository at this point in the history
pdqsort (Pattern-defeating quicksort):
    A novel sorting algorithm that combines the fast average case of randomized quicksort
    with the fast worst case of heapsort, while achieving linear time on inputs with certain patterns.
    This implementation is based on https://github.com/zhangyunhao116/pdqsort
    which later replaced Go's original sort algorithm

also:
- compareFn now returns `std.math.Order`
- moved *blocksort* (default stable algorithm) and *pdqsort* (default unstable algorithm) to lib/std/sort/
- made tests run for each algorithm

Co-authored-by: VÖRÖSKŐI András <voroskoi@gmail.com>
  • Loading branch information
alichraghi and voroskoi committed May 17, 2023
1 parent fd213ac commit 87c8eaf
Show file tree
Hide file tree
Showing 38 changed files with 1,959 additions and 1,336 deletions.
4 changes: 2 additions & 2 deletions lib/std/array_hash_map.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2291,8 +2291,8 @@ test "sort" {
const C = struct {
keys: []i32,

pub fn lessThan(ctx: @This(), a_index: usize, b_index: usize) bool {
return ctx.keys[a_index] < ctx.keys[b_index];
pub fn compare(ctx: @This(), a_index: usize, b_index: usize) std.math.Order {
return std.math.order(ctx.keys[a_index], ctx.keys[b_index]);
}
};

Expand Down
11 changes: 5 additions & 6 deletions lib/std/compress/deflate/huffman_code.zig
Original file line number Diff line number Diff line change
Expand Up @@ -346,17 +346,16 @@ pub fn generateFixedOffsetEncoding(allocator: Allocator) !HuffmanEncoder {
return h;
}

fn byLiteral(context: void, a: LiteralNode, b: LiteralNode) bool {
fn byLiteral(context: void, a: LiteralNode, b: LiteralNode) math.Order {
_ = context;
return a.literal < b.literal;
return math.order(a.literal, b.literal);
}

fn byFreq(context: void, a: LiteralNode, b: LiteralNode) bool {
_ = context;
fn byFreq(context: void, a: LiteralNode, b: LiteralNode) math.Order {
if (a.freq == b.freq) {
return a.literal < b.literal;
return byLiteral(context, a, b);
}
return a.freq < b.freq;
return math.order(a.freq, b.freq);
}

test "generate a Huffman code from an array of frequencies" {
Expand Down
8 changes: 4 additions & 4 deletions lib/std/compress/zstandard/decode/huffman.zig
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ fn assignSymbols(weight_sorted_prefixed_symbols: []LiteralsSection.HuffmanTree.P
LiteralsSection.HuffmanTree.PrefixedSymbol,
weight_sorted_prefixed_symbols,
weights,
lessThanByWeight,
compareByWeight,
);

var prefix: u16 = 0;
Expand Down Expand Up @@ -222,13 +222,13 @@ pub fn decodeHuffmanTreeSlice(
return buildHuffmanTree(&weights, symbol_count);
}

fn lessThanByWeight(
fn compareByWeight(
weights: [256]u4,
lhs: LiteralsSection.HuffmanTree.PrefixedSymbol,
rhs: LiteralsSection.HuffmanTree.PrefixedSymbol,
) bool {
) std.math.Order {
// NOTE: this function relies on the use of a stable sorting algorithm,
// otherwise a special case of if (weights[lhs] == weights[rhs]) return lhs < rhs;
// should be added
return weights[lhs.symbol] < weights[rhs.symbol];
return std.math.order(weights[lhs.symbol], weights[rhs.symbol]);
}
6 changes: 3 additions & 3 deletions lib/std/comptime_string_map.zig
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ const mem = std.mem;
/// You can pass `struct { []const u8 }` (only keys) tuples if `V` is `void`.
pub fn ComptimeStringMap(comptime V: type, comptime kvs_list: anytype) type {
const precomputed = comptime blk: {
@setEvalBranchQuota(2000);
@setEvalBranchQuota(4000);
const KV = struct {
key: []const u8,
value: V,
};
var sorted_kvs: [kvs_list.len]KV = undefined;
const lenAsc = (struct {
fn lenAsc(context: void, a: KV, b: KV) bool {
fn lenAsc(context: void, a: KV, b: KV) std.math.Order {
_ = context;
return a.key.len < b.key.len;
return std.math.order(a.key.len, b.key.len);
}
}).lenAsc;
for (kvs_list, 0..) |kv, i| {
Expand Down
6 changes: 3 additions & 3 deletions lib/std/debug.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1211,7 +1211,7 @@ fn readMachODebugInfo(allocator: mem.Allocator, macho_file: File) !ModuleDebugIn
// Even though lld emits symbols in ascending order, this debug code
// should work for programs linked in any valid way.
// This sort is so that we can binary search later.
std.sort.sort(MachoSymbol, symbols, {}, MachoSymbol.addressLessThan);
std.sort.sort(MachoSymbol, symbols, {}, MachoSymbol.addressCompare);

return ModuleDebugInfo{
.base_address = undefined,
Expand Down Expand Up @@ -1269,9 +1269,9 @@ const MachoSymbol = struct {
return self.addr;
}

fn addressLessThan(context: void, lhs: MachoSymbol, rhs: MachoSymbol) bool {
fn addressCompare(context: void, lhs: MachoSymbol, rhs: MachoSymbol) std.math.Order {
_ = context;
return lhs.addr < rhs.addr;
return std.math.order(lhs.addr, rhs.addr);
}
};

Expand Down
4 changes: 2 additions & 2 deletions lib/std/enums.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1289,9 +1289,9 @@ test "std.enums.ensureIndexer" {
});
}

fn ascByValue(ctx: void, comptime a: EnumField, comptime b: EnumField) bool {
fn ascByValue(ctx: void, comptime a: EnumField, comptime b: EnumField) std.math.Order {
_ = ctx;
return a.value < b.value;
return std.math.order(a.value, b.value);
}
pub fn EnumIndexer(comptime E: type) type {
if (!@typeInfo(E).Enum.is_exhaustive) {
Expand Down
9 changes: 4 additions & 5 deletions lib/std/http/Headers.zig
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,10 @@ pub const Field = struct {
name: []const u8,
value: []const u8,

fn lessThan(ctx: void, a: Field, b: Field) bool {
fn compare(ctx: void, a: Field, b: Field) std.math.Order {
_ = ctx;
if (a.name.ptr == b.name.ptr) return false;

return ascii.lessThanIgnoreCase(a.name, b.name);
if (a.name.ptr == b.name.ptr) return .eq;
return ascii.orderIgnoreCase(a.name, b.name);
}
};

Expand Down Expand Up @@ -191,7 +190,7 @@ pub const Headers = struct {

/// Sorts the headers in lexicographical order.
pub fn sort(headers: *Headers) void {
std.sort.sort(Field, headers.list.items, {}, Field.lessThan);
std.sort.sort(Field, headers.list.items, {}, Field.compare);
headers.rebuildIndex();
}

Expand Down
10 changes: 5 additions & 5 deletions lib/std/multi_array_list.zig
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,12 @@ pub fn MultiArrayList(comptime T: type) type {
};
}
const Sort = struct {
fn lessThan(context: void, lhs: Data, rhs: Data) bool {
fn compare(context: void, lhs: Data, rhs: Data) std.math.Order {
_ = context;
return lhs.alignment > rhs.alignment;
return std.math.order(rhs.alignment, lhs.alignment);
}
};
std.sort.sort(Data, &data, {}, Sort.lessThan);
std.sort.sort(Data, &data, {}, Sort.compare);
var sizes_bytes: [fields.len]usize = undefined;
var field_indexes: [fields.len]usize = undefined;
for (data, 0..) |elem, i| {
Expand Down Expand Up @@ -483,8 +483,8 @@ pub fn MultiArrayList(comptime T: type) type {
}
}

pub fn lessThan(sc: @This(), a_index: usize, b_index: usize) bool {
return sc.sub_ctx.lessThan(a_index, b_index);
pub fn compare(sc: @This(), a_index: usize, b_index: usize) std.math.Order {
return sc.sub_ctx.compare(a_index, b_index);
}
};

Expand Down
6 changes: 3 additions & 3 deletions lib/std/net.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1082,7 +1082,7 @@ fn linuxLookupName(
key |= (MAXADDRS - @intCast(i32, i)) << DAS_ORDER_SHIFT;
addr.sortkey = key;
}
std.sort.sort(LookupAddr, addrs.items, {}, addrCmpLessThan);
std.sort.sort(LookupAddr, addrs.items, {}, addrCompare);
}

const Policy = struct {
Expand Down Expand Up @@ -1199,9 +1199,9 @@ fn IN6_IS_ADDR_SITELOCAL(a: [16]u8) bool {
}

// Parameters `b` and `a` swapped to make this descending.
fn addrCmpLessThan(context: void, b: LookupAddr, a: LookupAddr) bool {
fn addrCompare(context: void, b: LookupAddr, a: LookupAddr) std.math.Order {
_ = context;
return a.sortkey < b.sortkey;
return std.math.order(a.sortkey, b.sortkey);
}

fn linuxLookupNameFromNull(
Expand Down
8 changes: 4 additions & 4 deletions lib/std/priority_dequeue.zig
Original file line number Diff line number Diff line change
Expand Up @@ -461,12 +461,12 @@ pub fn PriorityDequeue(comptime T: type, comptime Context: type, comptime compar
};
}

fn lessThanComparison(context: void, a: u32, b: u32) Order {
fn compare(context: void, a: u32, b: u32) Order {
_ = context;
return std.math.order(a, b);
}

const PDQ = PriorityDequeue(u32, void, lessThanComparison);
const PDQ = PriorityDequeue(u32, void, compare);

test "std.PriorityDequeue: add and remove min" {
var queue = PDQ.init(testing.allocator, {});
Expand Down Expand Up @@ -977,11 +977,11 @@ fn generateRandomSlice(allocator: std.mem.Allocator, rng: std.rand.Random, size:
return array.toOwnedSlice();
}

fn contextLessThanComparison(context: []const u32, a: usize, b: usize) Order {
fn contextComparison(context: []const u32, a: usize, b: usize) Order {
return std.math.order(context[a], context[b]);
}

const CPDQ = PriorityDequeue(usize, []const u32, contextLessThanComparison);
const CPDQ = PriorityDequeue(usize, []const u32, contextComparison);

test "std.PriorityDequeue: add and remove" {
const context = [_]u32{ 5, 3, 4, 2, 2, 8, 0 };
Expand Down
7 changes: 4 additions & 3 deletions lib/std/priority_queue.zig
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,8 @@ fn lessThan(context: void, a: u32, b: u32) Order {
}

fn greaterThan(context: void, a: u32, b: u32) Order {
return lessThan(context, a, b).invert();
_ = context;
return std.math.order(b, a);
}

const PQlt = PriorityQueue(u32, void, lessThan);
Expand Down Expand Up @@ -616,11 +617,11 @@ test "std.PriorityQueue: siftUp in remove" {
}
}

fn contextLessThan(context: []const u32, a: usize, b: usize) Order {
fn contextCompare(context: []const u32, a: usize, b: usize) Order {
return std.math.order(context[a], context[b]);
}

const CPQlt = PriorityQueue(usize, []const u32, contextLessThan);
const CPQlt = PriorityQueue(usize, []const u32, contextCompare);

test "std.PriorityQueue: add and remove min heap with contextful comparator" {
const context = [_]u32{ 5, 3, 4, 2, 2, 8, 0 };
Expand Down

0 comments on commit 87c8eaf

Please sign in to comment.