const std = @import("std"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; const getNodeType = @import("node.zig").Node; pub fn BTreeMap(comptime K: type, comptime V: type) type { return struct { const Self = @This(); root: ?*Node, allocator: Allocator, const B = 6; const Node = getNodeType(K, V, B); const KV = Node.KV; const SearchResult = Node.SearchResult; const StackItem = struct { node: *Node, index: usize, }; pub fn init(allocator: Allocator) Self { return Self{ .allocator = allocator, .root = null, }; } pub fn deinit(self: Self) !void { var stack = std.ArrayList(*Node).init(self.allocator); defer stack.deinit(); if (self.root) |root| { try stack.append(root); } else return; while (stack.popOrNull()) |node| { if (!node.isLeaf()) { var i: usize = 0; while (i < node.len + 1) : (i += 1) { try stack.append(node.edges[i].?); } } self.allocator.destroy(node); } } pub fn isEmpty(self: *const Self) bool { if (self.root == null) return true; return self.root.?.len == 0; } /// Get value from a certain key. pub fn get(self: Self, key: K) ?V { var current = self.root; while (current) |node| { const result = node.search(key); if (result.found) { return node.values[result.index]; } else { current = node.edges[result.index]; } } return null; } /// Inserts key-value pair into the map. Swaps the values if already present /// and returns the old. /// This function has two stages. In the first stage, the tree /// is traversed to find the path to the relevant leaf node. /// This path is saved in a stack, containing the nodes itself /// and the indices of the children where the path continues. /// In the second phase, we try to insert and, if the node is full, /// split the node and hand the result of the split down the stack. /// Repeat this until insertion is successful or the root itself is split. pub fn fetchPut(self: *Self, key: K, value: V) !?V { // TODO: return KV like in std.HashMap if (self.root == null) { self.root = try Node.createFromKV(self.allocator, key, value); return null; } var stack = std.ArrayList(StackItem).init(self.allocator); defer stack.deinit(); // Traverse tree until we find the key or hit bottom. // If we we find the key, swap new with old value and return the old. // Build a stack to remember the path var current = self.root; var search_result: SearchResult = undefined; while (current) |node| { search_result = node.search(key); if (search_result.found) { return node.swapValue(search_result.index, value); } // Not found, go deeper. current = node.edges[search_result.index]; try stack.append(.{ .node = node, .index = search_result.index, }); } // Pop top of stack (bottom of tree/leaf node). var stack_next: ?StackItem = stack.pop(); // Try to insert to leaf node. var split_result = try stack_next.?.node.insertOrSplit( self.allocator, stack_next.?.index, key, value, null, ); // No split was necessary -> insertion was successful. We're Done. if (split_result == null) { return null; } // Split was necessary -> move down on stack. // The current node in stack was incorporated in the tree and the SplitResult. stack_next = stack.popOrNull(); // Repeat the process of splitting and inserting until insertion is // successful or we hit the root node, in which case the root node is split. while (split_result) |split_result_unwrapped| { if (stack_next) |stack_next_unwrapped| { // Try to insert the in current stack item. split_result = try stack_next_unwrapped.node.insertOrSplit( self.allocator, stack_next_unwrapped.index, split_result_unwrapped.key, split_result_unwrapped.value, split_result_unwrapped.edge, ); stack_next = stack.popOrNull(); } else { // We reached the root. var new_root = try Node.createFromKV( self.allocator, split_result_unwrapped.key, split_result_unwrapped.value, ); new_root.edges[0] = self.root; new_root.edges[1] = split_result_unwrapped.edge; self.root = new_root; return null; } } else return null; } /// Removes and returns a key-value-pair. Returns null if not found. pub fn fetchRemove(self: *Self, key: K) !?KV { var stack = std.ArrayList(StackItem).init(self.allocator); defer stack.deinit(); // Traverse tree until we find the key or hit bottom. // Build a stack to remember the path var current = self.root; var search_result: SearchResult = undefined; var found_key_ptr: ?*K = null; var found_value_ptr: ?*V = null; while (current) |node| { search_result = node.search(key); if (search_result.found) { // Found! Remember pointers to key and value to swap later. found_key_ptr = &node.keys[search_result.index]; found_value_ptr = &node.values[search_result.index]; // If not reached leaf, increment index in order to find the // found key's inorder successor when we continue down the tree. if (!node.isLeaf()) search_result.index += 1; } try stack.append(.{ .node = node, .index = search_result.index, }); current = node.edges[search_result.index]; if (search_result.found) break; } else { // Key not found. return null; } // Continue building the stack to the inorder successor of the found key. while (current) |node| { try stack.append(.{ .node = node, .index = 0, }); current = node.edges[0]; } // Reached leaf node. Stack is complete. // Leaf node is on top of stack now. var current_stack = stack.pop(); // Swap the KV for deletion with its inorder successor. var out: KV = .{ .key = found_key_ptr.?.*, .value = found_value_ptr.?.* }; found_key_ptr.?.* = current_stack.node.keys[current_stack.index]; found_value_ptr.?.* = current_stack.node.values[current_stack.index]; // Now ew can remove the key-value pair in the leaf. This can result in an underflow, // which is handled below. _ = current_stack.node.remove(current_stack.index); // If our leaf is also the root, it cannot underflow. if (current_stack.node == self.root) return out; // Fix underflow and move down the stack until underflow is fixed. while (current_stack.node.isLacking()) { // We have an underflow in the current stack position. This is fixed // from the parent's erspective, so move down the stack. current_stack = stack.pop(); // Try to borrow, first from right, then from left. if (current_stack.node.borrowFromRight(current_stack.index)) return out; if (current_stack.node.borrowFromLeft(current_stack.index)) return out; // Borrow was not possible, merge nodes. if (current_stack.index == current_stack.node.len) { // the underflowed edge is the most right. Merge with left. current_stack.node.mergeEdges(self.allocator, current_stack.index - 1); } else { // Merge with right. current_stack.node.mergeEdges(self.allocator, current_stack.index); } if (current_stack.node == self.root) { // We reached the root. if (self.root.?.len == 0) { // If root is empty, replace with merged node. const new_root = current_stack.node.edges[0].?; self.allocator.destroy(self.root.?); self.root.? = new_root; } break; } } return out; } pub fn iteratorInit(self: *const Self) !Iterator { var new_stack = std.ArrayList(StackItem).init(self.allocator); if (self.root) |root| { try new_stack.append(.{ .node = root, .index = 0, }); } return Iterator{ .stack = new_stack, .backwards = false, }; } const Iterator = struct { stack: std.ArrayList(StackItem), backwards: bool, pub fn deinit(it: Iterator) void { it.stack.deinit(); } pub fn next(it: *Iterator) !?KV { while (it.topStackItem()) |item| { if (!item.node.isLeaf() and !it.backwards) { // Child exists at index or going forward, go deeper. var child = item.node.edges[item.index].?; try it.stack.append(StackItem{ .node = child, .index = 0, }); } else { // No Child or coming backwards. if (item.index < item.node.len) { // Node is not yet exhausted. // Return KV from Node and increment the node's index. var out = KV{ .key = item.node.keys[item.index], .value = item.node.values[item.index], }; item.index += 1; it.backwards = false; return out; } else { // Node is exhausted. // Set `backwards` so that this node is not entered again // in the next iteration. _ = it.stack.popOrNull(); it.backwards = true; } } } else return null; } fn topStackItem(it: *Iterator) ?*StackItem { if (it.stack.items.len == 0) { return null; } else { return &it.stack.items[it.stack.items.len - 1]; } } }; /// Intended for testing and debugging. Traverses whole tree and /// asserts the validity of every node. Also if every leaf has the /// same height. -> BTree itself is valid. fn assertValidity(self: *const Self) !void { if (self.root == null) return; var depth: ?usize = null; var backwards = false; var stack = std.ArrayList(StackItem).init(self.allocator); defer stack.deinit(); try stack.append(StackItem{ .node = self.root.?, .index = 0, }); self.root.?.assertValidityRoot(); var item: *StackItem = undefined; while (stack.items.len >= 1) { item = &stack.items[stack.items.len - 1]; if (!item.node.isLeaf() and !backwards) { // Go deeper. var child = item.node.edges[item.index].?; child.assertValidity(); try stack.append(StackItem{ .node = child, .index = 0, }); } else { // Reached leaf or moving backwards // Assert tree depth if (item.node.isLeaf()) { if (depth == null) { depth = stack.items.len; } else { assert(stack.items.len == depth.?); } } if (item.index < item.node.len) { // Node is not yet exhausted. item.index += 1; backwards = false; } else { // Node is exhausted. // Set `backwards` so that this node is not entered again // in the next iteration. _ = stack.popOrNull(); backwards = true; } } } } }; } const testing = std.testing; test { const n = 10000; var keys = std.ArrayList(i16).init(testing.allocator); defer keys.deinit(); var prng = std.rand.DefaultPrng.init(0); const random = prng.random(); var i: i16 = 0; while (i < n) : (i += 1) { keys.append(random.int(i16)) catch unreachable; } var tree = BTreeMap(i32, i32).init(testing.allocator); defer tree.deinit() catch unreachable; for (keys.items) |j| { _ = try tree.fetchPut(j, j); } _ = try tree.fetchPut(11111, 99999); _ = try tree.fetchPut(22222, 88888); //var it = try tree.iteratorInit(); //defer it.deinit(); //while (it.next() catch unreachable) |item| std.debug.print("{any}\n", .{item}); try testing.expect((try tree.fetchPut(11111, 0)).? == 99999); try testing.expectEqual( (try tree.fetchRemove(22222)).?, @TypeOf(tree).Node.KV{ .key = 22222, .value = 88888, }, ); try tree.assertValidity(); random.shuffle(i16, keys.items); for (keys.items) |j| { const out = try tree.fetchRemove(j); if (out) |u| { try testing.expect(u.key == j); try testing.expect(u.value == j); } } _ = try tree.fetchRemove(11111); try testing.expect(tree.isEmpty()); } test "structs as keys" { const Car = struct { power: i32, pub fn lt(a: @This(), b: @This()) bool { return a.power < b.power; } pub fn eq(a: @This(), b: @This()) bool { return a.power == b.power; } }; const n = 50; var cars = std.ArrayList(Car).init(testing.allocator); defer cars.deinit(); var prng = std.rand.DefaultPrng.init(0); const random = prng.random(); var i: u32 = 0; while (i < n) : (i += 1) { var power: i32 = @intCast(i32, random.int(u8)) + 100; cars.append(Car{ .power = power }) catch unreachable; } var tree = BTreeMap(Car, bool).init(testing.allocator); defer tree.deinit() catch unreachable; for (cars.items) |j| { _ = try tree.fetchPut(j, true); } for (cars.items) |j| { _ = try tree.fetchRemove(j); } }