mirror of
https://github.com/ziglang/zig.git
synced 2026-01-20 14:25:16 +00:00
Merge remote-tracking branch 'origin/master' into stage2-zig-cc
This merges in the revert that fixes the broken Windows build of master branch.
This commit is contained in:
commit
af64fd2f42
@ -1,265 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2015-2020 Zig Contributors
|
||||
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
|
||||
// The MIT license requires this copyright notice to be included in all copies
|
||||
// and substantial portions of the software.
|
||||
const builtin = @import("builtin");
|
||||
const std = @import("std.zig");
|
||||
const math = std.math;
|
||||
const debug = std.debug;
|
||||
const assert = std.debug.assert;
|
||||
const testing = std.testing;
|
||||
|
||||
/// There is a trade off of how quickly to fill a bloom filter;
|
||||
/// the number of items is:
|
||||
/// n_items / K * ln(2)
|
||||
/// the rate of false positives is:
|
||||
/// (1-e^(-K*N/n_items))^K
|
||||
/// where N is the number of items
|
||||
pub fn BloomFilter(
|
||||
/// Size of bloom filter in cells, must be a power of two.
|
||||
comptime n_items: usize,
|
||||
/// Number of cells to set per item
|
||||
comptime K: usize,
|
||||
/// Cell type, should be:
|
||||
/// - `bool` for a standard bloom filter
|
||||
/// - an unsigned integer type for a counting bloom filter
|
||||
comptime Cell: type,
|
||||
/// endianess of the Cell
|
||||
comptime endian: builtin.Endian,
|
||||
/// Hash function to use
|
||||
comptime hash: fn (out: []u8, Ki: usize, in: []const u8) void,
|
||||
) type {
|
||||
assert(n_items > 0);
|
||||
assert(math.isPowerOfTwo(n_items));
|
||||
assert(K > 0);
|
||||
const cellEmpty = if (Cell == bool) false else @as(Cell, 0);
|
||||
const cellMax = if (Cell == bool) true else math.maxInt(Cell);
|
||||
const n_bytes = (n_items * comptime std.meta.bitCount(Cell)) / 8;
|
||||
assert(n_bytes > 0);
|
||||
const Io = std.packed_int_array.PackedIntIo(Cell, endian);
|
||||
|
||||
return struct {
|
||||
const Self = @This();
|
||||
pub const items = n_items;
|
||||
pub const Index = math.IntFittingRange(0, n_items - 1);
|
||||
|
||||
data: [n_bytes]u8 = [_]u8{0} ** n_bytes,
|
||||
|
||||
pub fn reset(self: *Self) void {
|
||||
std.mem.set(u8, self.data[0..], 0);
|
||||
}
|
||||
|
||||
pub fn @"union"(x: Self, y: Self) Self {
|
||||
var r = Self{ .data = undefined };
|
||||
inline for (x.data) |v, i| {
|
||||
r.data[i] = v | y.data[i];
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
pub fn intersection(x: Self, y: Self) Self {
|
||||
var r = Self{ .data = undefined };
|
||||
inline for (x.data) |v, i| {
|
||||
r.data[i] = v & y.data[i];
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
pub fn getCell(self: Self, cell: Index) Cell {
|
||||
return Io.get(&self.data, cell, 0);
|
||||
}
|
||||
|
||||
pub fn incrementCell(self: *Self, cell: Index) void {
|
||||
if (Cell == bool or Cell == u1) {
|
||||
// skip the 'get' operation
|
||||
Io.set(&self.data, cell, 0, cellMax);
|
||||
} else {
|
||||
const old = Io.get(&self.data, cell, 0);
|
||||
if (old != cellMax) {
|
||||
Io.set(&self.data, cell, 0, old + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clearCell(self: *Self, cell: Index) void {
|
||||
Io.set(&self.data, cell, 0, cellEmpty);
|
||||
}
|
||||
|
||||
pub fn add(self: *Self, item: []const u8) void {
|
||||
comptime var i = 0;
|
||||
inline while (i < K) : (i += 1) {
|
||||
var K_th_bit: packed struct {
|
||||
x: Index,
|
||||
} = undefined;
|
||||
hash(std.mem.asBytes(&K_th_bit), i, item);
|
||||
incrementCell(self, K_th_bit.x);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains(self: Self, item: []const u8) bool {
|
||||
comptime var i = 0;
|
||||
inline while (i < K) : (i += 1) {
|
||||
var K_th_bit: packed struct {
|
||||
x: Index,
|
||||
} = undefined;
|
||||
hash(std.mem.asBytes(&K_th_bit), i, item);
|
||||
if (getCell(self, K_th_bit.x) == cellEmpty)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn resize(self: Self, comptime newsize: usize) BloomFilter(newsize, K, Cell, endian, hash) {
|
||||
var r: BloomFilter(newsize, K, Cell, endian, hash) = undefined;
|
||||
if (newsize < n_items) {
|
||||
std.mem.copy(u8, r.data[0..], self.data[0..r.data.len]);
|
||||
var copied: usize = r.data.len;
|
||||
while (copied < self.data.len) : (copied += r.data.len) {
|
||||
for (self.data[copied .. copied + r.data.len]) |s, i| {
|
||||
r.data[i] |= s;
|
||||
}
|
||||
}
|
||||
} else if (newsize == n_items) {
|
||||
r = self;
|
||||
} else if (newsize > n_items) {
|
||||
var copied: usize = 0;
|
||||
while (copied < r.data.len) : (copied += self.data.len) {
|
||||
std.mem.copy(u8, r.data[copied .. copied + self.data.len], &self.data);
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/// Returns number of non-zero cells
|
||||
pub fn popCount(self: Self) Index {
|
||||
var n: Index = 0;
|
||||
if (Cell == bool or Cell == u1) {
|
||||
for (self.data) |b, i| {
|
||||
n += @popCount(u8, b);
|
||||
}
|
||||
} else {
|
||||
var i: usize = 0;
|
||||
while (i < n_items) : (i += 1) {
|
||||
const cell = self.getCell(@intCast(Index, i));
|
||||
n += if (if (Cell == bool) cell else cell > 0) @as(Index, 1) else @as(Index, 0);
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
pub fn estimateItems(self: Self) f64 {
|
||||
const m = comptime @intToFloat(f64, n_items);
|
||||
const k = comptime @intToFloat(f64, K);
|
||||
const X = @intToFloat(f64, self.popCount());
|
||||
return (comptime (-m / k)) * math.log1p(X * comptime (-1 / m));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn hashFunc(out: []u8, Ki: usize, in: []const u8) void {
|
||||
var st = std.crypto.hash.Gimli.init(.{});
|
||||
st.update(std.mem.asBytes(&Ki));
|
||||
st.update(in);
|
||||
st.final(out);
|
||||
}
|
||||
|
||||
test "std.BloomFilter" {
|
||||
// https://github.com/ziglang/zig/issues/5127
|
||||
if (std.Target.current.cpu.arch == .mips) return error.SkipZigTest;
|
||||
|
||||
inline for ([_]type{ bool, u1, u2, u3, u4 }) |Cell| {
|
||||
const emptyCell = if (Cell == bool) false else @as(Cell, 0);
|
||||
const BF = BloomFilter(128 * 8, 8, Cell, builtin.endian, hashFunc);
|
||||
var bf = BF{};
|
||||
var i: usize = undefined;
|
||||
// confirm that it is initialised to the empty filter
|
||||
i = 0;
|
||||
while (i < BF.items) : (i += 1) {
|
||||
testing.expectEqual(emptyCell, bf.getCell(@intCast(BF.Index, i)));
|
||||
}
|
||||
testing.expectEqual(@as(BF.Index, 0), bf.popCount());
|
||||
testing.expectEqual(@as(f64, 0), bf.estimateItems());
|
||||
// fill in a few items
|
||||
bf.incrementCell(42);
|
||||
bf.incrementCell(255);
|
||||
bf.incrementCell(256);
|
||||
bf.incrementCell(257);
|
||||
// check that they were set
|
||||
testing.expectEqual(true, bf.getCell(42) != emptyCell);
|
||||
testing.expectEqual(true, bf.getCell(255) != emptyCell);
|
||||
testing.expectEqual(true, bf.getCell(256) != emptyCell);
|
||||
testing.expectEqual(true, bf.getCell(257) != emptyCell);
|
||||
// clear just one of them; make sure the rest are still set
|
||||
bf.clearCell(256);
|
||||
testing.expectEqual(true, bf.getCell(42) != emptyCell);
|
||||
testing.expectEqual(true, bf.getCell(255) != emptyCell);
|
||||
testing.expectEqual(false, bf.getCell(256) != emptyCell);
|
||||
testing.expectEqual(true, bf.getCell(257) != emptyCell);
|
||||
// reset any of the ones we've set and confirm we're back to the empty filter
|
||||
bf.clearCell(42);
|
||||
bf.clearCell(255);
|
||||
bf.clearCell(257);
|
||||
i = 0;
|
||||
while (i < BF.items) : (i += 1) {
|
||||
testing.expectEqual(emptyCell, bf.getCell(@intCast(BF.Index, i)));
|
||||
}
|
||||
testing.expectEqual(@as(BF.Index, 0), bf.popCount());
|
||||
testing.expectEqual(@as(f64, 0), bf.estimateItems());
|
||||
|
||||
// Lets add a string
|
||||
bf.add("foo");
|
||||
testing.expectEqual(true, bf.contains("foo"));
|
||||
{
|
||||
// try adding same string again. make sure popcount is the same
|
||||
const old_popcount = bf.popCount();
|
||||
testing.expect(old_popcount > 0);
|
||||
bf.add("foo");
|
||||
testing.expectEqual(true, bf.contains("foo"));
|
||||
testing.expectEqual(old_popcount, bf.popCount());
|
||||
}
|
||||
|
||||
// Get back to empty filter via .reset
|
||||
bf.reset();
|
||||
// Double check that .reset worked
|
||||
i = 0;
|
||||
while (i < BF.items) : (i += 1) {
|
||||
testing.expectEqual(emptyCell, bf.getCell(@intCast(BF.Index, i)));
|
||||
}
|
||||
testing.expectEqual(@as(BF.Index, 0), bf.popCount());
|
||||
testing.expectEqual(@as(f64, 0), bf.estimateItems());
|
||||
|
||||
comptime var teststrings = [_][]const u8{
|
||||
"foo",
|
||||
"bar",
|
||||
"a longer string",
|
||||
"some more",
|
||||
"the quick brown fox",
|
||||
"unique string",
|
||||
};
|
||||
inline for (teststrings) |str| {
|
||||
bf.add(str);
|
||||
}
|
||||
inline for (teststrings) |str| {
|
||||
testing.expectEqual(true, bf.contains(str));
|
||||
}
|
||||
|
||||
{ // estimate should be close for low packing
|
||||
const est = bf.estimateItems();
|
||||
testing.expect(est > @intToFloat(f64, teststrings.len) - 1);
|
||||
testing.expect(est < @intToFloat(f64, teststrings.len) + 1);
|
||||
}
|
||||
|
||||
const larger_bf = bf.resize(4096);
|
||||
inline for (teststrings) |str| {
|
||||
testing.expectEqual(true, larger_bf.contains(str));
|
||||
}
|
||||
testing.expectEqual(@as(u12, bf.popCount()) * (4096 / 1024), larger_bf.popCount());
|
||||
|
||||
const smaller_bf = bf.resize(64);
|
||||
inline for (teststrings) |str| {
|
||||
testing.expectEqual(true, smaller_bf.contains(str));
|
||||
}
|
||||
testing.expect(bf.popCount() <= @as(u10, smaller_bf.popCount()) * (1024 / 64));
|
||||
}
|
||||
}
|
||||
@ -813,26 +813,3 @@ fn run_lock_file_test(contexts: []FileLockTestContext) !void {
|
||||
try threads.append(try std.Thread.spawn(ctx, FileLockTestContext.run));
|
||||
}
|
||||
}
|
||||
|
||||
test "deleteDir" {
|
||||
var tmp_dir = tmpDir(.{});
|
||||
defer tmp_dir.cleanup();
|
||||
|
||||
// deleting a non-existent directory
|
||||
testing.expectError(error.FileNotFound, tmp_dir.dir.deleteDir("test_dir"));
|
||||
|
||||
var dir = try tmp_dir.dir.makeOpenPath("test_dir", .{});
|
||||
var file = try dir.createFile("test_file", .{});
|
||||
file.close();
|
||||
dir.close();
|
||||
|
||||
// deleting a non-empty directory
|
||||
testing.expectError(error.DirNotEmpty, tmp_dir.dir.deleteDir("test_dir"));
|
||||
|
||||
dir = try tmp_dir.dir.openDir("test_dir", .{});
|
||||
try dir.deleteFile("test_file");
|
||||
dir.close();
|
||||
|
||||
// deleting an empty directory
|
||||
try tmp_dir.dir.deleteDir("test_dir");
|
||||
}
|
||||
|
||||
@ -765,7 +765,6 @@ pub const DeleteFileError = error{
|
||||
Unexpected,
|
||||
NotDir,
|
||||
IsDir,
|
||||
DirNotEmpty,
|
||||
};
|
||||
|
||||
pub const DeleteFileOptions = struct {
|
||||
@ -820,7 +819,7 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil
|
||||
0,
|
||||
);
|
||||
switch (rc) {
|
||||
.SUCCESS => CloseHandle(tmp_handle),
|
||||
.SUCCESS => return CloseHandle(tmp_handle),
|
||||
.OBJECT_NAME_INVALID => unreachable,
|
||||
.OBJECT_NAME_NOT_FOUND => return error.FileNotFound,
|
||||
.INVALID_PARAMETER => unreachable,
|
||||
@ -829,21 +828,6 @@ pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFil
|
||||
.SHARING_VIOLATION => return error.FileBusy,
|
||||
else => return unexpectedStatus(rc),
|
||||
}
|
||||
|
||||
// If a directory fails to be deleted, CloseHandle will still report success
|
||||
// Check if the directory still exists and return error.DirNotEmpty if true
|
||||
if (options.remove_dir) {
|
||||
var basic_info: FILE_BASIC_INFORMATION = undefined;
|
||||
switch (ntdll.NtQueryAttributesFile(&attr, &basic_info)) {
|
||||
.SUCCESS => return error.DirNotEmpty,
|
||||
.OBJECT_NAME_NOT_FOUND => return,
|
||||
.OBJECT_PATH_NOT_FOUND => return,
|
||||
.INVALID_PARAMETER => unreachable,
|
||||
.ACCESS_DENIED => return error.AccessDenied,
|
||||
.OBJECT_PATH_SYNTAX_BAD => unreachable,
|
||||
else => |urc| return unexpectedStatus(urc),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub const MoveFileError = error{ FileNotFound, Unexpected };
|
||||
|
||||
633
lib/std/rb.zig
633
lib/std/rb.zig
@ -1,633 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2015-2020 Zig Contributors
|
||||
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
|
||||
// The MIT license requires this copyright notice to be included in all copies
|
||||
// and substantial portions of the software.
|
||||
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};
|
||||
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,
|
||||
/// };
|
||||
/// fn number(node: *rb.Node) Number {
|
||||
/// return @fieldParentPtr(Number, "node", node);
|
||||
/// }
|
||||
pub const Node = struct {
|
||||
left: ?*Node,
|
||||
right: ?*Node,
|
||||
|
||||
/// 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) {
|
||||
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) {
|
||||
var parent = node.getParent();
|
||||
if (parent) |p| {
|
||||
if (node != p.left)
|
||||
return p;
|
||||
node = p;
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn isRoot(node: *Node) bool {
|
||||
return node.getParent() == null;
|
||||
}
|
||||
|
||||
fn isRed(node: *Node) bool {
|
||||
return node.getColor() == Red;
|
||||
}
|
||||
|
||||
fn isBlack(node: *Node) bool {
|
||||
return node.getColor() == Black;
|
||||
}
|
||||
|
||||
fn setParent(node: *Node, parent: ?*Node) void {
|
||||
node.parent_and_color = @ptrToInt(parent) | (node.parent_and_color & 1);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
fn setColor(node: *Node, color: Color) void {
|
||||
const mask: usize = 1;
|
||||
node.parent_and_color = (node.parent_and_color & ~mask) | @enumToInt(color);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
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;
|
||||
|
||||
maybe_key = doLookup(node, tree, &maybe_parent, &is_left);
|
||||
if (maybe_key) |key| {
|
||||
return key;
|
||||
}
|
||||
|
||||
node.left = null;
|
||||
node.right = null;
|
||||
node.setColor(Red);
|
||||
node.setParent(maybe_parent);
|
||||
|
||||
if (maybe_parent) |parent| {
|
||||
parent.setChild(node, is_left);
|
||||
} else {
|
||||
tree.root = node;
|
||||
}
|
||||
|
||||
while (node.getParent()) |*parent| {
|
||||
if (parent.*.isBlack())
|
||||
break;
|
||||
// the root is always black
|
||||
var grandpa = parent.*.getParent() orelse unreachable;
|
||||
|
||||
if (parent.* == grandpa.left) {
|
||||
var maybe_uncle = grandpa.right;
|
||||
|
||||
if (maybe_uncle) |uncle| {
|
||||
if (uncle.isBlack())
|
||||
break;
|
||||
|
||||
parent.*.setColor(Black);
|
||||
uncle.setColor(Black);
|
||||
grandpa.setColor(Red);
|
||||
node = grandpa;
|
||||
} else {
|
||||
if (node == parent.*.right) {
|
||||
rotateLeft(parent.*, tree);
|
||||
node = parent.*;
|
||||
parent.* = node.getParent().?; // Just rotated
|
||||
}
|
||||
parent.*.setColor(Black);
|
||||
grandpa.setColor(Red);
|
||||
rotateRight(grandpa, tree);
|
||||
}
|
||||
} else {
|
||||
var maybe_uncle = grandpa.left;
|
||||
|
||||
if (maybe_uncle) |uncle| {
|
||||
if (uncle.isBlack())
|
||||
break;
|
||||
|
||||
parent.*.setColor(Black);
|
||||
uncle.setColor(Black);
|
||||
grandpa.setColor(Red);
|
||||
node = grandpa;
|
||||
} else {
|
||||
if (node == parent.*.left) {
|
||||
rotateRight(parent.*, tree);
|
||||
node = parent.*;
|
||||
parent.* = node.getParent().?; // Just rotated
|
||||
}
|
||||
parent.*.setColor(Black);
|
||||
grandpa.setColor(Red);
|
||||
rotateLeft(grandpa, tree);
|
||||
}
|
||||
}
|
||||
}
|
||||
// This was an insert, there is at least one node.
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
color = node.getColor();
|
||||
newnode = null;
|
||||
} else {
|
||||
if (node.left == null) {
|
||||
next = node.right.?; // Not both null as per above
|
||||
} else if (node.right == null) {
|
||||
next = node.left.?; // Not both null as per above
|
||||
} else
|
||||
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.?;
|
||||
|
||||
color = next.getColor();
|
||||
next.setColor(node.getColor());
|
||||
|
||||
next.left = left;
|
||||
left.setParent(next);
|
||||
|
||||
if (next != right) {
|
||||
var parent = next.getParent().?; // Was traversed via child node (right/left)
|
||||
next.setParent(node.getParent());
|
||||
|
||||
newnode = next.right;
|
||||
parent.left = node;
|
||||
|
||||
next.right = right;
|
||||
right.setParent(next);
|
||||
} else {
|
||||
next.setParent(maybe_parent);
|
||||
maybe_parent = next;
|
||||
newnode = next.right;
|
||||
}
|
||||
} else {
|
||||
color = node.getColor();
|
||||
newnode = next;
|
||||
}
|
||||
}
|
||||
|
||||
if (newnode) |n|
|
||||
n.setParent(maybe_parent);
|
||||
|
||||
if (color == Red)
|
||||
return;
|
||||
if (newnode) |n| {
|
||||
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.
|
||||
|
||||
if (sibling.isRed()) {
|
||||
sibling.setColor(Black);
|
||||
parent.setColor(Red);
|
||||
rotateLeft(parent, tree);
|
||||
sibling = parent.right.?; // Just rotated
|
||||
}
|
||||
if ((if (sibling.left) |n| n.isBlack() else true) and
|
||||
(if (sibling.right) |n| n.isBlack() else true))
|
||||
{
|
||||
sibling.setColor(Red);
|
||||
node = parent;
|
||||
maybe_parent = parent.getParent();
|
||||
continue;
|
||||
}
|
||||
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
|
||||
}
|
||||
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.
|
||||
|
||||
if (sibling.isRed()) {
|
||||
sibling.setColor(Black);
|
||||
parent.setColor(Red);
|
||||
rotateRight(parent, tree);
|
||||
sibling = parent.left.?; // Just rotated
|
||||
}
|
||||
if ((if (sibling.left) |n| n.isBlack() else true) and
|
||||
(if (sibling.right) |n| n.isBlack() else true))
|
||||
{
|
||||
sibling.setColor(Red);
|
||||
node = parent;
|
||||
maybe_parent = parent.getParent();
|
||||
continue;
|
||||
}
|
||||
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
|
||||
}
|
||||
sibling.setColor(parent.getColor());
|
||||
parent.setColor(Black);
|
||||
sibling.left.?.setColor(Black); // Same number of black nodes
|
||||
rotateRight(parent, tree);
|
||||
newnode = tree.root;
|
||||
break;
|
||||
}
|
||||
|
||||
if (node.isRed())
|
||||
break;
|
||||
}
|
||||
|
||||
if (newnode) |n|
|
||||
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;
|
||||
|
||||
if (old.getParent()) |parent| {
|
||||
parent.setChild(new, parent.left == old);
|
||||
} else
|
||||
tree.root = new;
|
||||
|
||||
if (old.left) |left|
|
||||
left.setParent(new);
|
||||
if (old.right) |right|
|
||||
right.setParent(new);
|
||||
|
||||
new.* = old.*;
|
||||
}
|
||||
|
||||
pub fn init(f: fn (*Node, *Node, *Tree) Order) Tree {
|
||||
return Tree{
|
||||
.root = null,
|
||||
.compareFn = f,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
fn rotateLeft(node: *Node, tree: *Tree) void {
|
||||
var p: *Node = node;
|
||||
var q: *Node = node.right orelse unreachable;
|
||||
var parent: *Node = undefined;
|
||||
|
||||
if (!p.isRoot()) {
|
||||
parent = p.getParent().?;
|
||||
if (parent.left == p) {
|
||||
parent.left = q;
|
||||
} else {
|
||||
parent.right = q;
|
||||
}
|
||||
q.setParent(parent);
|
||||
} else {
|
||||
tree.root = q;
|
||||
q.setParent(null);
|
||||
}
|
||||
p.setParent(q);
|
||||
|
||||
p.right = q.left;
|
||||
if (p.right) |right| {
|
||||
right.setParent(p);
|
||||
}
|
||||
q.left = p;
|
||||
}
|
||||
|
||||
fn rotateRight(node: *Node, tree: *Tree) void {
|
||||
var p: *Node = node;
|
||||
var q: *Node = node.left orelse unreachable;
|
||||
var parent: *Node = undefined;
|
||||
|
||||
if (!p.isRoot()) {
|
||||
parent = p.getParent().?;
|
||||
if (parent.left == p) {
|
||||
parent.left = q;
|
||||
} else {
|
||||
parent.right = q;
|
||||
}
|
||||
q.setParent(parent);
|
||||
} else {
|
||||
tree.root = q;
|
||||
q.setParent(null);
|
||||
}
|
||||
p.setParent(q);
|
||||
|
||||
p.left = q.right;
|
||||
if (p.left) |left| {
|
||||
left.setParent(p);
|
||||
}
|
||||
q.right = p;
|
||||
}
|
||||
|
||||
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" {
|
||||
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" {
|
||||
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);
|
||||
}
|
||||
@ -14,7 +14,6 @@ pub const AutoArrayHashMap = array_hash_map.AutoArrayHashMap;
|
||||
pub const AutoArrayHashMapUnmanaged = array_hash_map.AutoArrayHashMapUnmanaged;
|
||||
pub const AutoHashMap = hash_map.AutoHashMap;
|
||||
pub const AutoHashMapUnmanaged = hash_map.AutoHashMapUnmanaged;
|
||||
pub const BloomFilter = @import("bloom_filter.zig").BloomFilter;
|
||||
pub const BufMap = @import("buf_map.zig").BufMap;
|
||||
pub const BufSet = @import("buf_set.zig").BufSet;
|
||||
pub const ChildProcess = @import("child_process.zig").ChildProcess;
|
||||
@ -77,7 +76,6 @@ pub const packed_int_array = @import("packed_int_array.zig");
|
||||
pub const pdb = @import("pdb.zig");
|
||||
pub const process = @import("process.zig");
|
||||
pub const rand = @import("rand.zig");
|
||||
pub const rb = @import("rb.zig");
|
||||
pub const sort = @import("sort.zig");
|
||||
pub const ascii = @import("ascii.zig");
|
||||
pub const testing = @import("testing.zig");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user