Authored by https://github.com/CraigglesO Original Discussion: #9890 I already had to create these functions and test cases for my own project so I decided to contribute to the main code base in hopes it would simplify my own. I realize this is still under discussion but this was a trivial amount of work so I thought I could help nudge the discussion towards a decision. Why add these to the standard library To better illustrate and solidify their value, the standard library's "sort" module already contains several binary search queries on arrays such as binarySearch and internal functions binaryFirst & binaryLast. A final example of its use: the Zig code itself created and used a bounding search in the linker. There still lacks the ability to allow the programmer themselves to search the array for a rough position and find an index to read &/ update. Adding these functions would also help to complement dynamic structures like ArrayList with it's insert function. Example Case I'm building a library in Zig for GIS geometry. To store points, lines, and polygons each 3D point is first translated into what's called an S2CellId. This is a fancy way of saying I reduce the Earth's spherical data into a 1D Hilbert Curve with cm precision. This gives me 2 convenient truths: Hilbert Curves have locality of reference. All points can be stored inside a 1D array Since lowerBound and upperBound to find data inside a radius for instance. If I'm interested in a specific cell at a specific "level" and want to iterate all duplicates, equalRange is a best fit.
935 lines
30 KiB
Zig
935 lines
30 KiB
Zig
const std = @import("std.zig");
|
|
const assert = std.debug.assert;
|
|
const testing = std.testing;
|
|
const mem = std.mem;
|
|
const math = std.math;
|
|
|
|
pub const Mode = enum { stable, unstable };
|
|
|
|
pub const block = @import("sort/block.zig").block;
|
|
pub const pdq = @import("sort/pdq.zig").pdq;
|
|
pub const pdqContext = @import("sort/pdq.zig").pdqContext;
|
|
|
|
/// Stable in-place sort. O(n) best case, O(pow(n, 2)) worst case.
|
|
/// O(1) memory (no allocator required).
|
|
/// Sorts in ascending order with respect to the given `lessThan` function.
|
|
pub fn insertion(
|
|
comptime T: type,
|
|
items: []T,
|
|
context: anytype,
|
|
comptime lessThanFn: fn (@TypeOf(context), lhs: T, rhs: T) bool,
|
|
) void {
|
|
const Context = struct {
|
|
items: []T,
|
|
sub_ctx: @TypeOf(context),
|
|
|
|
pub fn lessThan(ctx: @This(), a: usize, b: usize) bool {
|
|
return lessThanFn(ctx.sub_ctx, ctx.items[a], ctx.items[b]);
|
|
}
|
|
|
|
pub fn swap(ctx: @This(), a: usize, b: usize) void {
|
|
return mem.swap(T, &ctx.items[a], &ctx.items[b]);
|
|
}
|
|
};
|
|
insertionContext(0, items.len, Context{ .items = items, .sub_ctx = context });
|
|
}
|
|
|
|
/// Stable in-place sort. O(n) best case, O(pow(n, 2)) worst case.
|
|
/// O(1) memory (no allocator required).
|
|
/// `context` must have methods `swap` and `lessThan`,
|
|
/// which each take 2 `usize` parameters indicating the index of an item.
|
|
/// Sorts in ascending order with respect to `lessThan`.
|
|
pub fn insertionContext(a: usize, b: usize, context: anytype) void {
|
|
assert(a <= b);
|
|
|
|
var i = a + 1;
|
|
while (i < b) : (i += 1) {
|
|
var j = i;
|
|
while (j > a and context.lessThan(j, j - 1)) : (j -= 1) {
|
|
context.swap(j, j - 1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Unstable in-place sort. O(n*log(n)) best case, worst case and average case.
|
|
/// O(1) memory (no allocator required).
|
|
/// Sorts in ascending order with respect to the given `lessThan` function.
|
|
pub fn heap(
|
|
comptime T: type,
|
|
items: []T,
|
|
context: anytype,
|
|
comptime lessThanFn: fn (@TypeOf(context), lhs: T, rhs: T) bool,
|
|
) void {
|
|
const Context = struct {
|
|
items: []T,
|
|
sub_ctx: @TypeOf(context),
|
|
|
|
pub fn lessThan(ctx: @This(), a: usize, b: usize) bool {
|
|
return lessThanFn(ctx.sub_ctx, ctx.items[a], ctx.items[b]);
|
|
}
|
|
|
|
pub fn swap(ctx: @This(), a: usize, b: usize) void {
|
|
return mem.swap(T, &ctx.items[a], &ctx.items[b]);
|
|
}
|
|
};
|
|
heapContext(0, items.len, Context{ .items = items, .sub_ctx = context });
|
|
}
|
|
|
|
/// Unstable in-place sort. O(n*log(n)) best case, worst case and average case.
|
|
/// O(1) memory (no allocator required).
|
|
/// `context` must have methods `swap` and `lessThan`,
|
|
/// which each take 2 `usize` parameters indicating the index of an item.
|
|
/// Sorts in ascending order with respect to `lessThan`.
|
|
pub fn heapContext(a: usize, b: usize, context: anytype) void {
|
|
assert(a <= b);
|
|
// build the heap in linear time.
|
|
var i = a + (b - a) / 2;
|
|
while (i > a) {
|
|
i -= 1;
|
|
siftDown(a, i, b, context);
|
|
}
|
|
|
|
// pop maximal elements from the heap.
|
|
i = b;
|
|
while (i > a) {
|
|
i -= 1;
|
|
context.swap(a, i);
|
|
siftDown(a, a, i, context);
|
|
}
|
|
}
|
|
|
|
fn siftDown(a: usize, target: usize, b: usize, context: anytype) void {
|
|
var cur = target;
|
|
while (true) {
|
|
// When we don't overflow from the multiply below, the following expression equals (2*cur) - (2*a) + a + 1
|
|
// The `+ a + 1` is safe because:
|
|
// for `a > 0` then `2a >= a + 1`.
|
|
// for `a = 0`, the expression equals `2*cur+1`. `2*cur` is an even number, therefore adding 1 is safe.
|
|
var child = (math.mul(usize, cur - a, 2) catch break) + a + 1;
|
|
|
|
// stop if we overshot the boundary
|
|
if (!(child < b)) break;
|
|
|
|
// `next_child` is at most `b`, therefore no overflow is possible
|
|
const next_child = child + 1;
|
|
|
|
// store the greater child in `child`
|
|
if (next_child < b and context.lessThan(child, next_child)) {
|
|
child = next_child;
|
|
}
|
|
|
|
// stop if the Heap invariant holds at `cur`.
|
|
if (context.lessThan(child, cur)) break;
|
|
|
|
// swap `cur` with the greater child,
|
|
// move one step down, and continue sifting.
|
|
context.swap(child, cur);
|
|
cur = child;
|
|
}
|
|
}
|
|
|
|
/// Use to generate a comparator function for a given type. e.g. `sort(u8, slice, {}, asc(u8))`.
|
|
pub fn asc(comptime T: type) fn (void, T, T) bool {
|
|
return struct {
|
|
pub fn inner(_: void, a: T, b: T) bool {
|
|
return a < b;
|
|
}
|
|
}.inner;
|
|
}
|
|
|
|
/// Use to generate a comparator function for a given type. e.g. `sort(u8, slice, {}, desc(u8))`.
|
|
pub fn desc(comptime T: type) fn (void, T, T) bool {
|
|
return struct {
|
|
pub fn inner(_: void, a: T, b: T) bool {
|
|
return a > b;
|
|
}
|
|
}.inner;
|
|
}
|
|
|
|
const asc_u8 = asc(u8);
|
|
const asc_i32 = asc(i32);
|
|
const desc_u8 = desc(u8);
|
|
const desc_i32 = desc(i32);
|
|
|
|
const sort_funcs = &[_]fn (comptime type, anytype, anytype, comptime anytype) void{
|
|
block,
|
|
pdq,
|
|
insertion,
|
|
heap,
|
|
};
|
|
|
|
const context_sort_funcs = &[_]fn (usize, usize, anytype) void{
|
|
// blockContext,
|
|
pdqContext,
|
|
insertionContext,
|
|
heapContext,
|
|
};
|
|
|
|
const IdAndValue = struct {
|
|
id: usize,
|
|
value: i32,
|
|
|
|
fn lessThan(context: void, a: IdAndValue, b: IdAndValue) bool {
|
|
_ = context;
|
|
return a.value < b.value;
|
|
}
|
|
};
|
|
|
|
test "stable sort" {
|
|
const expected = [_]IdAndValue{
|
|
IdAndValue{ .id = 0, .value = 0 },
|
|
IdAndValue{ .id = 1, .value = 0 },
|
|
IdAndValue{ .id = 2, .value = 0 },
|
|
IdAndValue{ .id = 0, .value = 1 },
|
|
IdAndValue{ .id = 1, .value = 1 },
|
|
IdAndValue{ .id = 2, .value = 1 },
|
|
IdAndValue{ .id = 0, .value = 2 },
|
|
IdAndValue{ .id = 1, .value = 2 },
|
|
IdAndValue{ .id = 2, .value = 2 },
|
|
};
|
|
|
|
var cases = [_][9]IdAndValue{
|
|
[_]IdAndValue{
|
|
IdAndValue{ .id = 0, .value = 0 },
|
|
IdAndValue{ .id = 0, .value = 1 },
|
|
IdAndValue{ .id = 0, .value = 2 },
|
|
IdAndValue{ .id = 1, .value = 0 },
|
|
IdAndValue{ .id = 1, .value = 1 },
|
|
IdAndValue{ .id = 1, .value = 2 },
|
|
IdAndValue{ .id = 2, .value = 0 },
|
|
IdAndValue{ .id = 2, .value = 1 },
|
|
IdAndValue{ .id = 2, .value = 2 },
|
|
},
|
|
[_]IdAndValue{
|
|
IdAndValue{ .id = 0, .value = 2 },
|
|
IdAndValue{ .id = 0, .value = 1 },
|
|
IdAndValue{ .id = 0, .value = 0 },
|
|
IdAndValue{ .id = 1, .value = 2 },
|
|
IdAndValue{ .id = 1, .value = 1 },
|
|
IdAndValue{ .id = 1, .value = 0 },
|
|
IdAndValue{ .id = 2, .value = 2 },
|
|
IdAndValue{ .id = 2, .value = 1 },
|
|
IdAndValue{ .id = 2, .value = 0 },
|
|
},
|
|
};
|
|
|
|
for (&cases) |*case| {
|
|
block(IdAndValue, (case.*)[0..], {}, IdAndValue.lessThan);
|
|
for (case.*, 0..) |item, i| {
|
|
try testing.expect(item.id == expected[i].id);
|
|
try testing.expect(item.value == expected[i].value);
|
|
}
|
|
}
|
|
}
|
|
|
|
test "sort" {
|
|
const u8cases = [_][]const []const u8{
|
|
&[_][]const u8{
|
|
"",
|
|
"",
|
|
},
|
|
&[_][]const u8{
|
|
"a",
|
|
"a",
|
|
},
|
|
&[_][]const u8{
|
|
"az",
|
|
"az",
|
|
},
|
|
&[_][]const u8{
|
|
"za",
|
|
"az",
|
|
},
|
|
&[_][]const u8{
|
|
"asdf",
|
|
"adfs",
|
|
},
|
|
&[_][]const u8{
|
|
"one",
|
|
"eno",
|
|
},
|
|
};
|
|
|
|
const i32cases = [_][]const []const i32{
|
|
&[_][]const i32{
|
|
&[_]i32{},
|
|
&[_]i32{},
|
|
},
|
|
&[_][]const i32{
|
|
&[_]i32{1},
|
|
&[_]i32{1},
|
|
},
|
|
&[_][]const i32{
|
|
&[_]i32{ 0, 1 },
|
|
&[_]i32{ 0, 1 },
|
|
},
|
|
&[_][]const i32{
|
|
&[_]i32{ 1, 0 },
|
|
&[_]i32{ 0, 1 },
|
|
},
|
|
&[_][]const i32{
|
|
&[_]i32{ 1, -1, 0 },
|
|
&[_]i32{ -1, 0, 1 },
|
|
},
|
|
&[_][]const i32{
|
|
&[_]i32{ 2, 1, 3 },
|
|
&[_]i32{ 1, 2, 3 },
|
|
},
|
|
&[_][]const i32{
|
|
&[_]i32{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 55, 32, 39, 58, 21, 88, 43, 22, 59 },
|
|
&[_]i32{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 21, 22, 32, 39, 43, 55, 58, 59, 88 },
|
|
},
|
|
};
|
|
|
|
inline for (sort_funcs) |sortFn| {
|
|
for (u8cases) |case| {
|
|
var buf: [20]u8 = undefined;
|
|
const slice = buf[0..case[0].len];
|
|
@memcpy(slice, case[0]);
|
|
sortFn(u8, slice, {}, asc_u8);
|
|
try testing.expect(mem.eql(u8, slice, case[1]));
|
|
}
|
|
|
|
for (i32cases) |case| {
|
|
var buf: [20]i32 = undefined;
|
|
const slice = buf[0..case[0].len];
|
|
@memcpy(slice, case[0]);
|
|
sortFn(i32, slice, {}, asc_i32);
|
|
try testing.expect(mem.eql(i32, slice, case[1]));
|
|
}
|
|
}
|
|
}
|
|
|
|
test "sort descending" {
|
|
const rev_cases = [_][]const []const i32{
|
|
&[_][]const i32{
|
|
&[_]i32{},
|
|
&[_]i32{},
|
|
},
|
|
&[_][]const i32{
|
|
&[_]i32{1},
|
|
&[_]i32{1},
|
|
},
|
|
&[_][]const i32{
|
|
&[_]i32{ 0, 1 },
|
|
&[_]i32{ 1, 0 },
|
|
},
|
|
&[_][]const i32{
|
|
&[_]i32{ 1, 0 },
|
|
&[_]i32{ 1, 0 },
|
|
},
|
|
&[_][]const i32{
|
|
&[_]i32{ 1, -1, 0 },
|
|
&[_]i32{ 1, 0, -1 },
|
|
},
|
|
&[_][]const i32{
|
|
&[_]i32{ 2, 1, 3 },
|
|
&[_]i32{ 3, 2, 1 },
|
|
},
|
|
};
|
|
|
|
inline for (sort_funcs) |sortFn| {
|
|
for (rev_cases) |case| {
|
|
var buf: [8]i32 = undefined;
|
|
const slice = buf[0..case[0].len];
|
|
@memcpy(slice, case[0]);
|
|
sortFn(i32, slice, {}, desc_i32);
|
|
try testing.expect(mem.eql(i32, slice, case[1]));
|
|
}
|
|
}
|
|
}
|
|
|
|
test "sort with context in the middle of a slice" {
|
|
const Context = struct {
|
|
items: []i32,
|
|
|
|
pub fn lessThan(ctx: @This(), a: usize, b: usize) bool {
|
|
return ctx.items[a] < ctx.items[b];
|
|
}
|
|
|
|
pub fn swap(ctx: @This(), a: usize, b: usize) void {
|
|
return mem.swap(i32, &ctx.items[a], &ctx.items[b]);
|
|
}
|
|
};
|
|
|
|
const i32cases = [_][]const []const i32{
|
|
&[_][]const i32{
|
|
&[_]i32{ 0, 1, 8, 3, 6, 5, 4, 2, 9, 7, 10, 55, 32, 39, 58, 21, 88, 43, 22, 59 },
|
|
&[_]i32{ 50, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 21, 22, 32, 39, 43, 55, 58, 59, 88 },
|
|
},
|
|
};
|
|
|
|
const ranges = [_]struct { start: usize, end: usize }{
|
|
.{ .start = 10, .end = 20 },
|
|
.{ .start = 1, .end = 11 },
|
|
.{ .start = 3, .end = 7 },
|
|
};
|
|
|
|
inline for (context_sort_funcs) |sortFn| {
|
|
for (i32cases) |case| {
|
|
for (ranges) |range| {
|
|
var buf: [20]i32 = undefined;
|
|
const slice = buf[0..case[0].len];
|
|
@memcpy(slice, case[0]);
|
|
sortFn(range.start, range.end, Context{ .items = slice });
|
|
try testing.expectEqualSlices(i32, case[1][range.start..range.end], slice[range.start..range.end]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
test "sort fuzz testing" {
|
|
var prng = std.rand.DefaultPrng.init(0x12345678);
|
|
const random = prng.random();
|
|
const test_case_count = 10;
|
|
|
|
inline for (sort_funcs) |sortFn| {
|
|
var i: usize = 0;
|
|
while (i < test_case_count) : (i += 1) {
|
|
const array_size = random.intRangeLessThan(usize, 0, 1000);
|
|
const array = try testing.allocator.alloc(i32, array_size);
|
|
defer testing.allocator.free(array);
|
|
// populate with random data
|
|
for (array) |*item| {
|
|
item.* = random.intRangeLessThan(i32, 0, 100);
|
|
}
|
|
sortFn(i32, array, {}, asc_i32);
|
|
try testing.expect(isSorted(i32, array, {}, asc_i32));
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn binarySearch(
|
|
comptime T: type,
|
|
key: anytype,
|
|
items: []const T,
|
|
context: anytype,
|
|
comptime compareFn: fn (context: @TypeOf(context), key: @TypeOf(key), mid_item: T) math.Order,
|
|
) ?usize {
|
|
var left: usize = 0;
|
|
var right: usize = items.len;
|
|
|
|
while (left < right) {
|
|
// Avoid overflowing in the midpoint calculation
|
|
const mid = left + (right - left) / 2;
|
|
// Compare the key with the midpoint element
|
|
switch (compareFn(context, key, items[mid])) {
|
|
.eq => return mid,
|
|
.gt => left = mid + 1,
|
|
.lt => right = mid,
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
test "binarySearch" {
|
|
const S = struct {
|
|
fn order_u32(context: void, lhs: u32, rhs: u32) math.Order {
|
|
_ = context;
|
|
return math.order(lhs, rhs);
|
|
}
|
|
fn order_i32(context: void, lhs: i32, rhs: i32) math.Order {
|
|
_ = context;
|
|
return math.order(lhs, rhs);
|
|
}
|
|
};
|
|
try testing.expectEqual(
|
|
@as(?usize, null),
|
|
binarySearch(u32, @as(u32, 1), &[_]u32{}, {}, S.order_u32),
|
|
);
|
|
try testing.expectEqual(
|
|
@as(?usize, 0),
|
|
binarySearch(u32, @as(u32, 1), &[_]u32{1}, {}, S.order_u32),
|
|
);
|
|
try testing.expectEqual(
|
|
@as(?usize, null),
|
|
binarySearch(u32, @as(u32, 1), &[_]u32{0}, {}, S.order_u32),
|
|
);
|
|
try testing.expectEqual(
|
|
@as(?usize, null),
|
|
binarySearch(u32, @as(u32, 0), &[_]u32{1}, {}, S.order_u32),
|
|
);
|
|
try testing.expectEqual(
|
|
@as(?usize, 4),
|
|
binarySearch(u32, @as(u32, 5), &[_]u32{ 1, 2, 3, 4, 5 }, {}, S.order_u32),
|
|
);
|
|
try testing.expectEqual(
|
|
@as(?usize, 0),
|
|
binarySearch(u32, @as(u32, 2), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.order_u32),
|
|
);
|
|
try testing.expectEqual(
|
|
@as(?usize, 1),
|
|
binarySearch(i32, @as(i32, -4), &[_]i32{ -7, -4, 0, 9, 10 }, {}, S.order_i32),
|
|
);
|
|
try testing.expectEqual(
|
|
@as(?usize, 3),
|
|
binarySearch(i32, @as(i32, 98), &[_]i32{ -100, -25, 2, 98, 99, 100 }, {}, S.order_i32),
|
|
);
|
|
const R = struct {
|
|
b: i32,
|
|
e: i32,
|
|
|
|
fn r(b: i32, e: i32) @This() {
|
|
return @This(){ .b = b, .e = e };
|
|
}
|
|
|
|
fn order(context: void, key: i32, mid_item: @This()) math.Order {
|
|
_ = context;
|
|
|
|
if (key < mid_item.b) {
|
|
return .lt;
|
|
}
|
|
|
|
if (key > mid_item.e) {
|
|
return .gt;
|
|
}
|
|
|
|
return .eq;
|
|
}
|
|
};
|
|
try testing.expectEqual(
|
|
@as(?usize, null),
|
|
binarySearch(R, @as(i32, -45), &[_]R{ R.r(-100, -50), R.r(-40, -20), R.r(-10, 20), R.r(30, 40) }, {}, R.order),
|
|
);
|
|
try testing.expectEqual(
|
|
@as(?usize, 2),
|
|
binarySearch(R, @as(i32, 10), &[_]R{ R.r(-100, -50), R.r(-40, -20), R.r(-10, 20), R.r(30, 40) }, {}, R.order),
|
|
);
|
|
try testing.expectEqual(
|
|
@as(?usize, 1),
|
|
binarySearch(R, @as(i32, -20), &[_]R{ R.r(-100, -50), R.r(-40, -20), R.r(-10, 20), R.r(30, 40) }, {}, R.order),
|
|
);
|
|
}
|
|
|
|
/// Returns the index pointing to the first element in the range [first,last)
|
|
/// which does not compare less than val.
|
|
/// The function optimizes the number of comparisons performed by using a binary search O(log n).
|
|
/// An example lessThan function:
|
|
/// fn lower_u32(_: void, key: u32, lhs: u32) bool { return lhs < key; }
|
|
pub fn lowerBound(
|
|
comptime T: type,
|
|
key: anytype,
|
|
items: []const T,
|
|
context: anytype,
|
|
comptime lessThan: fn (context: @TypeOf(context), key: @TypeOf(key), lhs: T) bool,
|
|
) usize {
|
|
var left: usize = 0;
|
|
var right: usize = items.len;
|
|
var mid: usize = undefined;
|
|
|
|
while (left < right) {
|
|
mid = left + (right - left) / 2;
|
|
if (lessThan(context, key, items[mid])) {
|
|
left = mid + 1;
|
|
} else {
|
|
right = mid;
|
|
}
|
|
}
|
|
|
|
return left;
|
|
}
|
|
|
|
test "lowerBound" {
|
|
const S = struct {
|
|
fn lower_u32(context: void, key: u32, lhs: u32) bool {
|
|
_ = context;
|
|
return lhs < key;
|
|
}
|
|
fn lower_i32(context: void, key: i32, lhs: i32) bool {
|
|
_ = context;
|
|
return lhs < key;
|
|
}
|
|
fn lower_f32(context: void, key: f32, lhs: f32) bool {
|
|
_ = context;
|
|
return lhs < key;
|
|
}
|
|
};
|
|
|
|
// u32
|
|
// test no data
|
|
try testing.expectEqual(
|
|
@as(?usize, 0),
|
|
lowerBound(u32, @as(u32, 0), &[_]u32{}, {}, S.lower_u32),
|
|
);
|
|
// test below the first element
|
|
try testing.expectEqual(
|
|
@as(?usize, 0),
|
|
lowerBound(u32, @as(u32, 0), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32),
|
|
);
|
|
// test equal to the first element
|
|
try testing.expectEqual(
|
|
@as(?usize, 0),
|
|
lowerBound(u32, @as(u32, 2), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32),
|
|
);
|
|
// test between two numbers
|
|
try testing.expectEqual(
|
|
@as(?usize, 2),
|
|
lowerBound(u32, @as(u32, 5), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32),
|
|
);
|
|
// test equal to a number not at the ends
|
|
try testing.expectEqual(
|
|
@as(?usize, 2),
|
|
lowerBound(u32, @as(u32, 8), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32),
|
|
);
|
|
// test equal to the last element
|
|
try testing.expectEqual(
|
|
@as(?usize, 5),
|
|
lowerBound(u32, @as(u32, 64), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32),
|
|
);
|
|
// test above the last element
|
|
try testing.expectEqual(
|
|
@as(?usize, 6),
|
|
lowerBound(u32, @as(u32, 100), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32),
|
|
);
|
|
// i32
|
|
try testing.expectEqual(
|
|
@as(?usize, 2),
|
|
lowerBound(i32, @as(i32, 5), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_i32),
|
|
);
|
|
// f32
|
|
try testing.expectEqual(
|
|
@as(?usize, 1),
|
|
lowerBound(f32, @as(f32, -33.4), &[_]f32{ -54.2, -26.7, 0.0, 56.55, 100.1, 322.0 }, {}, S.lower_f32),
|
|
);
|
|
}
|
|
|
|
/// Returns the index pointing to the first element in the range [first,last)
|
|
/// which compares greater than val.
|
|
/// The function optimizes the number of comparisons performed by using a binary search O(log n).
|
|
/// An example greaterThan function:
|
|
/// fn upper_u32(_: void, key: u32, rhs: u32) bool { return key >= rhs; }
|
|
pub fn upperBound(
|
|
comptime T: type,
|
|
key: anytype,
|
|
items: []const T,
|
|
context: anytype,
|
|
comptime greaterThan: fn (context: @TypeOf(context), key: @TypeOf(key), rhs: T) bool,
|
|
) usize {
|
|
var left: usize = 0;
|
|
var right: usize = items.len;
|
|
var mid: usize = undefined;
|
|
|
|
while (left < right) {
|
|
mid = (right + left) / 2;
|
|
if (greaterThan(context, key, items[mid])) {
|
|
left = mid + 1;
|
|
} else {
|
|
right = mid;
|
|
}
|
|
}
|
|
|
|
return left;
|
|
}
|
|
|
|
test "upperBound" {
|
|
const S = struct {
|
|
fn upper_u32(context: void, key: u32, rhs: u32) bool {
|
|
_ = context;
|
|
return key >= rhs;
|
|
}
|
|
fn upper_i32(context: void, key: i32, rhs: i32) bool {
|
|
_ = context;
|
|
return key >= rhs;
|
|
}
|
|
fn upper_f32(context: void, key: f32, rhs: f32) bool {
|
|
_ = context;
|
|
return key >= rhs;
|
|
}
|
|
};
|
|
|
|
// u32
|
|
// test no data
|
|
try testing.expectEqual(
|
|
@as(?usize, 0),
|
|
upperBound(u32, @as(u32, 0), &[_]u32{}, {}, S.upper_u32),
|
|
);
|
|
// test below the first element
|
|
try testing.expectEqual(
|
|
@as(?usize, 0),
|
|
upperBound(u32, @as(u32, 0), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.upper_u32),
|
|
);
|
|
// test equal to the first element
|
|
try testing.expectEqual(
|
|
@as(?usize, 1),
|
|
upperBound(u32, @as(u32, 2), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.upper_u32),
|
|
);
|
|
// test between two numbers
|
|
try testing.expectEqual(
|
|
@as(?usize, 2),
|
|
upperBound(u32, @as(u32, 5), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.upper_u32),
|
|
);
|
|
// test equal to a number not at the ends
|
|
try testing.expectEqual(
|
|
@as(?usize, 3),
|
|
upperBound(u32, @as(u32, 8), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.upper_u32),
|
|
);
|
|
// test equal to the last element
|
|
try testing.expectEqual(
|
|
@as(?usize, 6),
|
|
upperBound(u32, @as(u32, 64), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.upper_u32),
|
|
);
|
|
// test above the last element
|
|
try testing.expectEqual(
|
|
@as(?usize, 6),
|
|
upperBound(u32, @as(u32, 100), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.upper_u32),
|
|
);
|
|
// i32
|
|
try testing.expectEqual(
|
|
@as(?usize, 2),
|
|
upperBound(i32, @as(i32, 5), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.upper_i32),
|
|
);
|
|
// f32
|
|
try testing.expectEqual(
|
|
@as(?usize, 1),
|
|
upperBound(f32, @as(f32, -33.4), &[_]f32{ -54.2, -26.7, 0.0, 56.55, 100.1, 322.0 }, {}, S.upper_f32),
|
|
);
|
|
}
|
|
|
|
/// Returns a range containing all elements equivalent to value in the range [first,last)
|
|
/// The function optimizes the number of comparisons performed by using a binary search O(2 * log n).
|
|
/// Example lessThan & greaterThan functions:
|
|
/// fn lower_u32(_: void, key: u32, lhs: u32) bool { return lhs < key; }
|
|
/// fn upper_u32(_: void, key: u32, rhs: u32) bool { return key >= rhs; }
|
|
pub fn equalRange(
|
|
comptime T: type,
|
|
key: anytype,
|
|
items: []const T,
|
|
context: anytype,
|
|
comptime lessThan: fn (context: @TypeOf(context), key: @TypeOf(key), lhs: T) bool,
|
|
comptime greaterThan: fn (context: @TypeOf(context), key: @TypeOf(key), rhs: T) bool,
|
|
) struct { usize, usize } {
|
|
return .{
|
|
lowerBound(T, key, items, context, lessThan),
|
|
upperBound(T, key, items, context, greaterThan),
|
|
};
|
|
}
|
|
|
|
test "equalRange" {
|
|
const S = struct {
|
|
fn lower_i32(context: void, key: i32, lhs: i32) bool {
|
|
_ = context;
|
|
return lhs < key;
|
|
}
|
|
fn upper_i32(context: void, key: i32, rhs: i32) bool {
|
|
_ = context;
|
|
return key >= rhs;
|
|
}
|
|
fn lower_u32(context: void, key: u32, lhs: u32) bool {
|
|
_ = context;
|
|
return lhs < key;
|
|
}
|
|
fn upper_u32(context: void, key: u32, rhs: u32) bool {
|
|
_ = context;
|
|
return key >= rhs;
|
|
}
|
|
fn lower_f32(context: void, key: f32, lhs: f32) bool {
|
|
_ = context;
|
|
return lhs < key;
|
|
}
|
|
fn upper_f32(context: void, key: f32, rhs: f32) bool {
|
|
_ = context;
|
|
return key >= rhs;
|
|
}
|
|
};
|
|
|
|
// i32
|
|
// test no data
|
|
try testing.expectEqual(
|
|
@as(struct { usize, usize }, .{ 0, 0 }),
|
|
equalRange(i32, @as(i32, 0), &[_]i32{}, {}, S.lower_i32, S.upper_i32),
|
|
);
|
|
// test below the first element
|
|
try testing.expectEqual(
|
|
@as(struct { usize, usize }, .{ 0, 0 }),
|
|
equalRange(i32, @as(i32, 0), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_i32, S.upper_i32),
|
|
);
|
|
// test equal to the first element
|
|
try testing.expectEqual(
|
|
@as(struct { usize, usize }, .{ 0, 1 }),
|
|
equalRange(i32, @as(i32, 2), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_i32, S.upper_i32),
|
|
);
|
|
// test between two numbers
|
|
try testing.expectEqual(
|
|
@as(struct { usize, usize }, .{ 2, 2 }),
|
|
equalRange(i32, @as(i32, 5), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_i32, S.upper_i32),
|
|
);
|
|
// test equal to a number not at the ends
|
|
try testing.expectEqual(
|
|
@as(struct { usize, usize }, .{ 2, 3 }),
|
|
equalRange(i32, @as(i32, 8), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_i32, S.upper_i32),
|
|
);
|
|
// test equal to the last element
|
|
try testing.expectEqual(
|
|
@as(struct { usize, usize }, .{ 5, 6 }),
|
|
equalRange(i32, @as(i32, 64), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_i32, S.upper_i32),
|
|
);
|
|
// test above the last element
|
|
try testing.expectEqual(
|
|
@as(struct { usize, usize }, .{ 6, 6 }),
|
|
equalRange(i32, @as(i32, 100), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_i32, S.upper_i32),
|
|
);
|
|
// test many of the same element
|
|
try testing.expectEqual(
|
|
@as(struct { usize, usize }, .{ 2, 6 }),
|
|
equalRange(i32, @as(i32, 8), &[_]i32{ 2, 4, 8, 8, 8, 8, 15, 22 }, {}, S.lower_i32, S.upper_i32),
|
|
);
|
|
// u32
|
|
try testing.expectEqual(
|
|
@as(struct { usize, usize }, .{ 2, 2 }),
|
|
equalRange(u32, @as(u32, 5), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32, S.upper_u32),
|
|
);
|
|
// f32
|
|
try testing.expectEqual(
|
|
@as(struct { usize, usize }, .{ 1, 1 }),
|
|
equalRange(f32, @as(f32, -33.4), &[_]f32{ -54.2, -26.7, 0.0, 56.55, 100.1, 322.0 }, {}, S.lower_f32, S.upper_f32),
|
|
);
|
|
}
|
|
|
|
pub fn argMin(
|
|
comptime T: type,
|
|
items: []const T,
|
|
context: anytype,
|
|
comptime lessThan: fn (@TypeOf(context), lhs: T, rhs: T) bool,
|
|
) ?usize {
|
|
if (items.len == 0) {
|
|
return null;
|
|
}
|
|
|
|
var smallest = items[0];
|
|
var smallest_index: usize = 0;
|
|
for (items[1..], 0..) |item, i| {
|
|
if (lessThan(context, item, smallest)) {
|
|
smallest = item;
|
|
smallest_index = i + 1;
|
|
}
|
|
}
|
|
|
|
return smallest_index;
|
|
}
|
|
|
|
test "argMin" {
|
|
try testing.expectEqual(@as(?usize, null), argMin(i32, &[_]i32{}, {}, asc_i32));
|
|
try testing.expectEqual(@as(?usize, 0), argMin(i32, &[_]i32{1}, {}, asc_i32));
|
|
try testing.expectEqual(@as(?usize, 0), argMin(i32, &[_]i32{ 1, 2, 3, 4, 5 }, {}, asc_i32));
|
|
try testing.expectEqual(@as(?usize, 3), argMin(i32, &[_]i32{ 9, 3, 8, 2, 5 }, {}, asc_i32));
|
|
try testing.expectEqual(@as(?usize, 0), argMin(i32, &[_]i32{ 1, 1, 1, 1, 1 }, {}, asc_i32));
|
|
try testing.expectEqual(@as(?usize, 0), argMin(i32, &[_]i32{ -10, 1, 10 }, {}, asc_i32));
|
|
try testing.expectEqual(@as(?usize, 3), argMin(i32, &[_]i32{ 6, 3, 5, 7, 6 }, {}, desc_i32));
|
|
}
|
|
|
|
pub fn min(
|
|
comptime T: type,
|
|
items: []const T,
|
|
context: anytype,
|
|
comptime lessThan: fn (context: @TypeOf(context), lhs: T, rhs: T) bool,
|
|
) ?T {
|
|
const i = argMin(T, items, context, lessThan) orelse return null;
|
|
return items[i];
|
|
}
|
|
|
|
test "min" {
|
|
try testing.expectEqual(@as(?i32, null), min(i32, &[_]i32{}, {}, asc_i32));
|
|
try testing.expectEqual(@as(?i32, 1), min(i32, &[_]i32{1}, {}, asc_i32));
|
|
try testing.expectEqual(@as(?i32, 1), min(i32, &[_]i32{ 1, 2, 3, 4, 5 }, {}, asc_i32));
|
|
try testing.expectEqual(@as(?i32, 2), min(i32, &[_]i32{ 9, 3, 8, 2, 5 }, {}, asc_i32));
|
|
try testing.expectEqual(@as(?i32, 1), min(i32, &[_]i32{ 1, 1, 1, 1, 1 }, {}, asc_i32));
|
|
try testing.expectEqual(@as(?i32, -10), min(i32, &[_]i32{ -10, 1, 10 }, {}, asc_i32));
|
|
try testing.expectEqual(@as(?i32, 7), min(i32, &[_]i32{ 6, 3, 5, 7, 6 }, {}, desc_i32));
|
|
}
|
|
|
|
pub fn argMax(
|
|
comptime T: type,
|
|
items: []const T,
|
|
context: anytype,
|
|
comptime lessThan: fn (context: @TypeOf(context), lhs: T, rhs: T) bool,
|
|
) ?usize {
|
|
if (items.len == 0) {
|
|
return null;
|
|
}
|
|
|
|
var biggest = items[0];
|
|
var biggest_index: usize = 0;
|
|
for (items[1..], 0..) |item, i| {
|
|
if (lessThan(context, biggest, item)) {
|
|
biggest = item;
|
|
biggest_index = i + 1;
|
|
}
|
|
}
|
|
|
|
return biggest_index;
|
|
}
|
|
|
|
test "argMax" {
|
|
try testing.expectEqual(@as(?usize, null), argMax(i32, &[_]i32{}, {}, asc_i32));
|
|
try testing.expectEqual(@as(?usize, 0), argMax(i32, &[_]i32{1}, {}, asc_i32));
|
|
try testing.expectEqual(@as(?usize, 4), argMax(i32, &[_]i32{ 1, 2, 3, 4, 5 }, {}, asc_i32));
|
|
try testing.expectEqual(@as(?usize, 0), argMax(i32, &[_]i32{ 9, 3, 8, 2, 5 }, {}, asc_i32));
|
|
try testing.expectEqual(@as(?usize, 0), argMax(i32, &[_]i32{ 1, 1, 1, 1, 1 }, {}, asc_i32));
|
|
try testing.expectEqual(@as(?usize, 2), argMax(i32, &[_]i32{ -10, 1, 10 }, {}, asc_i32));
|
|
try testing.expectEqual(@as(?usize, 1), argMax(i32, &[_]i32{ 6, 3, 5, 7, 6 }, {}, desc_i32));
|
|
}
|
|
|
|
pub fn max(
|
|
comptime T: type,
|
|
items: []const T,
|
|
context: anytype,
|
|
comptime lessThan: fn (context: @TypeOf(context), lhs: T, rhs: T) bool,
|
|
) ?T {
|
|
const i = argMax(T, items, context, lessThan) orelse return null;
|
|
return items[i];
|
|
}
|
|
|
|
test "max" {
|
|
try testing.expectEqual(@as(?i32, null), max(i32, &[_]i32{}, {}, asc_i32));
|
|
try testing.expectEqual(@as(?i32, 1), max(i32, &[_]i32{1}, {}, asc_i32));
|
|
try testing.expectEqual(@as(?i32, 5), max(i32, &[_]i32{ 1, 2, 3, 4, 5 }, {}, asc_i32));
|
|
try testing.expectEqual(@as(?i32, 9), max(i32, &[_]i32{ 9, 3, 8, 2, 5 }, {}, asc_i32));
|
|
try testing.expectEqual(@as(?i32, 1), max(i32, &[_]i32{ 1, 1, 1, 1, 1 }, {}, asc_i32));
|
|
try testing.expectEqual(@as(?i32, 10), max(i32, &[_]i32{ -10, 1, 10 }, {}, asc_i32));
|
|
try testing.expectEqual(@as(?i32, 3), max(i32, &[_]i32{ 6, 3, 5, 7, 6 }, {}, desc_i32));
|
|
}
|
|
|
|
pub fn isSorted(
|
|
comptime T: type,
|
|
items: []const T,
|
|
context: anytype,
|
|
comptime lessThan: fn (context: @TypeOf(context), lhs: T, rhs: T) bool,
|
|
) bool {
|
|
var i: usize = 1;
|
|
while (i < items.len) : (i += 1) {
|
|
if (lessThan(context, items[i], items[i - 1])) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
test "isSorted" {
|
|
try testing.expect(isSorted(i32, &[_]i32{}, {}, asc_i32));
|
|
try testing.expect(isSorted(i32, &[_]i32{10}, {}, asc_i32));
|
|
try testing.expect(isSorted(i32, &[_]i32{ 1, 2, 3, 4, 5 }, {}, asc_i32));
|
|
try testing.expect(isSorted(i32, &[_]i32{ -10, 1, 1, 1, 10 }, {}, asc_i32));
|
|
|
|
try testing.expect(isSorted(i32, &[_]i32{}, {}, desc_i32));
|
|
try testing.expect(isSorted(i32, &[_]i32{-20}, {}, desc_i32));
|
|
try testing.expect(isSorted(i32, &[_]i32{ 3, 2, 1, 0, -1 }, {}, desc_i32));
|
|
try testing.expect(isSorted(i32, &[_]i32{ 10, -10 }, {}, desc_i32));
|
|
|
|
try testing.expect(isSorted(i32, &[_]i32{ 1, 1, 1, 1, 1 }, {}, asc_i32));
|
|
try testing.expect(isSorted(i32, &[_]i32{ 1, 1, 1, 1, 1 }, {}, desc_i32));
|
|
|
|
try testing.expectEqual(false, isSorted(i32, &[_]i32{ 5, 4, 3, 2, 1 }, {}, asc_i32));
|
|
try testing.expectEqual(false, isSorted(i32, &[_]i32{ 1, 2, 3, 4, 5 }, {}, desc_i32));
|
|
|
|
try testing.expect(isSorted(u8, "abcd", {}, asc_u8));
|
|
try testing.expect(isSorted(u8, "zyxw", {}, desc_u8));
|
|
|
|
try testing.expectEqual(false, isSorted(u8, "abcd", {}, desc_u8));
|
|
try testing.expectEqual(false, isSorted(u8, "zyxw", {}, asc_u8));
|
|
|
|
try testing.expect(isSorted(u8, "ffff", {}, asc_u8));
|
|
try testing.expect(isSorted(u8, "ffff", {}, desc_u8));
|
|
}
|