feat(rtl): add bst and tagged pointer

This commit is contained in:
Loïc Grégoire
2026-03-28 13:56:25 -04:00
parent 263e971b1d
commit 37be346581
3 changed files with 155 additions and 0 deletions
+101
View File
@@ -0,0 +1,101 @@
const rtl = @import("rtl");
const std = @import("std");
pub const Node = struct {
left: *Node,
right: *Node,
parent: rtl.TaggedPtr(Node),
};
/// Base struct for binary search trees.
/// This should be used as a field in other types that are based on binary search trees, such as red-black trees or AVL trees.
pub fn BST(comptime cmp: fn (*Node, *Node) std.math.Order) type {
return struct {
const Self = @This();
root: *Node = undefined,
nil: Node = undefined,
/// Initialize the BST. This must be called before using the tree.
pub fn init(self: *Self) void {
self.nil.left = &self.nil;
self.nil.right = &self.nil;
self.nil.parent = &self.nil;
self.root = &self.nil;
}
/// Return whether or not the given node is the sentinel nil node.
pub fn is_nil(self: *Self, node: *Node) bool {
return node == &self.nil;
}
/// Search for a node in the BST that matches `elem`, and return a pointer to it if found.
pub fn search(self: *Self, elem: *Node) ?*Node {
var node = self.root;
while (!self.is_nil(node)) {
const order = cmp(elem, node);
if (order == .eq) {
return node;
} else if (order == .lt) {
node = node.left;
} else {
node = node.right;
}
}
return null;
}
/// Return a pointer to the minimum node in the subtree rooted at `node`.
pub fn minimum(self: *Self, node: *Node) *Node {
var current = node;
while (!self.is_nil(current.left)) {
current = current.left;
}
return current;
}
/// Return a pointer to the maximum node in the subtree rooted at `node`.
pub fn maximum(self: *Self, node: *Node) *Node {
var current = node;
while (!self.is_nil(current.right)) {
current = current.right;
}
return current;
}
/// Return a pointer to the successor of `node`, or nil if `node` is the maximum element in the tree.
pub fn successor(self: *Self, node: *Node) *Node {
if (!self.is_nil(node.right)) {
// Minimum of right subtree
return self.minimum(node.right);
}
// Go up and find it.
var current = node;
var parent = current.parent.get_ptr();
while (!self.is_nil(parent) and current == parent.right) {
current = parent;
parent = parent.parent.get_ptr();
}
if (parent == self.root) {
return &self.nil;
}
return parent;
}
/// Transplant the subtree rooted at `u` with the subtree rooted at `v`. This is used as a helper function.
pub fn transplant(self: *Self, u: *Node, v: *Node) void {
const u_parent = u.parent.get_ptr();
if (self.is_nil(u_parent)) {
self.root = v;
} else if (u == u_parent.left) {
u_parent.left = v;
} else {
u_parent.right = v;
}
v.parent.set_ptr(u_parent);
}
};
}
+2
View File
@@ -6,6 +6,8 @@ pub const barrier = @import("barrier.zig");
pub const cmdline = @import("cmdline.zig");
pub const pairing_heap = @import("pairing_heap.zig");
pub const PairingHeap = pairing_heap.PairingHeap;
pub const TaggedPtr = @import("tagged_ptr.zig").TaggedPtr;
pub const bst = @import("bst.zig");
test {
std.testing.refAllDecls(@This());
+52
View File
@@ -0,0 +1,52 @@
/// Struct used to wrap a pointer with a 2-bit tag in the lower bits.
/// This assumes that the pointer is at least 4-byte aligned.
pub fn TaggedPtr(comptime T: type) type {
return struct {
value: usize,
const Self = @This();
const mask: usize = 0x3;
pub fn init(ptr: *T, tag: u2) Self {
return Self{ .value = (@intFromPtr(ptr) & ~mask) | (@as(usize, tag) & mask) };
}
/// Return the pointer with the tag bits masked out.
pub fn get_ptr(self: *Self) *T {
return @ptrFromInt(self.value & ~mask);
}
/// Set the tag bits to `tag`, while preserving the pointer.
pub fn set_tag(self: *Self, tag: u2) void {
self.value = (self.value & ~mask) | (@as(usize, tag) & mask);
}
/// Return the tag bits.
pub fn get_tag(self: *Self) u2 {
return @as(u2, @truncate(self.value & mask));
}
/// Set the pointer to `ptr`, while preserving the tag bits.
pub fn set_ptr(self: *Self, ptr: *T) void {
self.value = (@intFromPtr(ptr) & ~mask) | (self.value & mask);
}
};
}
const std = @import("std");
test TaggedPtr {
var x: u32 = 0;
var tagged = TaggedPtr(u32).init(&x, 2);
try std.testing.expectEqual(&x, tagged.get_ptr());
try std.testing.expectEqual(2, tagged.get_tag());
tagged.set_tag(1);
try std.testing.expectEqual(1, tagged.get_tag());
var y: u32 = 0;
tagged.set_ptr(&y);
try std.testing.expectEqual(&y, tagged.get_ptr());
}