@deno/kv
Version:
A Deno KV client library optimized for Node.js.
232 lines (231 loc) • 8.47 kB
JavaScript
;
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
Object.defineProperty(exports, "__esModule", { value: true });
exports.RedBlackTree = void 0;
const comparators_js_1 = require("./comparators.js");
const binary_search_tree_js_1 = require("./binary_search_tree.js");
const _red_black_node_js_1 = require("./_red_black_node.js");
/**
* A red-black tree. This is a kind of self-balancing binary search tree. The
* values are in ascending order by default, using JavaScript's built-in
* comparison operators to sort the values.
*
* Red-Black Trees require fewer rotations than AVL Trees, so they can provide
* faster insertions and removal operations. If you need faster lookups, you
* should use an AVL Tree instead. AVL Trees are more strictly balanced than
* Red-Black Trees, so they can provide faster lookups.
*
* | Method | Average Case | Worst Case |
* | ------------- | ------------ | ---------- |
* | find(value) | O(log n) | O(log n) |
* | insert(value) | O(log n) | O(log n) |
* | remove(value) | O(log n) | O(log n) |
* | min() | O(log n) | O(log n) |
* | max() | O(log n) | O(log n) |
*
* @example
* ```ts
* import {
* ascend,
* descend,
* RedBlackTree,
* } from "https://deno.land/std@$STD_VERSION/data_structures/mod.ts";
* import { assertEquals } from "https://deno.land/std@$STD_VERSION/assert/assert_equals.ts";
*
* const values = [3, 10, 13, 4, 6, 7, 1, 14];
* const tree = new RedBlackTree<number>();
* values.forEach((value) => tree.insert(value));
* assertEquals([...tree], [1, 3, 4, 6, 7, 10, 13, 14]);
* assertEquals(tree.min(), 1);
* assertEquals(tree.max(), 14);
* assertEquals(tree.find(42), null);
* assertEquals(tree.find(7), 7);
* assertEquals(tree.remove(42), false);
* assertEquals(tree.remove(7), true);
* assertEquals([...tree], [1, 3, 4, 6, 10, 13, 14]);
*
* const invertedTree = new RedBlackTree<number>(descend);
* values.forEach((value) => invertedTree.insert(value));
* assertEquals([...invertedTree], [14, 13, 10, 7, 6, 4, 3, 1]);
* assertEquals(invertedTree.min(), 14);
* assertEquals(invertedTree.max(), 1);
* assertEquals(invertedTree.find(42), null);
* assertEquals(invertedTree.find(7), 7);
* assertEquals(invertedTree.remove(42), false);
* assertEquals(invertedTree.remove(7), true);
* assertEquals([...invertedTree], [14, 13, 10, 6, 4, 3, 1]);
*
* const words = new RedBlackTree<string>((a, b) =>
* ascend(a.length, b.length) || ascend(a, b)
* );
* ["truck", "car", "helicopter", "tank", "train", "suv", "semi", "van"]
* .forEach((value) => words.insert(value));
* assertEquals([...words], [
* "car",
* "suv",
* "van",
* "semi",
* "tank",
* "train",
* "truck",
* "helicopter",
* ]);
* assertEquals(words.min(), "car");
* assertEquals(words.max(), "helicopter");
* assertEquals(words.find("scooter"), null);
* assertEquals(words.find("tank"), "tank");
* assertEquals(words.remove("scooter"), false);
* assertEquals(words.remove("tank"), true);
* assertEquals([...words], [
* "car",
* "suv",
* "van",
* "semi",
* "train",
* "truck",
* "helicopter",
* ]);
* ```
*/
class RedBlackTree extends binary_search_tree_js_1.BinarySearchTree {
constructor(compare = comparators_js_1.ascend) {
super(compare);
}
static from(collection, options) {
let result;
let unmappedValues = [];
if (collection instanceof RedBlackTree) {
result = new RedBlackTree(options?.compare ?? collection.compare);
if (options?.compare || options?.map) {
unmappedValues = collection;
}
else {
const nodes = [];
if (collection.root) {
result.root = _red_black_node_js_1.RedBlackNode.from(collection.root);
nodes.push(result.root);
}
while (nodes.length) {
const node = nodes.pop();
const left = node.left
? _red_black_node_js_1.RedBlackNode.from(node.left)
: null;
const right = node.right
? _red_black_node_js_1.RedBlackNode.from(node.right)
: null;
if (left) {
left.parent = node;
nodes.push(left);
}
if (right) {
right.parent = node;
nodes.push(right);
}
}
}
}
else {
result = (options?.compare
? new RedBlackTree(options.compare)
: new RedBlackTree());
unmappedValues = collection;
}
const values = options?.map
? Array.from(unmappedValues, options.map, options.thisArg)
: unmappedValues;
for (const value of values)
result.insert(value);
return result;
}
removeFixup(parent, current) {
while (parent && !current?.red) {
const direction = parent.left === current ? "left" : "right";
const siblingDirection = direction === "right"
? "left"
: "right";
let sibling = parent[siblingDirection];
if (sibling?.red) {
sibling.red = false;
parent.red = true;
this.rotateNode(parent, direction);
sibling = parent[siblingDirection];
}
if (sibling) {
if (!sibling.left?.red && !sibling.right?.red) {
sibling.red = true;
current = parent;
parent = current.parent;
}
else {
if (!sibling[siblingDirection]?.red) {
sibling[direction].red = false;
sibling.red = true;
this.rotateNode(sibling, siblingDirection);
sibling = parent[siblingDirection];
}
sibling.red = parent.red;
parent.red = false;
sibling[siblingDirection].red = false;
this.rotateNode(parent, direction);
current = this.root;
parent = null;
}
}
}
if (current)
current.red = false;
}
/**
* Adds the value to the binary search tree if it does not already exist in it.
* Returns true if successful.
*/
insert(value) {
let node = this.insertNode(_red_black_node_js_1.RedBlackNode, value);
if (node) {
while (node.parent?.red) {
let parent = node.parent;
const parentDirection = parent.directionFromParent();
const uncleDirection = parentDirection === "right"
? "left"
: "right";
const uncle = parent.parent[uncleDirection] ??
null;
if (uncle?.red) {
parent.red = false;
uncle.red = false;
parent.parent.red = true;
node = parent.parent;
}
else {
if (node === parent[uncleDirection]) {
node = parent;
this.rotateNode(node, parentDirection);
parent = node.parent;
}
parent.red = false;
parent.parent.red = true;
this.rotateNode(parent.parent, uncleDirection);
}
}
this.root.red = false;
}
return !!node;
}
/**
* Removes node value from the binary search tree if found.
* Returns true if found and removed.
*/
remove(value) {
const node = this.findNode(value);
if (!node) {
return false;
}
const removedNode = this.removeNode(node);
if (removedNode && !removedNode.red) {
this.removeFixup(removedNode.parent, removedNode.left ?? removedNode.right);
}
return true;
}
}
exports.RedBlackTree = RedBlackTree;