optional type and simple rebalancing
This commit is contained in:
parent
482f01ed5e
commit
d3670bcf69
424
src/main.zig
424
src/main.zig
|
@ -1,5 +1,143 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
const Optional = struct {
|
||||||
|
const OptionEnum = enum {
|
||||||
|
Some,
|
||||||
|
None,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn into_option(value: anytype) Option(@typeInfo(@TypeOf(value)).Optional.child) {
|
||||||
|
return Option(@typeInfo(@TypeOf(value)).Optional.child).from_optional(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Option(comptime T: type) type {
|
||||||
|
return union(OptionEnum) {
|
||||||
|
Some: T,
|
||||||
|
None,
|
||||||
|
|
||||||
|
pub inline fn type_() type {
|
||||||
|
return T;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn from_optional(value: ?T) Self {
|
||||||
|
if (value) |v| {
|
||||||
|
return Self.some(v);
|
||||||
|
} else {
|
||||||
|
return Self.none();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_optional_t(self: Self) ?T {
|
||||||
|
return switch (self) {
|
||||||
|
.Some => |value| value,
|
||||||
|
.None => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn some(value: T) Self {
|
||||||
|
return .{ .Some = value };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn none() Self {
|
||||||
|
return Option(T).None;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn is_some(self: Self) bool {
|
||||||
|
return switch (self) {
|
||||||
|
.Some => true,
|
||||||
|
.None => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_none(self: Self) bool {
|
||||||
|
return !self.is_some();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_unwrap(self: Self) T {
|
||||||
|
return self.Some;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map(self: Self, comptime U: type, func: fn (T) U) Option(U) {
|
||||||
|
return switch (self) {
|
||||||
|
.Some => |value| Option(U).some(func(value)),
|
||||||
|
.None => Option(U).none(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn or_option(self: Self, other: Self) Self {
|
||||||
|
return switch (self) {
|
||||||
|
.Some => self,
|
||||||
|
.None => other,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn map_with(self: Self, with: anytype) Option(@typeInfo(@TypeOf(@TypeOf(with).call)).Fn.return_type.?) {
|
||||||
|
const Return = Option(@typeInfo(@TypeOf(@TypeOf(with).call)).Fn.return_type.?);
|
||||||
|
return switch (self) {
|
||||||
|
.Some => |value| Return.some(with.call(value)),
|
||||||
|
.None => Return.none(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn join(self: Self, comptime U: type, func: fn (T) Option(U)) Option(U) {
|
||||||
|
return switch (self) {
|
||||||
|
.Some => |value| func(value),
|
||||||
|
.None => Option(U).none(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn join_with(self: Self, with: anytype) Option(@typeInfo(@TypeOf(@TypeOf(with).call)).Fn.return_type.?.type_()) {
|
||||||
|
const Return = Option(@typeInfo(@TypeOf(@TypeOf(with).call)).Fn.return_type.?.type_());
|
||||||
|
return switch (self) {
|
||||||
|
.Some => |value| with.call(value),
|
||||||
|
.None => Return.none(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fn double(v: u32) u32 {
|
||||||
|
return v * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn option_test() void {
|
||||||
|
const Option = Optional.Option;
|
||||||
|
const opt = Option(u32).some(5);
|
||||||
|
const b = opt.map(u32, double);
|
||||||
|
|
||||||
|
std.debug.print("{}\n", .{b});
|
||||||
|
|
||||||
|
const c = b.map_with(struct {
|
||||||
|
val: i32,
|
||||||
|
|
||||||
|
fn call(self: @This(), v: u32) i32 {
|
||||||
|
return @intCast(i32, v) + self.val;
|
||||||
|
}
|
||||||
|
}{ .val = 3 });
|
||||||
|
|
||||||
|
std.debug.print("{}\n", .{c});
|
||||||
|
|
||||||
|
const d1: ?u32 = 4;
|
||||||
|
const d = Optional.into_option(d1).map_with(struct {
|
||||||
|
val: i32,
|
||||||
|
|
||||||
|
fn call(self: @This(), v: u32) i32 {
|
||||||
|
return @intCast(i32, v) + self.val;
|
||||||
|
}
|
||||||
|
}{ .val = 10 });
|
||||||
|
std.debug.print("{}\n", .{d});
|
||||||
|
|
||||||
|
std.debug.assert(b.Some == 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "option" {
|
||||||
|
std.debug.print("\n", .{});
|
||||||
|
option_test();
|
||||||
|
}
|
||||||
|
|
||||||
const BTree = struct {
|
const BTree = struct {
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
|
@ -206,6 +344,66 @@ const BTree = struct {
|
||||||
leaf.len = leaf.len + 1;
|
leaf.len = leaf.len + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn remove_key_from_node(self: NodeOrLeaf, key: u32) void {
|
||||||
|
switch (self.force()) {
|
||||||
|
.internal => |node| {
|
||||||
|
//const Option = Optional.Option;
|
||||||
|
// need to check if the edges to the left and right of the to-be-removed key exist,
|
||||||
|
// and if both exist, find the immediate neighbors of the key and promote
|
||||||
|
// the one in the bigger leaf up as a replacement.
|
||||||
|
|
||||||
|
// this node will not require rebalancement in that case, but the leaf
|
||||||
|
// from which we removed the replacement key might.
|
||||||
|
|
||||||
|
// safety: we already know the key is in this exact node.
|
||||||
|
const index = node.leaf.find_key(key).Leaf.idx;
|
||||||
|
|
||||||
|
const left = Optional.into_option(node.get_edge(index));
|
||||||
|
const right = Optional.into_option(node.get_edge(index + 1));
|
||||||
|
|
||||||
|
if (right.is_some() and left.is_some()) {
|
||||||
|
const right_value = right.unwrap().find_least_significant_key();
|
||||||
|
const left_value = left.unwrap().find_most_significant_key();
|
||||||
|
|
||||||
|
// pick the leaf with more values to reduce unneccesairy rebalances
|
||||||
|
const myleaf = if (right_value.leaf.len > left_value.leaf.len) {
|
||||||
|
// use right leaf
|
||||||
|
right_value;
|
||||||
|
} else {
|
||||||
|
// use left leaf
|
||||||
|
left_value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const promoted = myleaf.get_values()[myleaf.idx];
|
||||||
|
myleaf.remove_key(promoted);
|
||||||
|
self.leaf.values[index] = promoted;
|
||||||
|
|
||||||
|
// TODO: check `myleaf` for rebalance
|
||||||
|
} else {
|
||||||
|
// no adjacent edges
|
||||||
|
// (I don't think this can actually happen in a proper rtree)
|
||||||
|
|
||||||
|
// no need to promote any value from an adjecent subtree because
|
||||||
|
// there is no subtree on one side.
|
||||||
|
|
||||||
|
// instead we need to check for rebalance of self here TODO
|
||||||
|
|
||||||
|
node.shift_edges_left(index, 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.leaf => |node| {
|
||||||
|
// we can just remove the key and then check afterwards if the
|
||||||
|
// tree needs to be rebalanced
|
||||||
|
|
||||||
|
node.remove_key(key);
|
||||||
|
|
||||||
|
if (self.as_leaf().needs_rebalance()) {
|
||||||
|
std.debug.print("---------- require rebalance! -----------\n", .{});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn find_key(self: NodeOrLeaf, key: u32) Leaf.SearchResult {
|
fn find_key(self: NodeOrLeaf, key: u32) Leaf.SearchResult {
|
||||||
var leaf = self.as_leaf();
|
var leaf = self.as_leaf();
|
||||||
while (true) {
|
while (true) {
|
||||||
|
@ -237,7 +435,7 @@ const BTree = struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_most_significant_key(self: NodeOrLeaf) Leaf.SearchResult {
|
fn find_most_significant_key(self: NodeOrLeaf) struct { leaf: *Leaf, idx: u16 } {
|
||||||
var node = self;
|
var node = self;
|
||||||
while (true) {
|
while (true) {
|
||||||
switch (node.force()) {
|
switch (node.force()) {
|
||||||
|
@ -248,13 +446,13 @@ const BTree = struct {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.leaf => |leaf| {
|
.leaf => |leaf| {
|
||||||
return .{ .Leaf = .{ .leaf = leaf, .idx = leaf.len - 1 } };
|
return .{ .leaf = leaf, .idx = leaf.len - 1 };
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_least_significant_key(self: NodeOrLeaf) Leaf.SearchResult {
|
fn find_least_significant_key(self: NodeOrLeaf) struct { leaf: *Leaf, idx: u16 } {
|
||||||
var node = self;
|
var node = self;
|
||||||
while (true) {
|
while (true) {
|
||||||
switch (node.force()) {
|
switch (node.force()) {
|
||||||
|
@ -265,7 +463,7 @@ const BTree = struct {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.leaf => |leaf| {
|
.leaf => |leaf| {
|
||||||
return .{ .Leaf = .{ .leaf = leaf, .idx = 0 } };
|
return .{ .leaf = leaf, .idx = 0 };
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -402,6 +600,35 @@ const BTree = struct {
|
||||||
return self.edges[0..len];
|
return self.edges[0..len];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_edge(self: *Node, i: u16) ?NodeOrLeaf {
|
||||||
|
if (i <= self.leaf.len) {
|
||||||
|
const edge = self.get_edges()[i];
|
||||||
|
if (edge) |node| {
|
||||||
|
return node.force();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_edge(self: *Node, index: u16) ?NodeOrLeaf {
|
||||||
|
const edge = self.get_edge(index);
|
||||||
|
self.shift_edges_left(index + 1, 1);
|
||||||
|
|
||||||
|
return edge;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shift_edges_left(self: *Node, start: u16, count: u16) void {
|
||||||
|
std.debug.assert(start >= count);
|
||||||
|
if (start <= self.leaf.len) {
|
||||||
|
for (self.get_edges()[start..], start..) |edg, i| {
|
||||||
|
if (edg) |edge| {
|
||||||
|
edge.as_leaf().parent = .{ .parent = self, .idx = @intCast(u16, i) };
|
||||||
|
self.edges[i - count] = edge;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn dbg(self: *Node) void {
|
fn dbg(self: *Node) void {
|
||||||
const values = self.leaf.get_values();
|
const values = self.leaf.get_values();
|
||||||
const edges = self.get_edges()[0..values.len];
|
const edges = self.get_edges()[0..values.len];
|
||||||
|
@ -528,9 +755,144 @@ const BTree = struct {
|
||||||
Leaf: struct { leaf: *Leaf, idx: u16 },
|
Leaf: struct { leaf: *Leaf, idx: u16 },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn needs_rebalance(self: *Leaf) bool {
|
||||||
|
return self.len < MIN_AFTER_SPLIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rebalance(self: *Leaf) void {
|
||||||
|
if (self.needs_rebalance()) {
|
||||||
|
if (self.parent) |parent| {
|
||||||
|
const left_sib = Optional.into_option(parent.parent.get_edge(parent.idx - 1));
|
||||||
|
const right_sib = Optional.into_option(parent.parent.get_edge(parent.idx + 1));
|
||||||
|
|
||||||
|
const Side = enum { Left, Right };
|
||||||
|
|
||||||
|
const sibling: struct { node: NodeOrLeaf, side: Side } = blk: {
|
||||||
|
switch (left_sib) {
|
||||||
|
.Some => |left| {
|
||||||
|
switch (right_sib) {
|
||||||
|
.Some => |right| if (left.as_leaf().len > right.as_leaf().len) {
|
||||||
|
break :blk .{ .node = left, .side = .Left };
|
||||||
|
} else {
|
||||||
|
break :blk .{ .node = right, .side = .Right };
|
||||||
|
},
|
||||||
|
.None => {
|
||||||
|
break :blk .{ .node = left, .side = .Left };
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.None => {
|
||||||
|
switch (right_sib) {
|
||||||
|
.Some => |right| {
|
||||||
|
break :blk .{ .node = right, .side = .Right };
|
||||||
|
},
|
||||||
|
.None => {
|
||||||
|
// unreachable
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const offset = @intCast(i32, sibling.node.as_leaf().parent.?.idx) - @intCast(i32, parent.idx);
|
||||||
|
|
||||||
|
if (sibling.node.as_leaf().len > MIN_AFTER_SPLIT) {
|
||||||
|
// rotate
|
||||||
|
const index = @intCast(usize, parent.idx + offset);
|
||||||
|
|
||||||
|
NodeOrLeaf.from_leaf(self).push_value(parent.parent.leaf.values[index]);
|
||||||
|
parent.parent.leaf.values[index] = blk: {
|
||||||
|
if (offset < 0) {
|
||||||
|
const value = sibling.node.get_most_significant_value();
|
||||||
|
sibling.node.as_leaf().remove_key(value);
|
||||||
|
break :blk value;
|
||||||
|
} else {
|
||||||
|
const value = sibling.node.get_least_significant_value();
|
||||||
|
sibling.node.as_leaf().remove_key(value);
|
||||||
|
break :blk value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// merge
|
||||||
|
const left = switch (sibling.side) {
|
||||||
|
.Left => sibling.node.as_leaf(),
|
||||||
|
.Right => self,
|
||||||
|
};
|
||||||
|
const right = switch (sibling.side) {
|
||||||
|
.Right => sibling.node.as_leaf(),
|
||||||
|
.Left => self,
|
||||||
|
};
|
||||||
|
|
||||||
|
defer right.destroy();
|
||||||
|
|
||||||
|
// merge parent seperator into left
|
||||||
|
const key = parent.parent.leaf.get_value(left.parent.?.idx);
|
||||||
|
parent.parent.leaf.remove_key(key);
|
||||||
|
NodeOrLeaf.from_leaf(left).push_value(key);
|
||||||
|
|
||||||
|
// remove superfluous parent edge to right
|
||||||
|
_ = parent.parent.remove_edge(right.parent.?.idx);
|
||||||
|
|
||||||
|
// copy values from right to left
|
||||||
|
std.mem.copy(u32, left.values[left.len..], right.values[0..right.len]);
|
||||||
|
left.len = left.len + right.len;
|
||||||
|
|
||||||
|
parent.parent.as_leaf().rebalance();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// root node: if len is 0, just copy paste from child node if exists
|
||||||
|
if (self.len == 0) {
|
||||||
|
switch (NodeOrLeaf.from_leaf(self)) {
|
||||||
|
.internal => |root| {
|
||||||
|
if (root.edges[0]) |edge| {
|
||||||
|
defer edge.destroy();
|
||||||
|
// copy values
|
||||||
|
root.as_leaf().len = edge.as_leaf().len;
|
||||||
|
root.as_leaf().level = edge.as_leaf().level;
|
||||||
|
std.mem.copy(u32, root.leaf.values[0..], edge.as_leaf().values[0..]);
|
||||||
|
|
||||||
|
switch (edge.force()) {
|
||||||
|
.internal => |child| {
|
||||||
|
std.mem.copy(?NodeOrLeaf, root.edges[0..], child.edges[0..]);
|
||||||
|
|
||||||
|
for (root.get_edges(), 0..) |e, i| {
|
||||||
|
if (e) |ee| {
|
||||||
|
ee.as_leaf().parent = .{ .parent = root, .idx = @intCast(u16, i) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.leaf => {
|
||||||
|
// idk. just empty?
|
||||||
|
for (&root.edges) |*e| {
|
||||||
|
e.* = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
.leaf => {
|
||||||
|
// idk. just empty?
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn remove_key(self: *Leaf, key: u32) void {
|
fn remove_key(self: *Leaf, key: u32) void {
|
||||||
_ = self;
|
// safety: key must be contained by .values
|
||||||
_ = key;
|
var n: u16 = 0;
|
||||||
|
for (self.get_values(), 0..) |val, i| {
|
||||||
|
if (val >= key) {
|
||||||
|
n = @intCast(u16, i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (self.get_values()[n + 1 .. self.len], n..) |val, j| {
|
||||||
|
self.values[j] = val;
|
||||||
|
}
|
||||||
|
self.len = self.len - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_key(self: *Leaf, key: u32) SearchResult {
|
fn find_key(self: *Leaf, key: u32) SearchResult {
|
||||||
|
@ -572,6 +934,10 @@ const BTree = struct {
|
||||||
const len = self.len;
|
const len = self.len;
|
||||||
return self.values[0..len];
|
return self.values[0..len];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_value(self: *Leaf, idx: u16) u32 {
|
||||||
|
return self.get_values()[idx];
|
||||||
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -673,6 +1039,52 @@ test "btree rand insert" {
|
||||||
tree.dbg();
|
tree.dbg();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "ref removal" {
|
||||||
|
std.testing.refAllDeclsRecursive(BTree.Leaf);
|
||||||
|
std.testing.refAllDeclsRecursive(BTree.NodeOrLeaf);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "btree rebalance" {
|
||||||
|
std.debug.print("rebalance simple leaves\n", .{});
|
||||||
|
|
||||||
|
var tree = BTree.create(std.testing.allocator);
|
||||||
|
defer tree.destroy();
|
||||||
|
|
||||||
|
for (0..9) |i| {
|
||||||
|
_ = try tree.insert(@intCast(u32, i));
|
||||||
|
}
|
||||||
|
|
||||||
|
tree.dbg();
|
||||||
|
std.debug.print("\n", .{});
|
||||||
|
|
||||||
|
{
|
||||||
|
const node = tree.root.?.internal.edges[1].?;
|
||||||
|
node.as_leaf().len = node.as_leaf().len - 1;
|
||||||
|
node.as_leaf().rebalance();
|
||||||
|
|
||||||
|
tree.dbg();
|
||||||
|
std.debug.print("\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const node = tree.root.?.internal.edges[1].?;
|
||||||
|
node.as_leaf().len = node.as_leaf().len - 1;
|
||||||
|
node.as_leaf().rebalance();
|
||||||
|
|
||||||
|
tree.dbg();
|
||||||
|
std.debug.print("\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const node = tree.root.?.internal.edges[1].?;
|
||||||
|
node.as_leaf().len = node.as_leaf().len - 1;
|
||||||
|
node.as_leaf().rebalance();
|
||||||
|
|
||||||
|
tree.dbg();
|
||||||
|
std.debug.print("\n", .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test "btree least most sig value" {
|
test "btree least most sig value" {
|
||||||
std.debug.print("least/most significant values\n", .{});
|
std.debug.print("least/most significant values\n", .{});
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue