This commit is contained in:
2025-07-30 13:56:43 +02:00
parent a26404130f
commit b1ec27d62c
5 changed files with 2281 additions and 0 deletions

29
README.md Normal file
View File

@@ -0,0 +1,29 @@
# ZiRBTree
Intrusive [Red-Black Trees](https://en.wikipedia.org/wiki/Red%E2%80%93black_tree) for [Zig](https://ziglang.org/).
There are a generic and a non-generic implementation.
The generic one is generally easier to use as you just need one comparison function and just have to
provide it once. But of course the code is generated for each different specialization.
The non-generic one is not as ergonomic because of the need of `@fieldParentPtr` to access the
containing struct, but allows for cases when a single node is part of multiple trees without long
hierarchy chains, that would be created by the generic one. It's also just compiled one time instead
of once for each different specialization.
## Installation
Just vendor the files and import them as necessary.
## Examples
`main_generic.zig` is an example demonstrating the usage of the generic Red-Black Tree and
`main.zig` shows usage of the non-generic Red-Black Tree. You can also look at the tests in the
implementation files.
You can run these examples using `zig run main_generic.zig` and `zig run main.zig` respectively.
## License
The code is licensed under MIT.

1080
RedBlackTree.zig Normal file

File diff suppressed because it is too large Load Diff

1007
generic_red_black_tree.zig Normal file

File diff suppressed because it is too large Load Diff

91
main.zig Normal file
View File

@@ -0,0 +1,91 @@
const std = @import("std");
const zirb = @import("RedBlackTree.zig");
// Define the data structure that embeds the tree node.
const MyDataNode = struct {
id: u32,
name: []const u8,
node: zirb.Node,
};
// Helper to get the containing MyDataNode from a tree node.
fn getNodeData(n: *const zirb.Node) *const MyDataNode {
return @fieldParentPtr("node", n);
}
// Comparison function for inserting nodes.
fn compareNodes(context: void, lhs: *const zirb.Node, rhs: *const zirb.Node) std.math.Order {
_ = context;
const lhs_data = getNodeData(lhs);
const rhs_data = getNodeData(rhs);
return std.math.order(lhs_data.id, rhs_data.id);
}
// Comparison function for searching by key.
fn compareKeyToNode(context: void, key: comptime_int, node: *const zirb.Node) std.math.Order {
_ = context;
const node_data = getNodeData(node);
return std.math.order(key, node_data.id);
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Initialize the tree.
var tree = zirb.RedBlackTree{};
// Create and insert nodes.
const data_to_insert = [_]struct {
id: u32,
name: []const u8,
}{
.{ .id = 10, .name = "ten" },
.{ .id = 5, .name = "five" },
.{ .id = 15, .name = "fifteen" },
.{ .id = 1, .name = "one" },
.{ .id = 7, .name = "seven" },
};
var nodes = std.ArrayList(*MyDataNode).init(allocator);
defer {
for (nodes.items) |node| {
allocator.destroy(node);
}
nodes.deinit();
}
std.debug.print("Inserting nodes...\n", .{});
for (data_to_insert) |data| {
const node = try allocator.create(MyDataNode);
node.* = .{ .id = data.id, .name = data.name, .node = .{} };
try nodes.append(node);
tree.insert(&node.node, {}, compareNodes);
std.debug.print("\tInserted: id = {d}, name = {s}\n", .{ data.id, data.name });
}
// Iterate over the tree in order and print the data.
std.debug.print("\nIn-order traversal:\n", .{});
var it = tree.inorder();
while (it.next()) |node_ptr| {
const data_node = getNodeData(node_ptr);
std.debug.print("\tid = {d}, name = {s}\n", .{ data_node.id, data_node.name });
}
// Find and remove a node.
std.debug.print("\nRemoving node with id = 10...\n", .{});
const node_to_remove = tree.search(10, {}, compareKeyToNode) orelse {
std.debug.print("Node not found!\n", .{});
return;
};
tree.remove(node_to_remove);
// Iterate again to show the node has been removed.
std.debug.print("\nIn-order traversal after removal:\n", .{});
var it_after_remove = tree.inorder();
while (it_after_remove.next()) |node_ptr| {
const data_node = getNodeData(node_ptr);
std.debug.print("\tid = {d}, name = {s}\n", .{ data_node.id, data_node.name });
}
}

74
main_generic.zig Normal file
View File

@@ -0,0 +1,74 @@
const std = @import("std");
const zirb = @import("generic_red_black_tree.zig");
// Define the data structure to be stored in the tree.
const MyData = struct {
id: u32,
name: []const u8,
};
// Define the comparison function for MyData.
fn compareMyData(context: void, lhs: MyData, rhs: MyData) std.math.Order {
_ = context;
return std.math.order(lhs.id, rhs.id);
}
// Define the tree type using the generic RedBlackTree.
const MyTree = zirb.RedBlackTree(MyData, void, compareMyData);
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Initialize the tree.
var tree = MyTree{ .context = {} };
// Create and insert nodes.
const data_to_insert = [_]MyData{
.{ .id = 10, .name = "ten" },
.{ .id = 5, .name = "five" },
.{ .id = 15, .name = "fifteen" },
.{ .id = 1, .name = "one" },
.{ .id = 7, .name = "seven" },
};
var nodes = std.ArrayList(*MyTree.Node).init(allocator);
defer {
for (nodes.items) |node| {
allocator.destroy(node);
}
nodes.deinit();
}
std.debug.print("Inserting nodes...\n", .{});
for (data_to_insert) |data| {
const node = try allocator.create(MyTree.Node);
node.* = .{ .payload = data };
try nodes.append(node);
tree.insert(node);
std.debug.print("\tInserted: id = {d}, name = {s}\n", .{ data.id, data.name });
}
// Iterate over the tree in order and print the data.
std.debug.print("\nIn-order traversal:\n", .{});
var it = tree.inorder();
while (it.next()) |node| {
std.debug.print("\tid = {d}, name = {s}\n", .{ node.payload.id, node.payload.name });
}
// Find and remove a node.
std.debug.print("\nRemoving node with id = 10...\n", .{});
const node_to_remove = tree.search(.{ .id = 10, .name = "" }) orelse {
std.debug.print("Node not found!\n", .{});
return;
};
tree.remove(node_to_remove);
// Iterate again to show the node has been removed.
std.debug.print("\nIn-order traversal after removal:\n", .{});
var it_after_remove = tree.inorder();
while (it_after_remove.next()) |node| {
std.debug.print("\tid = {d}, name = {s}\n", .{ node.payload.id, node.payload.name });
}
}