Files
zing/src/util/btree.zig
T
2025-03-24 09:34:59 +02:00

369 lines
11 KiB
Zig

const std = @import("std");
const Allocator = std.mem.Allocator;
pub const Order = std.math.Order;
pub fn CompareFn(comptime N: type) type {
return fn (*const N, *const N) Order;
}
pub fn SearchFn(comptime N: type, comptime C: type) type {
return fn (*const N, C) Order;
}
pub fn BTree(comptime N: type, comptime compare_fn: CompareFn(N), comptime deinit_fn: ?fn (*N) void) type {
return struct {
gpa: Allocator,
root: ?*Node = null,
pub const Error = error{ already_exists, does_not_exist } || Allocator.Error;
pub fn WalkFn(comptime C: type) type {
return fn (*const Node, C) void;
}
pub const Iterator = struct {
current: ?*Node,
pub fn next(self: *Iterator) ?*const Node {
while (self.current) |n| {
const v = n;
if (n.right) |r| {
// Emit
self.current = Node.leftmost(r);
} else {
var nn = n;
while (nn.parent) |p| {
if (nn == p.right) {
nn = p;
} else {
break;
}
}
self.current = nn.parent;
}
return v;
}
return null;
}
};
pub const Node = struct {
key: N,
parent: ?*Node = null,
left: ?*Node = null,
right: ?*Node = null,
fn init(a: Allocator, key: N) Error!*Node {
const node = try a.create(Node);
node.* = .{
.key = key,
};
return node;
}
fn deinit(node: ?*Node, a: Allocator) void {
if (node) |n| {
if (comptime deinit_fn) |f| {
f(&n.key);
}
Node.deinit(n.left, a);
Node.deinit(n.right, a);
// Free node itself
a.destroy(n);
}
}
fn insert(node: ?*Node, a: Allocator, key: N) Error!struct { *Node, *Node } {
if (node) |n| {
const ord = compare_fn(&n.key, &key);
var inserted: *Node = undefined;
switch (ord) {
.lt => {
const child, inserted = try Node.insert(n.right, a, key);
child.parent = n;
n.right = child;
},
.gt => {
const child, inserted = try Node.insert(n.left, a, key);
child.parent = n;
n.left = child;
},
.eq => return error.already_exists,
}
return .{ n, inserted };
} else {
const n = try Node.init(a, key);
return .{ n, n };
}
}
fn remove_node(node: *Node, a: Allocator, destroy: bool) ?*Node {
if (node.left == null) {
// Only right/none
const tmp = node.right;
if (tmp) |t| {
t.parent = node.parent;
}
// Destroy the node
if (comptime deinit_fn) |f| {
if (destroy) {
f(&node.key);
}
}
a.destroy(node);
return tmp;
}
if (node.right == null) {
// Only left/none
const tmp = node.left;
if (tmp) |t| {
t.parent = node.parent;
}
// Destroy the node
if (comptime deinit_fn) |f| {
if (destroy) {
f(&node.key);
}
}
a.destroy(node);
return tmp;
}
// Both
var successor = node.right;
while (successor) |succ| {
if (succ.left) |l| {
successor = l;
} else {
break;
}
}
if (successor) |succ| {
node.key = succ.key;
node.right = Node.remove(node.right, a, succ.key) catch unreachable;
}
return node;
}
fn remove(node: ?*Node, a: Allocator, key: N) Error!?*Node {
if (node) |n| {
const ord = compare_fn(&n.key, &key);
switch (ord) {
.lt => n.right = try Node.remove(n.right, a, key),
.gt => n.left = try Node.remove(n.left, a, key),
.eq => return Node.remove_node(n, a, true),
}
return node;
} else {
return error.does_not_exist;
}
}
fn walk(node: ?*Node, ctx: anytype, walk_fn: WalkFn(@TypeOf(ctx))) void {
if (node) |n| {
Node.walk(n.left, ctx, walk_fn);
walk_fn(n, ctx);
Node.walk(n.right, ctx, walk_fn);
}
}
fn leftmost(node: ?*Node) ?*Node {
var n = node;
while (n) |nn| {
if (nn.left == null) {
break;
}
n = nn.left;
}
return n;
}
};
pub fn init(a: std.mem.Allocator) @This() {
return .{ .gpa = a };
}
pub fn deinit(self: *@This()) void {
Node.deinit(self.root, self.gpa);
}
pub fn iterator(self: *@This()) Iterator {
return .{ .current = Node.leftmost(self.root) };
}
pub fn insert(self: *@This(), key: N) Error!*Node {
self.root, const inserted = try Node.insert(self.root, self.gpa, key);
return inserted;
}
pub fn remove(self: *@This(), key: N) Error!void {
self.root = try Node.remove(self.root, self.gpa, key);
}
pub fn remove_node(self: *@This(), node: *Node, destroy: bool) Error!void {
if (node.parent) |p| {
// Non-root node
const np = Node.remove_node(node, self.gpa, destroy);
if (np) |npp| {
npp.parent = p;
}
if (node == p.right) {
p.right = np;
} else {
p.left = np;
}
} else {
// Root node
const np = Node.remove_node(node, self.gpa, destroy);
if (np) |npp| {
npp.parent = null;
}
self.root = np;
}
}
pub fn lookup(self: *const @This(), key: N) ?*Node {
const search_fn = struct {
fn call(n: *const N, cx: N) Order {
return compare_fn(n, &cx);
}
}.call;
return self.search(key, search_fn);
}
pub fn search(
self: *const @This(),
ctx: anytype,
search_fn: SearchFn(N, @TypeOf(ctx)),
) ?*Node {
var node = self.root;
while (node) |n| {
const ord = search_fn(&n.key, ctx);
switch (ord) {
.gt => node = n.left,
.eq => return n,
.lt => node = n.right,
}
}
return null;
}
pub fn walk(self: *@This(), ctx: anytype, walk_fn: WalkFn(@TypeOf(ctx))) void {
Node.walk(self.root, ctx, walk_fn);
}
};
}
test "BTree insertion/removal" {
const int_compare_fn = struct {
fn call(a: *const u32, b: *const u32) Order {
if (a.* > b.*) {
return .gt;
} else if (a.* == b.*) {
return .eq;
} else {
return .lt;
}
}
}.call;
const Tree = BTree(u32, int_compare_fn, null);
var tree = Tree.init(std.testing.allocator);
defer tree.deinit();
for (50..100) |i| {
_ = try tree.insert(@truncate(i));
}
for (1..50) |i| {
_ = try tree.insert(@truncate(i));
}
for (1..100) |i| {
const k = @as(u32, @truncate(i));
try std.testing.expectEqual(k, tree.lookup(k).?.key);
}
for (1..100) |i| {
const k = 100 - @as(u32, @truncate(i));
if (i % 2 == 0) {
try tree.remove(k);
}
}
for (1..100) |i| {
const k = @as(u32, @truncate(i));
if (i % 2 == 0) {
try std.testing.expectEqual(null, tree.lookup(k));
} else {
try std.testing.expectEqual(k, tree.lookup(k).?.key);
}
}
}
test "BTree removal by node" {
const int_compare_fn = struct {
fn call(a: *const u32, b: *const u32) Order {
if (a.* > b.*) {
return .gt;
} else if (a.* == b.*) {
return .eq;
} else {
return .lt;
}
}
}.call;
const Tree = BTree(u32, int_compare_fn, null);
var tree = Tree.init(std.testing.allocator);
defer tree.deinit();
_ = try tree.insert(10);
_ = try tree.insert(11);
_ = try tree.insert(12);
{
const n = tree.lookup(10).?;
try tree.remove_node(n, true);
}
try std.testing.expectEqual(null, tree.lookup(10));
try std.testing.expectEqual(12, tree.lookup(12).?.key);
try std.testing.expectEqual(11, tree.lookup(11).?.key);
}
test "BTree iterator" {
const int_compare_fn = struct {
fn call(a: *const u32, b: *const u32) Order {
if (a.* > b.*) {
return .gt;
} else if (a.* == b.*) {
return .eq;
} else {
return .lt;
}
}
}.call;
const Tree = BTree(u32, int_compare_fn, null);
var tree = Tree.init(std.testing.allocator);
defer tree.deinit();
for (50..100) |i| {
_ = try tree.insert(@truncate(i));
}
for (1..50) |i| {
_ = try tree.insert(@truncate(i));
}
var it = tree.iterator();
for (1..100) |i| {
const n = it.next().?;
try std.testing.expectEqual(i, n.key);
}
try std.testing.expectEqual(null, it.next());
}