Skip to content

Commit 3831cb1

Browse files
committed
Make the API more consistent #2
1 parent 02ffd8a commit 3831cb1

File tree

4 files changed

+49
-67
lines changed

4 files changed

+49
-67
lines changed

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Priorities, in order:
3535

3636
- `src/lib.zig`: Public API entry point. Re-exports `SortedSet`, `RedBlackTreeSet`, `BTreeMap`, `SkipListMap`, `TrieMap`, and `CartesianTreeMap`.
3737
- `src/ordered/sorted_set.zig`: `SortedSet` (insertion-sorted `std.ArrayList` backed by a linear scan for insert and removal).
38-
- `src/ordered/red_black_tree_set.zig`: `RedBlackTreeSet` (self-balancing BST).
38+
- `src/ordered/red_black_tree_set.zig`: `RedBlackTreeSet` (self-balancing BST; takes an explicit three-way comparison function, consistent with the other generic-key containers).
3939
- `src/ordered/btree_map.zig`: `BTreeMap` (cache-friendly B-tree with configurable branching factor).
4040
- `src/ordered/skip_list_map.zig`: `SkipListMap` (probabilistic skip list with a per-instance PRNG).
4141
- `src/ordered/trie_map.zig`: `TrieMap` (prefix tree, specialised for `[]const u8` keys).

benches/b3_red_black_tree_set.zig

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,12 @@ pub fn main() !void {
2020
}
2121
}
2222

23-
const Context = struct {
24-
pub fn lessThan(self: @This(), a: i32, b: i32) bool {
25-
_ = self;
26-
return a < b;
27-
}
28-
};
23+
fn i32Compare(lhs: i32, rhs: i32) std.math.Order {
24+
return std.math.order(lhs, rhs);
25+
}
2926

3027
fn benchmarkInsert(allocator: std.mem.Allocator, size: usize) !void {
31-
var tree = ordered.RedBlackTreeSet(i32, Context).init(allocator, Context{});
28+
var tree = ordered.RedBlackTreeSet(i32, i32Compare).init(allocator);
3229
defer tree.deinit();
3330

3431
var timer = try Timer.start();
@@ -50,7 +47,7 @@ fn benchmarkInsert(allocator: std.mem.Allocator, size: usize) !void {
5047
}
5148

5249
fn benchmarkFind(allocator: std.mem.Allocator, size: usize) !void {
53-
var tree = ordered.RedBlackTreeSet(i32, Context).init(allocator, Context{});
50+
var tree = ordered.RedBlackTreeSet(i32, i32Compare).init(allocator);
5451
defer tree.deinit();
5552

5653
var i: i32 = 0;
@@ -79,7 +76,7 @@ fn benchmarkFind(allocator: std.mem.Allocator, size: usize) !void {
7976
}
8077

8178
fn benchmarkRemove(allocator: std.mem.Allocator, size: usize) !void {
82-
var tree = ordered.RedBlackTreeSet(i32, Context).init(allocator, Context{});
79+
var tree = ordered.RedBlackTreeSet(i32, i32Compare).init(allocator);
8380
defer tree.deinit();
8481

8582
var i: i32 = 0;
@@ -106,7 +103,7 @@ fn benchmarkRemove(allocator: std.mem.Allocator, size: usize) !void {
106103
}
107104

108105
fn benchmarkIterator(allocator: std.mem.Allocator, size: usize) !void {
109-
var tree = ordered.RedBlackTreeSet(i32, Context).init(allocator, Context{});
106+
var tree = ordered.RedBlackTreeSet(i32, i32Compare).init(allocator);
110107
defer tree.deinit();
111108

112109
var i: i32 = 0;

examples/e3_red_black_tree_set.zig

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,17 @@
11
const std = @import("std");
22
const ordered = @import("ordered");
33

4-
// Context object for comparison. This is needed by RedBlackTreeSet
5-
const I32Context = struct {
6-
// This function must be public to be visible from the library code.
7-
pub fn lessThan(_: @This(), a: i32, b: i32) bool {
8-
return a < b;
9-
}
10-
};
4+
// Comparison function for the keys. Returns a `std.math.Order` — the same
5+
// three-way shape used by every other generic-key container in the library.
6+
fn i32Compare(lhs: i32, rhs: i32) std.math.Order {
7+
return std.math.order(lhs, rhs);
8+
}
119

1210
pub fn main() !void {
1311
const allocator = std.heap.page_allocator;
1412

1513
std.debug.print("## RedBlackTreeSet Example (as a Set) ##\n", .{});
16-
var rbt = ordered.RedBlackTreeSet(i32, I32Context).init(allocator, .{});
14+
var rbt = ordered.RedBlackTreeSet(i32, i32Compare).init(allocator);
1715
defer rbt.deinit();
1816

1917
try rbt.put(40);

src/ordered/red_black_tree_set.zig

Lines changed: 35 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -35,22 +35,25 @@ const Allocator = std.mem.Allocator;
3535
const testing = std.testing;
3636
const assert = std.debug.assert;
3737

38-
/// Creates a Red-black tree type for the given data type and comparison context.
38+
/// Creates a Red-black tree type for the given data type and key-comparison function.
39+
///
40+
/// The `compare` function is the same three-way comparator used by every other
41+
/// generic-key container in the library (`BTreeMap`, `SkipListMap`, `SortedSet`,
42+
/// and `CartesianTreeMap`), so tree types compose uniformly.
3943
///
4044
/// ## Parameters
4145
/// - `T`: The data type to store in the tree
42-
/// - `Context`: A type providing a `lessThan(ctx, a, b) bool` method for comparison
46+
/// - `compare`: Three-way comparison function returning `std.math.Order`
4347
///
4448
/// ## Example
4549
/// ```zig
46-
/// const Context = struct {
47-
/// pub fn lessThan(_: @This(), a: i32, b: i32) bool {
48-
/// return a < b;
49-
/// }
50-
/// };
51-
/// var tree = RedBlackTreeSet(i32, Context).init(allocator, .{});
50+
/// fn i32Order(a: i32, b: i32) std.math.Order { return std.math.order(a, b); }
51+
/// var tree = RedBlackTreeSet(i32, i32Order).init(allocator);
5252
/// ```
53-
pub fn RedBlackTreeSet(comptime T: type, comptime Context: type) type {
53+
pub fn RedBlackTreeSet(
54+
comptime T: type,
55+
comptime compare: fn (lhs: T, rhs: T) std.math.Order,
56+
) type {
5457
return struct {
5558
const Self = @This();
5659

@@ -74,19 +77,16 @@ pub fn RedBlackTreeSet(comptime T: type, comptime Context: type) type {
7477

7578
root: ?*Node,
7679
allocator: Allocator,
77-
context: Context,
7880
size: usize,
7981

8082
/// Creates a new empty Red-black tree.
8183
///
8284
/// ## Parameters
8385
/// - `allocator`: Memory allocator for node allocation
84-
/// - `context`: Comparison context instance
85-
pub fn init(allocator: Allocator, context: Context) Self {
86+
pub fn init(allocator: Allocator) Self {
8687
return Self{
8788
.root = null,
8889
.allocator = allocator,
89-
.context = context,
9090
.size = 0,
9191
};
9292
}
@@ -124,7 +124,7 @@ pub fn RedBlackTreeSet(comptime T: type, comptime Context: type) type {
124124

125125
/// Inserts or updates a value in the tree.
126126
///
127-
/// If the value already exists (as determined by the context's lessThan method),
127+
/// If the value already exists (as determined by the `compare` function),
128128
/// it will be updated. Otherwise, a new node is created.
129129
///
130130
/// Time complexity: O(log n)
@@ -160,15 +160,15 @@ pub fn RedBlackTreeSet(comptime T: type, comptime Context: type) type {
160160

161161
while (current != null) {
162162
parent = current;
163-
if (self.context.lessThan(data, current.?.data)) {
163+
if (compare(data, current.?.data) == .lt) {
164164
current = current.?.left;
165165
} else {
166166
current = current.?.right;
167167
}
168168
}
169169

170170
new_node.parent = parent;
171-
if (self.context.lessThan(data, parent.?.data)) {
171+
if (compare(data, parent.?.data) == .lt) {
172172
parent.?.left = new_node;
173173
} else {
174174
parent.?.right = new_node;
@@ -442,12 +442,10 @@ pub fn RedBlackTreeSet(comptime T: type, comptime Context: type) type {
442442
var current = self.root;
443443

444444
while (current) |node| {
445-
if (self.context.lessThan(data, node.data)) {
446-
current = node.left;
447-
} else if (self.context.lessThan(node.data, data)) {
448-
current = node.right;
449-
} else {
450-
return node;
445+
switch (compare(data, node.data)) {
446+
.lt => current = node.left,
447+
.gt => current = node.right,
448+
.eq => return node,
451449
}
452450
}
453451

@@ -536,24 +534,13 @@ pub fn RedBlackTreeSet(comptime T: type, comptime Context: type) type {
536534
};
537535
}
538536

539-
/// Default context for comparable types
540-
pub fn DefaultContext(comptime T: type) type {
541-
return struct {
542-
pub fn lessThan(self: @This(), a: T, b: T) bool {
543-
_ = self;
544-
return a < b;
545-
}
546-
};
547-
}
548-
549-
// Convenience type aliases
550-
pub fn RedBlackTreeSetManaged(comptime T: type) type {
551-
return RedBlackTreeSet(T, DefaultContext(T));
537+
fn i32Compare(lhs: i32, rhs: i32) std.math.Order {
538+
return std.math.order(lhs, rhs);
552539
}
553540

554541
test "RedBlackTreeSet: basic operations" {
555542
const allocator = std.testing.allocator;
556-
var tree = RedBlackTreeSet(i32, DefaultContext(i32)).init(allocator, .{});
543+
var tree = RedBlackTreeSet(i32, i32Compare).init(allocator);
557544
defer tree.deinit();
558545

559546
try tree.put(10);
@@ -568,7 +555,7 @@ test "RedBlackTreeSet: basic operations" {
568555

569556
test "RedBlackTreeSet: empty tree operations" {
570557
const allocator = std.testing.allocator;
571-
var tree = RedBlackTreeSet(i32, DefaultContext(i32)).init(allocator, .{});
558+
var tree = RedBlackTreeSet(i32, i32Compare).init(allocator);
572559
defer tree.deinit();
573560

574561
try std.testing.expect(!tree.contains(42));
@@ -578,7 +565,7 @@ test "RedBlackTreeSet: empty tree operations" {
578565

579566
test "RedBlackTreeSet: single element" {
580567
const allocator = std.testing.allocator;
581-
var tree = RedBlackTreeSet(i32, DefaultContext(i32)).init(allocator, .{});
568+
var tree = RedBlackTreeSet(i32, i32Compare).init(allocator);
582569
defer tree.deinit();
583570

584571
try tree.put(42);
@@ -594,7 +581,7 @@ test "RedBlackTreeSet: single element" {
594581

595582
test "RedBlackTreeSet: duplicate insertions" {
596583
const allocator = std.testing.allocator;
597-
var tree = RedBlackTreeSet(i32, DefaultContext(i32)).init(allocator, .{});
584+
var tree = RedBlackTreeSet(i32, i32Compare).init(allocator);
598585
defer tree.deinit();
599586

600587
try tree.put(10);
@@ -607,7 +594,7 @@ test "RedBlackTreeSet: duplicate insertions" {
607594

608595
test "RedBlackTreeSet: sequential insertion" {
609596
const allocator = std.testing.allocator;
610-
var tree = RedBlackTreeSet(i32, DefaultContext(i32)).init(allocator, .{});
597+
var tree = RedBlackTreeSet(i32, i32Compare).init(allocator);
611598
defer tree.deinit();
612599

613600
var i: i32 = 0;
@@ -626,7 +613,7 @@ test "RedBlackTreeSet: sequential insertion" {
626613

627614
test "RedBlackTreeSet: reverse insertion" {
628615
const allocator = std.testing.allocator;
629-
var tree = RedBlackTreeSet(i32, DefaultContext(i32)).init(allocator, .{});
616+
var tree = RedBlackTreeSet(i32, i32Compare).init(allocator);
630617
defer tree.deinit();
631618

632619
var i: i32 = 50;
@@ -640,7 +627,7 @@ test "RedBlackTreeSet: reverse insertion" {
640627

641628
test "RedBlackTreeSet: remove from middle" {
642629
const allocator = std.testing.allocator;
643-
var tree = RedBlackTreeSet(i32, DefaultContext(i32)).init(allocator, .{});
630+
var tree = RedBlackTreeSet(i32, i32Compare).init(allocator);
644631
defer tree.deinit();
645632

646633
try tree.put(10);
@@ -659,7 +646,7 @@ test "RedBlackTreeSet: remove from middle" {
659646

660647
test "RedBlackTreeSet: remove root" {
661648
const allocator = std.testing.allocator;
662-
var tree = RedBlackTreeSet(i32, DefaultContext(i32)).init(allocator, .{});
649+
var tree = RedBlackTreeSet(i32, i32Compare).init(allocator);
663650
defer tree.deinit();
664651

665652
try tree.put(10);
@@ -674,7 +661,7 @@ test "RedBlackTreeSet: remove root" {
674661

675662
test "RedBlackTreeSet: minimum and maximum" {
676663
const allocator = std.testing.allocator;
677-
var tree = RedBlackTreeSet(i32, DefaultContext(i32)).init(allocator, .{});
664+
var tree = RedBlackTreeSet(i32, i32Compare).init(allocator);
678665
defer tree.deinit();
679666

680667
try tree.put(10);
@@ -694,7 +681,7 @@ test "RedBlackTreeSet: minimum and maximum" {
694681

695682
test "RedBlackTreeSet: iterator empty tree" {
696683
const allocator = std.testing.allocator;
697-
var tree = RedBlackTreeSet(i32, DefaultContext(i32)).init(allocator, .{});
684+
var tree = RedBlackTreeSet(i32, i32Compare).init(allocator);
698685
defer tree.deinit();
699686

700687
var iter = try tree.iterator();
@@ -706,7 +693,7 @@ test "RedBlackTreeSet: iterator empty tree" {
706693

707694
test "RedBlackTreeSet: clear" {
708695
const allocator = std.testing.allocator;
709-
var tree = RedBlackTreeSet(i32, DefaultContext(i32)).init(allocator, .{});
696+
var tree = RedBlackTreeSet(i32, i32Compare).init(allocator);
710697
defer tree.deinit();
711698

712699
try tree.put(1);
@@ -720,7 +707,7 @@ test "RedBlackTreeSet: clear" {
720707

721708
test "RedBlackTreeSet: negative numbers" {
722709
const allocator = std.testing.allocator;
723-
var tree = RedBlackTreeSet(i32, DefaultContext(i32)).init(allocator, .{});
710+
var tree = RedBlackTreeSet(i32, i32Compare).init(allocator);
724711
defer tree.deinit();
725712

726713
try tree.put(-10);
@@ -735,7 +722,7 @@ test "RedBlackTreeSet: negative numbers" {
735722

736723
test "RedBlackTreeSet: get returns correct node" {
737724
const allocator = std.testing.allocator;
738-
var tree = RedBlackTreeSet(i32, DefaultContext(i32)).init(allocator, .{});
725+
var tree = RedBlackTreeSet(i32, i32Compare).init(allocator);
739726
defer tree.deinit();
740727

741728
try tree.put(10);

0 commit comments

Comments
 (0)