commit b088e7ba553d0fc9693f9829b8ff8ff25b96861e
Author: Janis <janis@nirgendwo.xyz>
Date:   Fri Mar 17 17:41:30 2023 +0100

    initial commit

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d864d9e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/zig-cache/
+/zig-out/
diff --git a/build.zig b/build.zig
new file mode 100644
index 0000000..d484d0e
--- /dev/null
+++ b/build.zig
@@ -0,0 +1,71 @@
+const std = @import("std");
+
+// Although this function looks imperative, note that its job is to
+// declaratively construct a build graph that will be executed by an external
+// runner.
+pub fn build(b: *std.Build) void {
+    // Standard target options allows the person running `zig build` to choose
+    // what target to build for. Here we do not override the defaults, which
+    // means any target is allowed, and the default is native. Other options
+    // for restricting supported target set are available.
+    const target = b.standardTargetOptions(.{});
+
+    // Standard optimization options allow the person running `zig build` to select
+    // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
+    // set a preferred release mode, allowing the user to decide how to optimize.
+    const optimize = b.standardOptimizeOption(.{});
+
+    const exe = b.addExecutable(.{
+        .name = "datastructures",
+        // In this case the main source file is merely a path, however, in more
+        // complicated build scripts, this could be a generated file.
+        .root_source_file = .{ .path = "src/main.zig" },
+        .target = target,
+        .optimize = optimize,
+    });
+
+    exe.linkLibC();
+
+    // This declares intent for the executable to be installed into the
+    // standard location when the user invokes the "install" step (the default
+    // step when running `zig build`).
+    exe.install();
+
+    // This *creates* a RunStep in the build graph, to be executed when another
+    // step is evaluated that depends on it. The next line below will establish
+    // such a dependency.
+    const run_cmd = exe.run();
+
+    // By making the run step depend on the install step, it will be run from the
+    // installation directory rather than directly from within the cache directory.
+    // This is not necessary, however, if the application depends on other installed
+    // files, this ensures they will be present and in the expected location.
+    run_cmd.step.dependOn(b.getInstallStep());
+
+    // This allows the user to pass arguments to the application in the build
+    // command itself, like this: `zig build run -- arg1 arg2 etc`
+    if (b.args) |args| {
+        run_cmd.addArgs(args);
+    }
+
+    // This creates a build step. It will be visible in the `zig build --help` menu,
+    // and can be selected like this: `zig build run`
+    // This will evaluate the `run` step rather than the default, which is "install".
+    const run_step = b.step("run", "Run the app");
+    run_step.dependOn(&run_cmd.step);
+
+    // Creates a step for unit testing.
+    const exe_tests = b.addTest(.{
+        .root_source_file = .{ .path = "src/main.zig" },
+        .target = target,
+        .optimize = optimize,
+    });
+
+    exe_tests.linkLibC();
+
+    // Similar to creating the run step earlier, this exposes a `test` step to
+    // the `zig build --help` menu, providing a way for the user to request
+    // running the unit tests.
+    const test_step = b.step("test", "Run unit tests");
+    test_step.dependOn(&exe_tests.step);
+}
diff --git a/src/main.zig b/src/main.zig
new file mode 100644
index 0000000..3065525
--- /dev/null
+++ b/src/main.zig
@@ -0,0 +1,306 @@
+const std = @import("std");
+
+const BTree = struct {
+    const Self = @This();
+
+    const B: usize = 3;
+    const CAPACITY: usize = 2 * B - 1;
+    const NUM_EDGES: usize = 2 * B;
+
+    ally: std.mem.Allocator = std.heap.c_allocator,
+    root: ?NodeOrLeaf,
+
+    fn create(ally: std.mem.Allocator) Self {
+        return Self{
+            .ally = ally,
+            .root = null,
+        };
+    }
+
+    fn insert(self: *Self, value: u32) !void {
+        if (self.root) |*root| {
+            switch (root.*) {
+                .internal => |node| {
+                    std.debug.print("can't insert values into {?} yet :|\n", .{node});
+                },
+                .leaf => |leaf| {
+                    try leaf.insert_value(value);
+                },
+            }
+        } else {
+            var leaf: *Leaf = try self.ally.create(Leaf);
+            errdefer self.ally.destroy(leaf);
+            leaf.init(self.ally);
+            try leaf.insert_value(value);
+            self.root = NodeOrLeaf{ .leaf = leaf };
+        }
+    }
+
+    fn destroy(self: *Self) void {
+        if (self.root) |*root| {
+            root.destroy();
+        }
+    }
+
+    const NodeOrLeafTag = enum {
+        internal,
+        leaf,
+    };
+
+    const NodeOrLeaf = union(NodeOrLeafTag) {
+        internal: *Node,
+        leaf: *Leaf,
+
+        fn destroy(self: *NodeOrLeaf) void {
+            self.as_leaf().destroy();
+        }
+
+        fn as_leaf(self: *NodeOrLeaf) *Leaf {
+            switch (self.*) {
+                .internal => |node| {
+                    return node.as_leaf();
+                },
+                .leaf => |leaf| {
+                    return leaf;
+                },
+            }
+        }
+
+        fn from_leaf(leaf: *Leaf) NodeOrLeaf {
+            if (leaf.level == 0) {
+                return .{ .leaf = leaf };
+            } else {
+                return .{ .node = @ptrCast(Node, leaf) };
+            }
+        }
+    };
+
+    const Node = struct {
+        leaf: Leaf,
+
+        edges: [NUM_EDGES]?NodeOrLeaf = undefined,
+
+        fn create(ally: std.mem.Allocator) !Leaf {
+            var node = try ally.create(Node);
+            node.init(ally);
+
+            return node;
+        }
+
+        fn init(self: *Leaf, ally: std.mem.Allocator) void {
+            self.* = Node{ .leaf = Leaf{ .ally = ally } };
+        }
+
+        fn as_leaf(self: *Node) *Leaf {
+            return &self.leaf;
+        }
+
+        fn insert_node(self: *Node, child: NodeOrLeaf) void {
+            const self_leaf = self.as_leaf();
+            const ls = child.as_leaf().get_values()[0];
+
+            var idx: u16 = 0;
+            for (self_leaf.get_values(), 0..) |v, i| {
+                idx = @intCast(u16, i);
+                if (v > ls) {
+                    break;
+                }
+            }
+
+            if (self.get_edges()[idx]) |edge| {
+                std.debug.print("edge already present?: {?}", .{edge});
+            } else {
+                child.as_leaf().parent = .{ .parent = self, .idx = idx };
+                self.get_edges()[idx] = child;
+            }
+        }
+
+        fn get_edges(self: *Node) []?NodeOrLeaf {
+            const len = self.leaf.len;
+            return self.edges[0..len];
+        }
+    };
+
+    const ParentPtr = struct {
+        parent: *Node,
+        idx: u16,
+    };
+
+    const Leaf = struct {
+        ally: std.mem.Allocator,
+        level: usize = 0,
+        parent: ?ParentPtr = null,
+
+        len: u16 = 0,
+        values: [CAPACITY]u32 = undefined,
+
+        fn create(ally: std.mem.Allocator) !*Leaf {
+            var leaf = try ally.create(Leaf);
+            leaf.init(ally);
+
+            return leaf;
+        }
+
+        fn init(self: *Leaf, ally: std.mem.Allocator) void {
+            self.* = Leaf{ .ally = ally };
+        }
+
+        fn destroy(self: *Leaf) void {
+            self.ally.destroy(self);
+        }
+
+        fn push_value(self: *Leaf, value: u32) void {
+            std.debug.assert(self.len < CAPACITY);
+            var tmp = value;
+            for (self.get_values()) |*val| {
+                if (val.* < value) {
+                    continue;
+                }
+                const t = val.*;
+                val.* = tmp;
+                tmp = t;
+            }
+            self.values[self.len] = tmp;
+            self.len = self.len + 1;
+        }
+
+        const SplitResult = struct {
+            // attached
+            left: *Leaf,
+            // lose value, previously attacked, must be inserted
+            middle: u32,
+            // free floating leaf, must be attached
+            right: *Leaf,
+        };
+
+        fn split_at(self: *Leaf, value: u32) !SplitResult {
+            var idx: u16 = 0;
+            for (self.get_values(), 0..) |v, i| {
+                idx = @intCast(u16, i);
+                if (v > value) {
+                    break;
+                }
+            }
+
+            std.debug.assert(idx > 0 and idx < CAPACITY - 1);
+
+            var new = try Leaf.create(self.ally);
+            new.level = self.level;
+            var middle: u32 = undefined;
+
+            // take from right half
+            if (idx > B) {
+                new.len = self.len - (idx + 1);
+                std.mem.copy(u32, &new.values, self.values[(idx + 1)..self.len]);
+
+                middle = self.values[idx];
+
+                self.len = idx;
+                self.push_value(value);
+            } else {
+                // take from left half
+                new.len = self.len - (idx);
+                std.mem.copy(u32, &new.values, self.values[idx..self.len]);
+
+                new.push_value(value);
+
+                middle = self.values[idx - 1];
+
+                self.len = idx - 1;
+            }
+
+            return .{ .left = self, .middle = middle, .right = new };
+        }
+
+        fn insert_value(self: *Leaf, value: u32) !void {
+            if (self.len < CAPACITY) {
+                self.push_value(value);
+            } else {
+                return error.LeafAtCapacity;
+            }
+        }
+
+        fn get_values(self: *Leaf) []u32 {
+            const len = self.len;
+            return self.values[0..len];
+        }
+    };
+};
+
+pub fn main() !void {
+    // Prints to stderr (it's a shortcut based on `std.io.getStdErr()`)
+    std.debug.print("All your {s} are belong to us.\n", .{"codebase"});
+
+    // stdout is for the actual output of your application, for example if you
+    // are implementing gzip, then only the compressed bytes should be sent to
+    // stdout, not any debugging messages.
+    const stdout_file = std.io.getStdOut().writer();
+    var bw = std.io.bufferedWriter(stdout_file);
+    const stdout = bw.writer();
+
+    try stdout.print("Run `zig build test` to run the tests.\n", .{});
+
+    try bw.flush(); // don't forget to flush!
+}
+
+test "btree leaf" {
+    std.testing.refAllDeclsRecursive(BTree);
+    std.testing.refAllDeclsRecursive(BTree.Leaf);
+
+    var leaf = BTree.Leaf{ .ally = std.testing.allocator, .parent = null, .len = 2, .values = [_]u32{ 5, 6, undefined, undefined, undefined } };
+    const values = leaf.get_values();
+
+    std.debug.print("{?}\n", .{leaf});
+    std.debug.print("{any}\n", .{values});
+}
+
+fn printValues(leaf: *BTree.Leaf) void {
+    const values = leaf.get_values();
+    std.debug.print("{any}\n", .{values});
+}
+
+test "leaf split" {
+    std.debug.print("testing splitting\n", .{});
+
+    var tree = BTree.create(std.testing.allocator);
+    defer tree.destroy();
+    try tree.insert(2);
+    try tree.insert(4);
+    try tree.insert(6);
+    try tree.insert(3);
+    try tree.insert(7);
+    std.debug.print("before split:", .{});
+    printValues(tree.root.?.as_leaf());
+
+    const split = try tree.root.?.as_leaf().split_at(5);
+
+    std.debug.print("after split:", .{});
+    printValues(tree.root.?.as_leaf());
+
+    std.debug.print("split: {?}\n", .{split});
+    tree.ally.destroy(split.right);
+}
+
+test "btree new" {
+    std.debug.print("testing insertion\n", .{});
+    var tree = BTree.create(std.testing.allocator);
+    defer tree.destroy();
+    try tree.insert(5);
+    printValues(tree.root.?.as_leaf());
+    try tree.insert(4);
+    printValues(tree.root.?.as_leaf());
+    try tree.insert(6);
+    printValues(tree.root.?.as_leaf());
+    try tree.insert(3);
+    printValues(tree.root.?.as_leaf());
+    try tree.insert(7);
+    printValues(tree.root.?.as_leaf());
+    //try tree.insert(8);
+}
+
+test "simple test" {
+    var list = std.ArrayList(i32).init(std.testing.allocator);
+    defer list.deinit(); // try commenting this out and see if zig detects the memory leak!
+    try list.append(42);
+    try std.testing.expectEqual(@as(i32, 42), list.pop());
+}