From 664c18544c2622352a4e38254496a349006d331b Mon Sep 17 00:00:00 2001 From: Craig O'Connor Date: Sat, 27 Jan 2024 22:42:56 +0100 Subject: [PATCH 1/2] Add lowerBound/upperBound/equalRange 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. --- lib/std/sort.zig | 284 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 284 insertions(+) diff --git a/lib/std/sort.zig b/lib/std/sort.zig index f354e80df6..6523b96ef5 100644 --- a/lib/std/sort.zig +++ b/lib/std/sort.zig @@ -502,6 +502,290 @@ test "binarySearch" { ); } +/// 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, From e487b576fadc2fd48100667ff233e4a0bb4f0aa5 Mon Sep 17 00:00:00 2001 From: John Schmidt Date: Sat, 27 Jan 2024 22:43:35 +0100 Subject: [PATCH 2/2] Changes to lowerBound/upperBound/equalRange The old definitions had some problems: - In `lowerBound`, the `lhs` (left hand side) argument was passed on the right hand side. - In `upperBound`, the `greaterThan` function needed to return `greaterThanOrEqual` for the function work, so either the name or the implementation is incorrect. To fix both problems, define the functions in terms of a `lessThan` function that returns `lhs < rhs`. The is more consistent with the rest of `sort.zig` and it's also how C++ implements lower/upperBound (1)(2). (1) https://en.cppreference.com/w/cpp/algorithm/lower_bound (2) https://en.cppreference.com/w/cpp/algorithm/upper_bound - Rewrite doc comments. - Add a couple of more test cases. - Add docstring for std.sort.binarySearch --- lib/std/sort.zig | 232 ++++++++++++++++++++++------------------------- 1 file changed, 108 insertions(+), 124 deletions(-) diff --git a/lib/std/sort.zig b/lib/std/sort.zig index 6523b96ef5..2781867a54 100644 --- a/lib/std/sort.zig +++ b/lib/std/sort.zig @@ -399,6 +399,13 @@ test "sort fuzz testing" { } } +/// Returns the index of an element in `items` equal to `key`. +/// If there are multiple such elements, returns the index of any one of them. +/// If there are no such elements, returns `null`. +/// +/// `items` must be sorted in ascending order with respect to `compareFn`. +/// +/// O(log n) complexity. pub fn binarySearch( comptime T: type, key: anytype, @@ -502,25 +509,25 @@ test "binarySearch" { ); } -/// 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; } +/// Returns the index of the first element in `items` greater than or equal to `key`, +/// or `items.len` if all elements are less than `key`. +/// +/// `items` must be sorted in ascending order with respect to `compareFn`. +/// +/// O(log n) complexity. 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, + comptime lessThan: fn (context: @TypeOf(context), lhs: @TypeOf(key), rhs: 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])) { + const mid = left + (right - left) / 2; + if (lessThan(context, items[mid], key)) { left = mid + 1; } else { right = mid; @@ -532,87 +539,85 @@ pub fn lowerBound( test "lowerBound" { const S = struct { - fn lower_u32(context: void, key: u32, lhs: u32) bool { + fn lower_u32(context: void, lhs: u32, rhs: u32) bool { _ = context; - return lhs < key; + return lhs < rhs; } - fn lower_i32(context: void, key: i32, lhs: i32) bool { + fn lower_i32(context: void, lhs: i32, rhs: i32) bool { _ = context; - return lhs < key; + return lhs < rhs; } - fn lower_f32(context: void, key: f32, lhs: f32) bool { + fn lower_f32(context: void, lhs: f32, rhs: f32) bool { _ = context; - return lhs < key; + return lhs < rhs; } }; - // u32 - // test no data try testing.expectEqual( - @as(?usize, 0), + @as(usize, 0), lowerBound(u32, @as(u32, 0), &[_]u32{}, {}, S.lower_u32), ); - // test below the first element try testing.expectEqual( - @as(?usize, 0), + @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), + @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), + @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), + @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), + @as(usize, 6), + lowerBound(u32, @as(u32, 8), &[_]u32{ 2, 4, 7, 7, 7, 7, 16, 32, 64 }, {}, S.lower_u32), + ); + try testing.expectEqual( + @as(usize, 2), + lowerBound(u32, @as(u32, 8), &[_]u32{ 2, 4, 8, 8, 8, 8, 16, 32, 64 }, {}, S.lower_u32), + ); + 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), + @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), + @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), + @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; } +/// Returns the index of the first element in `items` greater than `key`, +/// or `items.len` if all elements are less than or equal to `key`. +/// +/// `items` must be sorted in ascending order with respect to `compareFn`. +/// +/// O(log n) complexity. 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, + comptime lessThan: fn (context: @TypeOf(context), lhs: @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])) { + const mid = (right + left) / 2; + if (!lessThan(context, key, items[mid])) { left = mid + 1; } else { right = mid; @@ -624,165 +629,144 @@ pub fn upperBound( test "upperBound" { const S = struct { - fn upper_u32(context: void, key: u32, rhs: u32) bool { + fn lower_u32(context: void, lhs: u32, rhs: u32) bool { _ = context; - return key >= rhs; + return lhs < rhs; } - fn upper_i32(context: void, key: i32, rhs: i32) bool { + fn lower_i32(context: void, lhs: i32, rhs: i32) bool { _ = context; - return key >= rhs; + return lhs < rhs; } - fn upper_f32(context: void, key: f32, rhs: f32) bool { + fn lower_f32(context: void, lhs: f32, rhs: f32) bool { _ = context; - return key >= rhs; + return lhs < rhs; } }; - // u32 - // test no data try testing.expectEqual( - @as(?usize, 0), - upperBound(u32, @as(u32, 0), &[_]u32{}, {}, S.upper_u32), + @as(usize, 0), + upperBound(u32, @as(u32, 0), &[_]u32{}, {}, S.lower_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), + @as(usize, 0), + upperBound(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, 1), - upperBound(u32, @as(u32, 2), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.upper_u32), + @as(usize, 1), + upperBound(u32, @as(u32, 2), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_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), + @as(usize, 2), + upperBound(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, 3), - upperBound(u32, @as(u32, 8), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.upper_u32), + @as(usize, 6), + upperBound(u32, @as(u32, 8), &[_]u32{ 2, 4, 7, 7, 7, 7, 16, 32, 64 }, {}, S.lower_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), + @as(usize, 6), + upperBound(u32, @as(u32, 8), &[_]u32{ 2, 4, 8, 8, 8, 8, 16, 32, 64 }, {}, S.lower_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), + @as(usize, 3), + upperBound(u32, @as(u32, 8), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32), ); - // i32 try testing.expectEqual( - @as(?usize, 2), - upperBound(i32, @as(i32, 5), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.upper_i32), + @as(usize, 6), + upperBound(u32, @as(u32, 64), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32), ); - // 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), + @as(usize, 6), + upperBound(u32, @as(u32, 100), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_u32), + ); + try testing.expectEqual( + @as(usize, 2), + upperBound(i32, @as(i32, 5), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_i32), + ); + 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.lower_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; } +/// Returns a tuple of the lower and upper indices in `items` between which all elements are equal to `key`. +/// If no element in `items` is equal to `key`, both indices are the +/// index of the first element in `items` greater than `key`. +/// If no element in `items` is greater than `key`, both indices equal `items.len`. +/// +/// `items` must be sorted in ascending order with respect to `compareFn`. +/// +/// O(log n) complexity. +/// +/// See also: `lowerBound` and `upperBound`. 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, + comptime lessThan: fn (context: @TypeOf(context), lhs: @TypeOf(key), rhs: T) bool, ) struct { usize, usize } { return .{ lowerBound(T, key, items, context, lessThan), - upperBound(T, key, items, context, greaterThan), + upperBound(T, key, items, context, lessThan), }; } test "equalRange" { const S = struct { - fn lower_i32(context: void, key: i32, lhs: i32) bool { + fn lower_u32(context: void, lhs: u32, rhs: u32) bool { _ = context; - return lhs < key; + return lhs < rhs; } - fn upper_i32(context: void, key: i32, rhs: i32) bool { + fn lower_i32(context: void, lhs: i32, rhs: i32) bool { _ = context; - return key >= rhs; + return lhs < rhs; } - fn lower_u32(context: void, key: u32, lhs: u32) bool { + fn lower_f32(context: void, lhs: f32, rhs: f32) 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; + return lhs < 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), + equalRange(i32, @as(i32, 0), &[_]i32{}, {}, S.lower_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), + equalRange(i32, @as(i32, 0), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_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), + equalRange(i32, @as(i32, 2), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_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), + equalRange(i32, @as(i32, 5), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_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), + equalRange(i32, @as(i32, 8), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_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), + equalRange(i32, @as(i32, 64), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_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), + equalRange(i32, @as(i32, 100), &[_]i32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_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), + equalRange(i32, @as(i32, 8), &[_]i32{ 2, 4, 8, 8, 8, 8, 15, 22 }, {}, S.lower_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), + equalRange(u32, @as(u32, 5), &[_]u32{ 2, 4, 8, 16, 32, 64 }, {}, S.lower_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), + equalRange(f32, @as(f32, -33.4), &[_]f32{ -54.2, -26.7, 0.0, 56.55, 100.1, 322.0 }, {}, S.lower_f32), ); }