fcoin/lib/utils/rbt.js
2017-07-31 18:21:02 -07:00

900 lines
15 KiB
JavaScript

/*!
* rbt.js - iterative red black tree for bcoin
* Copyright (c) 2016-2017, Christopher Jeffrey (MIT License).
* https://github.com/bcoin-org/bcoin
*/
'use strict';
const assert = require('assert');
const RED = 0;
const BLACK = 1;
let SENTINEL;
/**
* An iterative red black tree.
* @alias module:utils.RBT
* @constructor
* @param {Function} compare - Comparator.
* @param {Boolean?} unique
*/
function RBT(compare, unique) {
if (!(this instanceof RBT))
return new RBT(compare, unique);
assert(typeof compare === 'function');
this.root = SENTINEL;
this.compare = compare;
this.unique = unique || false;
}
/**
* Clear the tree.
*/
RBT.prototype.reset = function reset() {
this.root = SENTINEL;
};
/**
* Do a key lookup.
* @param {Buffer|String} key
* @returns {Buffer?} value
*/
RBT.prototype.search = function search(key) {
let current = this.root;
while (!current.isNull()) {
const cmp = this.compare(key, current.key);
if (cmp === 0)
return current;
if (cmp < 0)
current = current.left;
else
current = current.right;
}
return null;
};
/**
* Insert a record.
* @param {Buffer|String} key
* @param {Buffer} value
*/
RBT.prototype.insert = function insert(key, value) {
let current = this.root;
let left = false;
let parent;
while (!current.isNull()) {
const cmp = this.compare(key, current.key);
if (this.unique && cmp === 0) {
current.key = key;
current.value = value;
return current;
}
parent = current;
if (cmp < 0) {
left = true;
current = current.left;
} else {
left = false;
current = current.right;
}
}
const node = new RBTNode(key, value);
if (!parent) {
this.root = node;
this.insertFixup(node);
return node;
}
node.parent = parent;
if (left)
parent.left = node;
else
parent.right = node;
this.insertFixup(node);
return node;
};
/**
* Repaint necessary nodes after insertion.
* @private
* @param {RBTNode} x
*/
RBT.prototype.insertFixup = function insertFixup(x) {
x.color = RED;
while (x !== this.root && x.parent.color === RED) {
if (x.parent === x.parent.parent.left) {
const y = x.parent.parent.right;
if (!y.isNull() && y.color === RED) {
x.parent.color = BLACK;
y.color = BLACK;
x.parent.parent.color = RED;
x = x.parent.parent;
} else {
if (x === x.parent.right) {
x = x.parent;
this.rotl(x);
}
x.parent.color = BLACK;
x.parent.parent.color = RED;
this.rotr(x.parent.parent);
}
} else {
const y = x.parent.parent.left;
if (!y.isNull() && y.color === RED) {
x.parent.color = BLACK;
y.color = BLACK;
x.parent.parent.color = RED;
x = x.parent.parent;
} else {
if (x === x.parent.left) {
x = x.parent;
this.rotr(x);
}
x.parent.color = BLACK;
x.parent.parent.color = RED;
this.rotl(x.parent.parent);
}
}
}
this.root.color = BLACK;
};
/**
* Remove a record.
* @param {Buffer|String} key
* @returns {Boolean}
*/
RBT.prototype.remove = function remove(key) {
let current = this.root;
while (!current.isNull()) {
const cmp = this.compare(key, current.key);
if (cmp === 0) {
this.removeNode(current);
return current;
}
if (cmp < 0)
current = current.left;
else
current = current.right;
}
return null;
};
/**
* Remove a single node.
* @private
* @param {RBTNode} z
*/
RBT.prototype.removeNode = function removeNode(z) {
let y = z;
if (!z.left.isNull() && !z.right.isNull())
y = this.successor(z);
const x = y.left.isNull() ? y.right : y.left;
x.parent = y.parent;
if (y.parent.isNull()) {
this.root = x;
} else {
if (y === y.parent.left)
y.parent.left = x;
else
y.parent.right = x;
}
if (y !== z) {
z.key = y.key;
z.value = y.value;
}
if (y.color === BLACK)
this.removeFixup(x);
};
/**
* Repaint necessary nodes after removal.
* @private
* @param {RBTNode} x
*/
RBT.prototype.removeFixup = function removeFixup(x) {
while (x !== this.root && x.color === BLACK) {
if (x === x.parent.left) {
let w = x.parent.right;
if (w.color === RED) {
w.color = BLACK;
x.parent.color = RED;
this.rotl(x.parent);
w = x.parent.right;
}
if (w.left.color === BLACK && w.right.color === BLACK) {
w.color = RED;
x = x.parent;
} else {
if (w.right.color === BLACK) {
w.left.color = BLACK;
w.color = RED;
this.rotr(w);
w = x.parent.right;
}
w.color = x.parent.color;
x.parent.color = BLACK;
w.right.color = BLACK;
this.rotl(x.parent);
x = this.root;
}
} else {
let w = x.parent.left;
if (w.color === RED) {
w.color = BLACK;
x.parent.color = RED;
this.rotr(x.parent);
w = x.parent.left;
}
if (w.right.color === BLACK && w.left.color === BLACK) {
w.color = RED;
x = x.parent;
} else {
if (w.left.color === BLACK) {
w.right.color = BLACK;
w.color = RED;
this.rotl(w);
w = x.parent.left;
}
w.color = x.parent.color;
x.parent.color = BLACK;
w.left.color = BLACK;
this.rotr(x.parent);
x = this.root;
}
}
}
x.color = BLACK;
};
/**
* Do a left rotate.
* @private
* @param {RBTNode} x
*/
RBT.prototype.rotl = function rotl(x) {
const y = x.right;
x.right = y.left;
if (!y.left.isNull())
y.left.parent = x;
y.parent = x.parent;
if (x.parent.isNull()) {
this.root = y;
} else {
if (x === x.parent.left)
x.parent.left = y;
else
x.parent.right = y;
}
y.left = x;
x.parent = y;
};
/**
* Do a right rotate.
* @private
* @param {RBTNode} x
*/
RBT.prototype.rotr = function rotr(x) {
const y = x.left;
x.left = y.right;
if (!y.right.isNull())
y.right.parent = x;
y.parent = x.parent;
if (x.parent.isNull()) {
this.root = y;
} else {
if (x === x.parent.right)
x.parent.right = y;
else
x.parent.left = y;
}
y.right = x;
x.parent = y;
};
/**
* Minimum subtree.
* @private
* @param {RBTNode} z
* @returns {RBTNode}
*/
RBT.prototype.min = function min(z) {
if (z.isNull())
return z;
while (!z.left.isNull())
z = z.left;
return z;
};
/**
* Maximum subtree.
* @private
* @param {RBTNode} z
* @returns {RBTNode}
*/
RBT.prototype.max = function max(z) {
if (z.isNull())
return z;
while (!z.right.isNull())
z = z.right;
return z;
};
/**
* Successor node.
* @private
* @param {RBTNode} x
* @returns {RBTNode}
*/
RBT.prototype.successor = function successor(x) {
if (!x.right.isNull()) {
x = x.right;
while (!x.left.isNull())
x = x.left;
return x;
}
let y = x.parent;
while (!y.isNull() && x === y.right) {
x = y;
y = y.parent;
}
return y;
};
/**
* Predecessor node.
* @private
* @param {RBTNode} x
* @returns {RBTNode}
*/
RBT.prototype.predecessor = function predecessor(x) {
if (!x.left.isNull()) {
x = x.left;
while (!x.right.isNull())
x = x.right;
return x;
}
let y = x.parent;
while (!y.isNull() && x === y.left) {
x = y;
y = y.parent;
}
return y;
};
/**
* Take a snapshot and return
* a cloned root node (iterative).
* @returns {RBTNode}
*/
RBT.prototype.clone = function clone() {
if (this.root.isNull())
return SENTINEL;
const stack = [];
let current = this.root;
let left = true;
let parent, snapshot;
for (;;) {
if (!current.isNull()) {
const copy = current.clone();
if (parent)
copy.parent = parent;
if (left) {
if (parent)
parent.left = copy;
else
snapshot = copy;
} else {
if (parent)
parent.right = copy;
else
snapshot = copy;
}
stack.push(copy);
parent = copy;
left = true;
current = current.left;
continue;
}
if (stack.length === 0)
break;
current = stack.pop();
parent = current;
left = false;
current = current.right;
}
assert(snapshot);
return snapshot;
};
/**
* Take a snapshot and return
* a cloned root node (recursive).
* @returns {RBTNode}
*/
RBT.prototype.snapshot = function snapshot() {
if (this.root.isNull())
return SENTINEL;
const node = this.root.clone();
copyLeft(node, node.left);
copyRight(node, node.right);
return node;
};
/**
* Create an iterator.
* @param {RBTNode?} snapshot
* @returns {Iterator}
*/
RBT.prototype.iterator = function iterator(snapshot) {
return new Iterator(this, snapshot || this.root);
};
/**
* Traverse between a range of keys and collect records.
* @param {Buffer} min
* @param {Buffer} max
* @returns {RBTNode[]} Records.
*/
RBT.prototype.range = function range(min, max) {
const iter = this.iterator();
const items = [];
if (min)
iter.seekMin(min);
else
iter.seekFirst();
while (iter.next()) {
if (max && iter.compare(max) > 0)
break;
items.push(iter.data());
}
return items;
};
/**
* Iterator
* @constructor
* @ignore
* @param {RBT} tree
* @param {RBTNode} snapshot
* @property {RBT} tree
* @property {RBTNode} current
* @property {Object} key
* @property {Object} value
*/
function Iterator(tree, snapshot) {
this.tree = tree;
this.root = snapshot;
this.current = snapshot;
this.key = null;
this.value = null;
}
/**
* Compare keys using tree's comparator.
* @param {Object} key
*/
Iterator.prototype.compare = function compare(key) {
assert(this.key != null, 'No key.');
return this.tree.compare(this.key, key);
};
/**
* Test whether current node is valid.
*/
Iterator.prototype.valid = function valid() {
return !this.current.isNull();
};
/**
* Seek to the root.
*/
Iterator.prototype.reset = function reset() {
this.current = this.root;
this.key = null;
this.value = null;
};
/**
* Seek to the start of the tree.
*/
Iterator.prototype.seekFirst = function seekFirst() {
this.current = this.tree.min(this.root);
this.key = this.current.key;
this.value = this.current.value;
};
/**
* Seek to the end of the tree.
*/
Iterator.prototype.seekLast = function seekLast() {
this.current = this.tree.max(this.root);
this.key = this.current.key;
this.value = this.current.value;
};
/**
* Seek to a key from the current node (gte).
* @param {String} key
*/
Iterator.prototype.seek = function seek(key) {
return this.seekMin(key);
};
/**
* Seek to a key from the current node (gte).
* @param {String} key
*/
Iterator.prototype.seekMin = function seekMin(key) {
assert(key != null, 'No key passed to seek.');
let root = this.current;
let current = SENTINEL;
while (!root.isNull()) {
const cmp = this.tree.compare(root.key, key);
if (cmp === 0) {
current = root;
break;
}
if (cmp > 0) {
current = root;
root = root.left;
} else {
root = root.right;
}
}
this.current = current;
this.key = current.key;
this.value = current.value;
};
/**
* Seek to a key from the current node (lte).
* @param {String} key
*/
Iterator.prototype.seekMax = function seekMax(key) {
assert(key != null, 'No key passed to seek.');
let root = this.current;
let current = SENTINEL;
while (!root.isNull()) {
const cmp = this.tree.compare(root.key, key);
if (cmp === 0) {
current = root;
break;
}
if (cmp < 0) {
current = root;
root = root.right;
} else {
root = root.left;
}
}
this.current = current;
this.key = current.key;
this.value = current.value;
};
/**
* Seek to previous node.
* @param {String} key
*/
Iterator.prototype.prev = function prev() {
if (this.current.isNull()) {
this.key = null;
this.value = null;
return false;
}
this.key = this.current.key;
this.value = this.current.value;
this.current = this.tree.predecessor(this.current);
return true;
};
/**
* Seek to next node.
* @returns {Boolean}
*/
Iterator.prototype.next = function next() {
if (this.current.isNull()) {
this.key = null;
this.value = null;
return false;
}
this.key = this.current.key;
this.value = this.current.value;
this.current = this.tree.successor(this.current);
return true;
};
/**
* Return the current key/value pair.
* @returns {RBTData}
*/
Iterator.prototype.data = function data() {
assert(this.key != null, 'No data available.');
return new RBTData(this.key, this.value);
};
/**
* RBT Node
* @constructor
* @ignore
* @private
* @param {Buffer} key
* @param {Buffer} value
* @property {Buffer} key
* @property {Buffer} value
* @property {Number} color
* @property {RBTNode|RBTSentinel} parent
* @property {RBTNode|RBTSentinel} left
* @property {RBTNode|RBTSentinel} right
*/
function RBTNode(key, value) {
this.key = key;
this.value = value;
this.color = RED;
this.parent = SENTINEL;
this.left = SENTINEL;
this.right = SENTINEL;
}
/**
* Clone the node.
* @returns {RBTNode}
*/
RBTNode.prototype.clone = function clone() {
const node = new RBTNode(this.key, this.value);
node.color = this.color;
node.parent = this.parent;
node.left = this.left;
node.right = this.right;
return node;
};
/**
* Clone the node (key/value only).
* @returns {RBTData}
*/
RBTNode.prototype.copy = function copy() {
return new RBTData(this.key, this.value);
};
/**
* Inspect the rbt node.
* @returns {Object}
*/
RBTNode.prototype.inspect = function inspect() {
return {
key: this.key,
value: this.value,
color: this.color === RED ? 'red' : 'black',
left: this.left,
right: this.right
};
};
/**
* Test whether the node is a leaf.
* Always returns false.
* @returns {Boolean}
*/
RBTNode.prototype.isNull = function isNull() {
return false;
};
/**
* RBT Sentinel Node
* @constructor
* @ignore
* @property {null} key
* @property {null} value
* @property {Number} [color=BLACK]
* @property {null} parent
* @property {null} left
* @property {null} right
*/
function RBTSentinel() {
this.key = null;
this.value = null;
this.color = BLACK;
this.parent = null;
this.left = null;
this.right = null;
}
/**
* Inspect the rbt node.
* @returns {String}
*/
RBTSentinel.prototype.inspect = function inspect() {
return 'NIL';
};
/**
* Test whether the node is a leaf.
* Always returns true.
* @returns {Boolean}
*/
RBTSentinel.prototype.isNull = function isNull() {
return true;
};
/**
* RBT key/value pair
* @constructor
* @ignore
* @param {Buffer} key
* @param {Buffer} value
* @property {Buffer} key
* @property {Buffer} value
*/
function RBTData(key, value) {
this.key = key;
this.value = value;
}
/**
* Inspect the rbt data.
* @returns {Object}
*/
RBTData.prototype.inspect = function inspect() {
return {
key: this.key,
value: this.value
};
};
/*
* Helpers
*/
SENTINEL = new RBTSentinel();
function copyLeft(parent, node) {
if (!node.isNull()) {
parent.left = node.clone();
parent.left.parent = parent;
copyLeft(parent.left, node.left);
copyRight(parent.left, node.right);
}
}
function copyRight(parent, node) {
if (!node.isNull()) {
parent.right = node.clone();
parent.right.parent = parent;
copyLeft(parent.right, node.left);
copyRight(parent.right, node.right);
}
}
/*
* Expose
*/
module.exports = RBT;