Hi.
I’ve implemented very simple semi-generic trie.
It can hold arbitrary data, but alphabet size is fixed (256), hence semi-generic.
Currently I have only insert and search, deletion is not implemented yet.
Also there is some (almost useless) traversal over the entire trie.
Here it is
trie.zig
const std = @import("std");
const Allocator = std.mem.Allocator;
pub fn Trie(comptime T: type) type {
return struct {
pub const Edge = struct {
sym: u8,
kid: *Node,
};
pub const Node = struct {
data: T,
edge: []Edge,
};
const Self = @This();
root: Node,
allo: Allocator,
pub fn getNode(self: *Self, key: []const u8) ?*Node {
var n: *Node = &self.root;
for (key) |s| {
const i = __edgeIndex(n, s) orelse return null;
n = n.edge[i].kid;
}
return n;
}
pub fn addNode(self: *Self, key: []const u8) !*Node {
var n = &self.root;
for (key) |s| {
var i: ?u8 = null;
i = __edgeIndex(n, s);
if (i == null) {
var new_node = try self.allo.create(Node);
new_node.edge.len = 0;
new_node.data = undefined;
i = @intCast(n.edge.len);
n.edge = try self.allo.realloc(n.edge, n.edge.len + 1);
n.edge[i.?] = .{.sym = s, .kid = new_node};
}
n = n.edge[i.?].kid;
}
return n;
}
pub fn walkOver(self: *Self, node: *Node) void {
// depth first traversal
std.debug.print(" at {*} ... {} kids\n", .{node, node.edge.len});
for (node.edge) |e| {
walkOver(self, e.kid);
}
// breadth first traversal (?)
// std.debug.print(" at {*} ... {} kids\n", .{node, node.edge.len});
}
fn __edgeIndex(node: *Node, s: u8) ?u8 {
var index: ?u8 = null;
for (node.edge, 0..) |e, i| {
if (e.sym == s) {
index = @intCast(i);
break;
}
}
return index;
}
};
}
main.zig
const std = @import("std");
const Allocator = std.mem.Allocator;
const Data = struct {
a: u32,
b: u32,
};
const SomeTrie = @import("trie.zig").Trie(Data);
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
const d: Data = .{.a = 5, .b = 7};
var t: SomeTrie = .{
.root = .{
.data = d,
.edge = undefined,
},
.allo = allocator,
};
std.debug.print("root {*} a = {} b = {}\n", .{&t.root, t.root.data.a, t.root.data.b});
var n = t.getNode("123");
std.debug.print("{*}\n", .{n});
n = try t.addNode("123");
n.?.data = d;
n = t.getNode("1");
std.debug.print("{*} a = {X} b = {X}\n", .{n, n.?.data.a, n.?.data.b});
n = t.getNode("12");
std.debug.print("{*}\n", .{n});
n = t.getNode("123");
std.debug.print("{*} a = {} b = {}\n", .{n, n.?.data.a, n.?.data.b});
n = t.getNode("1234");
std.debug.print("{*}\n", .{n});
n = try t.addNode("124");
n.?.data = .{.a = 333, .b = 444};
n = t.getNode("124");
std.debug.print("{*} a = {} b = {}\n", .{n, n.?.data.a, n.?.data.b});
_ = try t.addNode("abc");
n = t.getNode("a");
std.debug.print("{*}\n", .{n});
n = t.getNode("ab");
std.debug.print("{*}\n", .{n});
n = t.getNode("abc");
std.debug.print("{*}\n", .{n});
n = t.getNode("abcd");
std.debug.print("{*}\n", .{n});
n = try t.addNode(&[_]u8{0,1,2});
n.?.data.a = 11;
n.?.data.b = 22;
n = t.getNode(&[_]u8{0,1,2});
std.debug.print("{*} a = {} b = {}\n", .{n, n.?.data.a, n.?.data.b});
t.walkOver(&t.root);
}
It works as expected, but I would like some comments on the code.
What is done good?
What is done bad/ugly?
Is it “ziggy” enough?
Thanks.