zig/lib/std/rb.zig

629 lines
19 KiB
Zig
Raw Normal View History

const std = @import("std");
const assert = std.debug.assert;
const testing = std.testing;
const Order = std.math.Order;
const Color = enum(u1) {
Black,
Red,
};
const Red = Color.Red;
const Black = Color.Black;
const ReplaceError = error{NotEqual};
2020-01-19 19:35:56 +00:00
const SortError = error{NotUnique}; // The new comparison function results in duplicates.
/// Insert this into your struct that you want to add to a red-black tree.
/// Do not use a pointer. Turn the *rb.Node results of the functions in rb
/// (after resolving optionals) to your structure using @fieldParentPtr(). Example:
///
/// const Number = struct {
/// node: rb.Node,
/// value: i32,
/// };
2018-09-02 06:23:34 +01:00
/// fn number(node: *rb.Node) Number {
/// return @fieldParentPtr(Number, "node", node);
/// }
pub const Node = struct {
left: ?*Node,
right: ?*Node,
2018-08-28 00:25:18 +01:00
/// parent | color
parent_and_color: usize,
pub fn next(constnode: *Node) ?*Node {
var node = constnode;
if (node.right) |right| {
var n = right;
while (n.left) |left|
n = left;
return n;
}
while (true) {
2018-11-16 04:09:21 +00:00
var parent = node.getParent();
if (parent) |p| {
if (node != p.right)
return p;
node = p;
} else
return null;
}
}
pub fn prev(constnode: *Node) ?*Node {
var node = constnode;
if (node.left) |left| {
var n = left;
while (n.right) |right|
n = right;
return n;
}
while (true) {
2018-11-16 04:09:21 +00:00
var parent = node.getParent();
if (parent) |p| {
if (node != p.left)
return p;
node = p;
} else
return null;
}
}
2018-11-16 04:09:21 +00:00
pub fn isRoot(node: *Node) bool {
return node.getParent() == null;
}
2018-11-16 04:09:21 +00:00
fn isRed(node: *Node) bool {
return node.getColor() == Red;
}
2018-11-16 04:09:21 +00:00
fn isBlack(node: *Node) bool {
return node.getColor() == Black;
}
2018-11-16 04:09:21 +00:00
fn setParent(node: *Node, parent: ?*Node) void {
node.parent_and_color = @ptrToInt(parent) | (node.parent_and_color & 1);
}
2018-11-16 04:09:21 +00:00
fn getParent(node: *Node) ?*Node {
const mask: usize = 1;
comptime {
assert(@alignOf(*Node) >= 2);
}
const maybe_ptr = node.parent_and_color & ~mask;
return if (maybe_ptr == 0) null else @intToPtr(*Node, maybe_ptr);
}
2018-11-16 04:09:21 +00:00
fn setColor(node: *Node, color: Color) void {
const mask: usize = 1;
node.parent_and_color = (node.parent_and_color & ~mask) | @enumToInt(color);
}
2018-11-16 04:09:21 +00:00
fn getColor(node: *Node) Color {
return @intToEnum(Color, @intCast(u1, node.parent_and_color & 1));
}
fn setChild(node: *Node, child: ?*Node, is_left: bool) void {
if (is_left) {
node.left = child;
} else {
node.right = child;
}
}
2018-11-16 04:09:21 +00:00
fn getFirst(nodeconst: *Node) *Node {
var node = nodeconst;
while (node.left) |left| {
node = left;
}
return node;
}
fn getLast(nodeconst: *Node) *Node {
var node = nodeconst;
while (node.right) |right| {
node = right;
}
return node;
}
};
pub const Tree = struct {
root: ?*Node,
compareFn: fn (*Node, *Node, *Tree) Order,
/// Re-sorts a tree with a new compare function
2020-01-19 19:35:56 +00:00
pub fn sort(tree: *Tree, newCompareFn: fn (*Node, *Node, *Tree) Order) SortError!void {
var newTree = Tree.init(newCompareFn);
var node: *Node = undefined;
while (true) {
node = tree.first() orelse break;
tree.remove(node);
if (newTree.insert(node) != null) {
return error.NotUnique; // EEXISTS
}
}
tree.* = newTree;
}
/// If you have a need for a version that caches this, please file a bug.
pub fn first(tree: *Tree) ?*Node {
var node: *Node = tree.root orelse return null;
while (node.left) |left| {
node = left;
}
return node;
}
pub fn last(tree: *Tree) ?*Node {
var node: *Node = tree.root orelse return null;
while (node.right) |right| {
node = right;
}
return node;
}
/// Duplicate keys are not allowed. The item with the same key already in the
/// tree will be returned, and the item will not be inserted.
pub fn insert(tree: *Tree, node_const: *Node) ?*Node {
var node = node_const;
var maybe_key: ?*Node = undefined;
var maybe_parent: ?*Node = undefined;
var is_left: bool = undefined;
2018-11-16 04:09:21 +00:00
maybe_key = doLookup(node, tree, &maybe_parent, &is_left);
if (maybe_key) |key| {
return key;
}
node.left = null;
node.right = null;
2018-11-16 04:09:21 +00:00
node.setColor(Red);
node.setParent(maybe_parent);
if (maybe_parent) |parent| {
parent.setChild(node, is_left);
} else {
tree.root = node;
}
2018-11-16 04:09:21 +00:00
while (node.getParent()) |*parent| {
if (parent.*.isBlack())
break;
2018-08-28 00:25:18 +01:00
// the root is always black
2018-11-16 04:09:21 +00:00
var grandpa = parent.*.getParent() orelse unreachable;
if (parent.* == grandpa.left) {
var maybe_uncle = grandpa.right;
if (maybe_uncle) |uncle| {
2018-11-16 04:09:21 +00:00
if (uncle.isBlack())
break;
2018-11-16 04:09:21 +00:00
parent.*.setColor(Black);
uncle.setColor(Black);
grandpa.setColor(Red);
node = grandpa;
} else {
if (node == parent.*.right) {
2018-11-16 04:09:21 +00:00
rotateLeft(parent.*, tree);
node = parent.*;
2018-11-16 04:09:21 +00:00
parent.* = node.getParent().?; // Just rotated
}
2018-11-16 04:09:21 +00:00
parent.*.setColor(Black);
grandpa.setColor(Red);
rotateRight(grandpa, tree);
}
} else {
var maybe_uncle = grandpa.left;
2018-08-28 00:25:18 +01:00
if (maybe_uncle) |uncle| {
2018-11-16 04:09:21 +00:00
if (uncle.isBlack())
break;
2018-11-16 04:09:21 +00:00
parent.*.setColor(Black);
uncle.setColor(Black);
grandpa.setColor(Red);
node = grandpa;
} else {
if (node == parent.*.left) {
2018-11-16 04:09:21 +00:00
rotateRight(parent.*, tree);
node = parent.*;
2018-11-16 04:09:21 +00:00
parent.* = node.getParent().?; // Just rotated
}
2018-11-16 04:09:21 +00:00
parent.*.setColor(Black);
grandpa.setColor(Red);
rotateLeft(grandpa, tree);
}
}
}
// This was an insert, there is at least one node.
2018-11-16 04:09:21 +00:00
tree.root.?.setColor(Black);
return null;
}
/// lookup searches for the value of key, using binary search. It will
/// return a pointer to the node if it is there, otherwise it will return null.
/// Complexity guaranteed O(log n), where n is the number of nodes book-kept
/// by tree.
pub fn lookup(tree: *Tree, key: *Node) ?*Node {
var parent: ?*Node = undefined;
var is_left: bool = undefined;
2018-11-16 04:09:21 +00:00
return doLookup(key, tree, &parent, &is_left);
}
/// If node is not part of tree, behavior is undefined.
pub fn remove(tree: *Tree, nodeconst: *Node) void {
var node = nodeconst;
// as this has the same value as node, it is unsafe to access node after newnode
var newnode: ?*Node = nodeconst;
2018-11-16 04:09:21 +00:00
var maybe_parent: ?*Node = node.getParent();
var color: Color = undefined;
var next: *Node = undefined;
// This clause is to avoid optionals
if (node.left == null and node.right == null) {
if (maybe_parent) |parent| {
parent.setChild(null, parent.left == node);
} else
tree.root = null;
2018-11-16 04:09:21 +00:00
color = node.getColor();
newnode = null;
} else {
if (node.left == null) {
next = node.right.?; // Not both null as per above
} else if (node.right == null) {
2018-08-28 00:25:18 +01:00
next = node.left.?; // Not both null as per above
} else
2018-11-16 04:09:21 +00:00
next = node.right.?.getFirst(); // Just checked for null above
if (maybe_parent) |parent| {
parent.setChild(next, parent.left == node);
} else
tree.root = next;
if (node.left != null and node.right != null) {
const left = node.left.?;
const right = node.right.?;
2018-11-16 04:09:21 +00:00
color = next.getColor();
next.setColor(node.getColor());
next.left = left;
2018-11-16 04:09:21 +00:00
left.setParent(next);
if (next != right) {
2018-11-16 04:09:21 +00:00
var parent = next.getParent().?; // Was traversed via child node (right/left)
next.setParent(node.getParent());
newnode = next.right;
parent.left = node;
next.right = right;
2018-11-16 04:09:21 +00:00
right.setParent(next);
} else {
2018-11-16 04:09:21 +00:00
next.setParent(maybe_parent);
maybe_parent = next;
newnode = next.right;
}
} else {
2018-11-16 04:09:21 +00:00
color = node.getColor();
newnode = next;
}
}
if (newnode) |n|
2018-11-16 04:09:21 +00:00
n.setParent(maybe_parent);
if (color == Red)
return;
if (newnode) |n| {
2018-11-16 04:09:21 +00:00
n.setColor(Black);
return;
}
while (node == tree.root) {
// If not root, there must be parent
var parent = maybe_parent.?;
if (node == parent.left) {
var sibling = parent.right.?; // Same number of black nodes.
2018-08-28 00:25:18 +01:00
2018-11-16 04:09:21 +00:00
if (sibling.isRed()) {
sibling.setColor(Black);
parent.setColor(Red);
rotateLeft(parent, tree);
sibling = parent.right.?; // Just rotated
}
2018-11-16 04:09:21 +00:00
if ((if (sibling.left) |n| n.isBlack() else true) and
(if (sibling.right) |n| n.isBlack() else true))
2018-08-28 00:25:18 +01:00
{
2018-11-16 04:09:21 +00:00
sibling.setColor(Red);
node = parent;
2018-11-16 04:09:21 +00:00
maybe_parent = parent.getParent();
continue;
}
2018-11-16 04:09:21 +00:00
if (if (sibling.right) |n| n.isBlack() else true) {
sibling.left.?.setColor(Black); // Same number of black nodes.
sibling.setColor(Red);
rotateRight(sibling, tree);
sibling = parent.right.?; // Just rotated
}
2018-11-16 04:09:21 +00:00
sibling.setColor(parent.getColor());
parent.setColor(Black);
sibling.right.?.setColor(Black); // Same number of black nodes.
rotateLeft(parent, tree);
newnode = tree.root;
break;
} else {
var sibling = parent.left.?; // Same number of black nodes.
2018-08-28 00:25:18 +01:00
2018-11-16 04:09:21 +00:00
if (sibling.isRed()) {
sibling.setColor(Black);
parent.setColor(Red);
rotateRight(parent, tree);
sibling = parent.left.?; // Just rotated
}
2018-11-16 04:09:21 +00:00
if ((if (sibling.left) |n| n.isBlack() else true) and
(if (sibling.right) |n| n.isBlack() else true))
2018-08-28 00:25:18 +01:00
{
2018-11-16 04:09:21 +00:00
sibling.setColor(Red);
node = parent;
2018-11-16 04:09:21 +00:00
maybe_parent = parent.getParent();
continue;
}
2018-11-16 04:09:21 +00:00
if (if (sibling.left) |n| n.isBlack() else true) {
sibling.right.?.setColor(Black); // Same number of black nodes
sibling.setColor(Red);
rotateLeft(sibling, tree);
sibling = parent.left.?; // Just rotated
}
2018-11-16 04:09:21 +00:00
sibling.setColor(parent.getColor());
parent.setColor(Black);
sibling.left.?.setColor(Black); // Same number of black nodes
rotateRight(parent, tree);
newnode = tree.root;
break;
}
2018-11-16 04:09:21 +00:00
if (node.isRed())
break;
}
if (newnode) |n|
2018-11-16 04:09:21 +00:00
n.setColor(Black);
}
/// This is a shortcut to avoid removing and re-inserting an item with the same key.
pub fn replace(tree: *Tree, old: *Node, newconst: *Node) !void {
var new = newconst;
// I assume this can get optimized out if the caller already knows.
if (tree.compareFn(old, new, tree) != .eq) return ReplaceError.NotEqual;
2018-11-16 04:09:21 +00:00
if (old.getParent()) |parent| {
parent.setChild(new, parent.left == old);
} else
tree.root = new;
if (old.left) |left|
2018-11-16 04:09:21 +00:00
left.setParent(new);
if (old.right) |right|
2018-11-16 04:09:21 +00:00
right.setParent(new);
new.* = old.*;
}
pub fn init(f: fn (*Node, *Node, *Tree) Order) Tree {
return Tree{
.root = null,
.compareFn = f,
};
}
};
2018-11-16 04:09:21 +00:00
fn rotateLeft(node: *Node, tree: *Tree) void {
var p: *Node = node;
var q: *Node = node.right orelse unreachable;
var parent: *Node = undefined;
2018-11-16 04:09:21 +00:00
if (!p.isRoot()) {
parent = p.getParent().?;
if (parent.left == p) {
parent.left = q;
} else {
parent.right = q;
}
2018-11-16 04:09:21 +00:00
q.setParent(parent);
} else {
tree.root = q;
2018-11-16 04:09:21 +00:00
q.setParent(null);
}
2018-11-16 04:09:21 +00:00
p.setParent(q);
p.right = q.left;
if (p.right) |right| {
2018-11-16 04:09:21 +00:00
right.setParent(p);
}
q.left = p;
}
2018-11-16 04:09:21 +00:00
fn rotateRight(node: *Node, tree: *Tree) void {
var p: *Node = node;
var q: *Node = node.left orelse unreachable;
var parent: *Node = undefined;
2018-11-16 04:09:21 +00:00
if (!p.isRoot()) {
parent = p.getParent().?;
if (parent.left == p) {
parent.left = q;
} else {
parent.right = q;
}
2018-11-16 04:09:21 +00:00
q.setParent(parent);
} else {
tree.root = q;
2018-11-16 04:09:21 +00:00
q.setParent(null);
}
2018-11-16 04:09:21 +00:00
p.setParent(q);
p.left = q.right;
if (p.left) |left| {
2018-11-16 04:09:21 +00:00
left.setParent(p);
}
q.right = p;
}
2018-11-16 04:09:21 +00:00
fn doLookup(key: *Node, tree: *Tree, pparent: *?*Node, is_left: *bool) ?*Node {
var maybe_node: ?*Node = tree.root;
pparent.* = null;
is_left.* = false;
while (maybe_node) |node| {
const res = tree.compareFn(node, key, tree);
if (res == .eq) {
return node;
}
pparent.* = node;
switch (res) {
.gt => {
is_left.* = true;
maybe_node = node.left;
},
.lt => {
is_left.* = false;
maybe_node = node.right;
},
.eq => unreachable, // handled above
}
}
return null;
}
const testNumber = struct {
node: Node,
value: usize,
};
fn testGetNumber(node: *Node) *testNumber {
return @fieldParentPtr(testNumber, "node", node);
}
fn testCompare(l: *Node, r: *Node, contextIgnored: *Tree) Order {
var left = testGetNumber(l);
var right = testGetNumber(r);
if (left.value < right.value) {
return .lt;
} else if (left.value == right.value) {
return .eq;
} else if (left.value > right.value) {
return .gt;
}
unreachable;
}
fn testCompareReverse(l: *Node, r: *Node, contextIgnored: *Tree) Order {
return testCompare(r, l, contextIgnored);
}
test "rb" {
significantly increase test coverage * add zig build option `-Dskip-libc` to skip tests that build libc (e.g. if you don't want to wait for musl to build) * add `-Denable-wine` option which uses wine to run cross compiled windows tests on non-windows hosts * add `-Denable-qemu` option which uses qemu to run cross compiled foreign architecture tests * add `-Denable-foreign-glibc=path` option which combined with `-Denable-qemu` enables running cross compiled tests that link against glibc. See https://github.com/ziglang/zig/wiki/Updating-libc#glibc for how to produce this directory. * the test matrix is done manually. release test builds are only enabled by default for the native target. this should save us some CI time, while still providing decent coverage of release builds. - add test coverage for `x86_64-linux-musl -lc` (building musl libc) - add test coverage for `x86_64-linux-gnu -lc` (building glibc) - add test coverage for `aarch64v8_5a-linux-none` - add test coverage for `aarch64v8_5a-linux-musl -lc` (building musl libc) - add test coverage for `aarch64v8_5a-linux-gnu -lc` (building glibc) - add test coverage for `arm-linux-none` - test coverage for `arm-linux-musleabihf -lc` (building musl libc) is disabled due to #3286 - test coverage for `arm-linux-gnueabihf -lc` (building glibc) is disabled due to #3287 - test coverage for `x86_64-windows-gnu -lc` (building mingw-w64) is disabled due to #3285 * enable qemu testing on the Linux CI job. There's not really a good reason to enable wine, since we have a Windows CI job as well. * remove the no longer needed `--build-file ../build.zig` from CI scripts * fix bug in glibc compilation where it wasn't properly reading the abi list txt files, resulting in "key not found" error. * std.build.Target gains: - isNetBSD - isLinux - osRequiresLibC - getArchPtrBitWidth - getExternalExecutor * zig build system gains support for enabling wine and enabling qemu. `artifact.enable_wine = true;`, `artifact.enable_qemu = true;`. This communicates that the system has these tools installed and the build system will use them to run tests. * zig build system gains support for overriding the dynamic linker of an executable artifact. * fix std.c.lseek prototype. makes behavior tests for arm-linux-musleabihf pass. * disable std lib tests that are failing on ARM. See #3288, #3289 * provide `std.os.off_t`. * disable some of the compiler_rt symbols for arm 32 bit. Fixes compiler_rt tests for arm 32 bit * add __stack_chk_guard when linking against glibc. Fixes std lib tests for aarch64-linux-gnu * workaround for "unable to inline function" using `@inlineCall`. Fixes compiler_rt tests for arm 32 bit.
2019-09-22 04:55:56 +01:00
if (@import("builtin").arch == .aarch64) {
// TODO https://github.com/ziglang/zig/issues/3288
return error.SkipZigTest;
}
var tree = Tree.init(testCompare);
var ns: [10]testNumber = undefined;
ns[0].value = 42;
ns[1].value = 41;
ns[2].value = 40;
ns[3].value = 39;
ns[4].value = 38;
ns[5].value = 39;
ns[6].value = 3453;
ns[7].value = 32345;
ns[8].value = 392345;
ns[9].value = 4;
var dup: testNumber = undefined;
dup.value = 32345;
_ = tree.insert(&ns[1].node);
_ = tree.insert(&ns[2].node);
_ = tree.insert(&ns[3].node);
_ = tree.insert(&ns[4].node);
_ = tree.insert(&ns[5].node);
_ = tree.insert(&ns[6].node);
_ = tree.insert(&ns[7].node);
_ = tree.insert(&ns[8].node);
_ = tree.insert(&ns[9].node);
tree.remove(&ns[3].node);
testing.expect(tree.insert(&dup.node) == &ns[7].node);
try tree.replace(&ns[7].node, &dup.node);
var num: *testNumber = undefined;
num = testGetNumber(tree.first().?);
while (num.node.next() != null) {
testing.expect(testGetNumber(num.node.next().?).value > num.value);
num = testGetNumber(num.node.next().?);
}
}
test "inserting and looking up" {
var tree = Tree.init(testCompare);
var number: testNumber = undefined;
number.value = 1000;
_ = tree.insert(&number.node);
var dup: testNumber = undefined;
//Assert that tuples with identical value fields finds the same pointer
dup.value = 1000;
assert(tree.lookup(&dup.node) == &number.node);
//Assert that tuples with identical values do not clobber when inserted.
_ = tree.insert(&dup.node);
assert(tree.lookup(&dup.node) == &number.node);
assert(tree.lookup(&number.node) != &dup.node);
assert(testGetNumber(tree.lookup(&dup.node).?).value == testGetNumber(&dup.node).value);
//Assert that if looking for a non-existing value, return null.
var non_existing_value: testNumber = undefined;
non_existing_value.value = 1234;
assert(tree.lookup(&non_existing_value.node) == null);
}
test "multiple inserts, followed by calling first and last" {
significantly increase test coverage * add zig build option `-Dskip-libc` to skip tests that build libc (e.g. if you don't want to wait for musl to build) * add `-Denable-wine` option which uses wine to run cross compiled windows tests on non-windows hosts * add `-Denable-qemu` option which uses qemu to run cross compiled foreign architecture tests * add `-Denable-foreign-glibc=path` option which combined with `-Denable-qemu` enables running cross compiled tests that link against glibc. See https://github.com/ziglang/zig/wiki/Updating-libc#glibc for how to produce this directory. * the test matrix is done manually. release test builds are only enabled by default for the native target. this should save us some CI time, while still providing decent coverage of release builds. - add test coverage for `x86_64-linux-musl -lc` (building musl libc) - add test coverage for `x86_64-linux-gnu -lc` (building glibc) - add test coverage for `aarch64v8_5a-linux-none` - add test coverage for `aarch64v8_5a-linux-musl -lc` (building musl libc) - add test coverage for `aarch64v8_5a-linux-gnu -lc` (building glibc) - add test coverage for `arm-linux-none` - test coverage for `arm-linux-musleabihf -lc` (building musl libc) is disabled due to #3286 - test coverage for `arm-linux-gnueabihf -lc` (building glibc) is disabled due to #3287 - test coverage for `x86_64-windows-gnu -lc` (building mingw-w64) is disabled due to #3285 * enable qemu testing on the Linux CI job. There's not really a good reason to enable wine, since we have a Windows CI job as well. * remove the no longer needed `--build-file ../build.zig` from CI scripts * fix bug in glibc compilation where it wasn't properly reading the abi list txt files, resulting in "key not found" error. * std.build.Target gains: - isNetBSD - isLinux - osRequiresLibC - getArchPtrBitWidth - getExternalExecutor * zig build system gains support for enabling wine and enabling qemu. `artifact.enable_wine = true;`, `artifact.enable_qemu = true;`. This communicates that the system has these tools installed and the build system will use them to run tests. * zig build system gains support for overriding the dynamic linker of an executable artifact. * fix std.c.lseek prototype. makes behavior tests for arm-linux-musleabihf pass. * disable std lib tests that are failing on ARM. See #3288, #3289 * provide `std.os.off_t`. * disable some of the compiler_rt symbols for arm 32 bit. Fixes compiler_rt tests for arm 32 bit * add __stack_chk_guard when linking against glibc. Fixes std lib tests for aarch64-linux-gnu * workaround for "unable to inline function" using `@inlineCall`. Fixes compiler_rt tests for arm 32 bit.
2019-09-22 04:55:56 +01:00
if (@import("builtin").arch == .aarch64) {
// TODO https://github.com/ziglang/zig/issues/3288
return error.SkipZigTest;
}
var tree = Tree.init(testCompare);
var zeroth: testNumber = undefined;
zeroth.value = 0;
var first: testNumber = undefined;
first.value = 1;
var second: testNumber = undefined;
second.value = 2;
var third: testNumber = undefined;
third.value = 3;
_ = tree.insert(&zeroth.node);
_ = tree.insert(&first.node);
_ = tree.insert(&second.node);
_ = tree.insert(&third.node);
assert(testGetNumber(tree.first().?).value == 0);
assert(testGetNumber(tree.last().?).value == 3);
var lookupNode: testNumber = undefined;
lookupNode.value = 3;
assert(tree.lookup(&lookupNode.node) == &third.node);
tree.sort(testCompareReverse) catch unreachable;
assert(testGetNumber(tree.first().?).value == 3);
assert(testGetNumber(tree.last().?).value == 0);
assert(tree.lookup(&lookupNode.node) == &third.node);
}