avl-promise
Version:
Largely copied from avl (https://www.npmjs.com/package/avl), but with a Promise-based comparator!
747 lines (621 loc) • 16.8 kB
JavaScript
import Promise from 'bluebird';
import { print, isBalanced } from './utils';
// TODO update doc comments
// TODO remove dead code
// function createNode (parent, left, right, height, key, data) {
// return { parent, left, right, balanceFactor: height, key, data };
// }
/**
* @typedef {{
* parent: ?Node,
* left: ?Node,
* right: ?Node,
* balanceFactor: number,
* key: Key,
* data: Value
* }} Node
*/
/**
* @typedef {*} Key
*/
/**
* @typedef {*} Value
*/
/**
* Default comparison function
* @param {Key} a
* @param {Key} b
* @returns {number}
*/
function DEFAULT_COMPARE_ASYNC (a, b) {
return a > b ?
Promise.resolve(1) :
a < b ?
Promise.resolve(-1) :
Promise.resolve(0);
}
/**
* Single left rotation
* @param {Node} node
* @return {Node}
*/
function rotateLeft (node) {
var rightNode = node.right;
node.right = rightNode.left;
if (rightNode.left) rightNode.left.parent = node;
rightNode.parent = node.parent;
if (rightNode.parent) {
if (rightNode.parent.left === node) {
rightNode.parent.left = rightNode;
} else {
rightNode.parent.right = rightNode;
}
}
node.parent = rightNode;
rightNode.left = node;
node.balanceFactor += 1;
if (rightNode.balanceFactor < 0) {
node.balanceFactor -= rightNode.balanceFactor;
}
rightNode.balanceFactor += 1;
if (node.balanceFactor > 0) {
rightNode.balanceFactor += node.balanceFactor;
}
return rightNode;
}
function rotateRight (node) {
var leftNode = node.left;
node.left = leftNode.right;
if (node.left) node.left.parent = node;
leftNode.parent = node.parent;
if (leftNode.parent) {
if (leftNode.parent.left === node) {
leftNode.parent.left = leftNode;
} else {
leftNode.parent.right = leftNode;
}
}
node.parent = leftNode;
leftNode.right = node;
node.balanceFactor -= 1;
if (leftNode.balanceFactor > 0) {
node.balanceFactor -= leftNode.balanceFactor;
}
leftNode.balanceFactor -= 1;
if (node.balanceFactor < 0) {
leftNode.balanceFactor += node.balanceFactor;
}
return leftNode;
}
// function leftBalance (node) {
// if (node.left.balanceFactor === -1) rotateLeft(node.left);
// return rotateRight(node);
// }
// function rightBalance (node) {
// if (node.right.balanceFactor === 1) rotateRight(node.right);
// return rotateLeft(node);
// }
export default class AVLTree {
/**
* Callback for comparator
* @callback comparatorCallback
* @param {Key} a
* @param {Key} b
* @returns {number}
*/
/**
* @class AVLTree
* @constructor
* @param {comparatorCallback} [comparator]
* @param {boolean} [noDuplicates=false] Disallow duplicates
*/
constructor (comparator, noDuplicates = false, countCompareCalls = false) {
this._root = null;
this._size = 0;
this._countCompareCalls = !!countCompareCalls;
this._compareCallsCounter = 0;
this._noDuplicates = !!noDuplicates;
const incWrapper = (f) => (...args) => {
this._compareCallsCounter++;
return f(...args);
};
this._comparatorAsync = this._countCompareCalls ?
incWrapper(comparator || DEFAULT_COMPARE_ASYNC) :
comparator || DEFAULT_COMPARE_ASYNC;
}
/**
* Clear the tree
* @return {AVLTree}
*/
destroy() {
this._root = null;
return this;
}
/**
* Number of nodes
* @return {number}
*/
get size () {
return this._size;
}
/**
* Whether the tree contains a node with the given key
* @param {Key} key
* @return {boolean} true/false
*/
contains (key) {
if (this._countCompareCalls) {
this._compareCallsCounter = 0;
}
return this._containsAsync(key, this._root);
}
_containsAsync (key, node) {
if (!node) { return Promise.resolve(false); }
return this._comparatorAsync(key, node.key)
.then(cmp => {
if (cmp === 0) return Promise.resolve(true);
if (cmp < 0) return this._containsAsync(key, node.left);
return this._containsAsync(key, node.right);
});
}
/* eslint-disable class-methods-use-this */
/**
* Successor node
* @param {Node} node
* @return {?Node}
*/
next (node) {
var successor = node;
if (successor) {
if (successor.right) {
successor = successor.right;
while (successor && successor.left) successor = successor.left;
} else {
successor = node.parent;
while (successor && successor.right === node) {
node = successor; successor = successor.parent;
}
}
}
return successor;
}
/**
* Predecessor node
* @param {Node} node
* @return {?Node}
*/
prev (node) {
var predecessor = node;
if (predecessor) {
if (predecessor.left) {
predecessor = predecessor.left;
while (predecessor && predecessor.right) predecessor = predecessor.right;
} else {
predecessor = node.parent;
while (predecessor && predecessor.left === node) {
node = predecessor;
predecessor = predecessor.parent;
}
}
}
return predecessor;
}
/* eslint-enable class-methods-use-this */
/**
* Callback for forEach
* @callback forEachCallback
* @param {Node} node
* @param {number} index
*/
/**
* @param {forEachCallback} callback
* @return {AVLTree}
*/
forEach(callback) {
var current = this._root;
var s = [], done = false, i = 0;
while (!done) {
// Reach the left most Node of the current Node
if (current) {
// Place pointer to a tree node on the stack
// before traversing the node's left subtree
s.push(current);
current = current.left;
} else {
// BackTrack from the empty subtree and visit the Node
// at the top of the stack; however, if the stack is
// empty you are done
if (s.length > 0) {
current = s.pop();
callback(current, i++);
// We have visited the node and its left
// subtree. Now, it's right subtree's turn
current = current.right;
} else done = true;
}
}
return this;
}
/**
* Walk key range from `low` to `high`. Stops if `fn` returns a value.
* @param {Key} low
* @param {Key} high
* @param {Function} fn
* @param {*?} ctx
* @return {SplayTree}
*/
range(low, high, fn, ctx) {
if (this._countCompareCalls) {
this._compareCallsCounter = 0;
}
const Q = [];
let node = this._root;
return this._range(Q, node, high, low, fn, ctx);
}
_range (Q, node, high, low, fn, ctx) {
if (Q.length === 0 && !node) return Promise.resolve(this);
if (node) {
Q.push(node);
node = node.left;
return this._range(Q, node, high, low, fn, ctx);
}
node = Q.pop();
return this._comparatorAsync(node.key, high)
.then(cmp => {
if (cmp > 0) return Promise.resolve(this);
return this._comparatorAsync(node.key, low)
.then(cmp => {
if (cmp >= 0) return Promise.resolve(fn.call(ctx, node));
return Promise.resolve();
})
.then(res => {
if (res) return Promise.resolve(this);
node = node.right;
return this._range(Q, node, high, low, fn, ctx);
});
});
}
/**
* Returns all keys in order
* @return {Array<Key>}
*/
keys () {
var current = this._root;
var s = [], r = [], done = false;
while (!done) {
if (current) {
s.push(current);
current = current.left;
} else {
if (s.length > 0) {
current = s.pop();
r.push(current.key);
current = current.right;
} else done = true;
}
}
return r;
}
/**
* Returns `data` fields of all nodes in order.
* @return {Array<Value>}
*/
values () {
var current = this._root;
var s = [], r = [], done = false;
while (!done) {
if (current) {
s.push(current);
current = current.left;
} else {
if (s.length > 0) {
current = s.pop();
r.push(current.data);
current = current.right;
} else done = true;
}
}
return r;
}
/**
* Returns node at given index
* @param {number} index
* @return {?Node}
*/
at (index) {
// removed after a consideration, more misleading than useful
// index = index % this.size;
// if (index < 0) index = this.size - index;
var current = this._root;
var s = [], done = false, i = 0;
while (!done) {
if (current) {
s.push(current);
current = current.left;
} else {
if (s.length > 0) {
current = s.pop();
if (i === index) return current;
i++;
current = current.right;
} else done = true;
}
}
return null;
}
/**
* Returns node with the minimum key
* @return {?Node}
*/
minNode () {
var node = this._root;
if (!node) return null;
while (node.left) node = node.left;
return node;
}
/**
* Returns node with the max key
* @return {?Node}
*/
maxNode () {
var node = this._root;
if (!node) return null;
while (node.right) node = node.right;
return node;
}
/**
* Min key
* @return {?Key}
*/
min () {
var node = this._root;
if (!node) return null;
while (node.left) node = node.left;
return node.key;
}
/**
* Max key
* @return {?Key}
*/
max () {
var node = this._root;
if (!node) return null;
while (node.right) node = node.right;
return node.key;
}
/**
* @return {boolean} true/false
*/
isEmpty() {
return !this._root;
}
/**
* Removes and returns the node with smallest key
* @return {?Node}
*/
pop () {
var node = this._root;
if (node) {
while (node.left) node = node.left;
const res = { key: node.key, data: node.data };
return this.remove(node.key)
.then(() => res);
}
return Promise.resolve(null);
}
/**
* Find node by key
* @param {Key} key
* @return {?Node}
*/
find (key) {
if (this._countCompareCalls) {
this._compareCallsCounter = 0;
}
return this._findAsync(key, this._root);
}
_findAsync (key, node) {
if (!node) return Promise.resolve(null);
return this._comparatorAsync(key, node.key)
.then(cmp => {
if (cmp < 0) return this._findAsync(key, node.left);
if (cmp > 0) return this._findAsync(key, node.right);
return Promise.resolve(node);
});
}
/**
* Insert a node into the tree
* @param {Key} key
* @param {Value} [data]
* @return {?Node}
*/
insert (key, data) {
if (this._countCompareCalls) {
this._compareCallsCounter = 0;
}
return this._insert(key, data);
}
_insert (key, data) {
if (!this._root) {
this._root = {
parent: null, left: null, right: null, balanceFactor: 0,
key, data
};
this._size++;
return Promise.resolve(this._root);
}
return Promise.resolve(this._insertAsync(key, data, this._root, this._noDuplicates));
}
_findParent (key, node) {
return this._comparatorAsync(key, node.key)
.then(cmp => {
if (cmp === 0 && this._noDuplicates) {
return Promise.resolve([node, cmp]);
}
if (cmp <= 0) {
return node.left ?
this._findParent(key, node.left, this._noDuplicates) :
Promise.resolve([node, cmp]);
}
return node.right ?
this._findParent(key, node.right, this._noDuplicates) :
Promise.resolve([node, cmp]);
});
}
_rebalanceInsert (parent, key) {
let newRoot;
if (!parent) {
this._size++;
return Promise.resolve();
}
return this._comparatorAsync(parent.key, key)
.then(cmp => {
if (cmp < 0) parent.balanceFactor -= 1;
else parent.balanceFactor += 1;
if (parent.balanceFactor === 0) {
this._size++;
return Promise.resolve();
} else if (parent.balanceFactor < -1) {
if (parent.right.balanceFactor === 1) rotateRight(parent.right);
newRoot = rotateLeft(parent);
if (parent === this._root) this._root = newRoot;
this._size++;
return Promise.resolve();
} else if (parent.balanceFactor > 1) {
if (parent.left.balanceFactor === -1) rotateLeft(parent.left);
newRoot = rotateRight(parent);
if (parent === this._root) this._root = newRoot;
this._size++;
return Promise.resolve();
}
return this._rebalanceInsert(parent.parent, key);
});
}
_insertAsync (key, data, node) {
let newNode;
return this._findParent(key, node)
.then(([parent, cmp]) => {
if (cmp === 0 && this._noDuplicates) return null;
newNode = {
left: null,
right: null,
balanceFactor: 0,
parent, key, data
};
if (cmp <= 0) parent.left = newNode;
else parent.right = newNode;
return this._rebalanceInsert(parent, key);
})
.then(() => newNode);
}
_rebalanceRemove (parent, pp) {
let newRoot;
if (!parent) return Promise.resolve();
if (parent.left === pp) parent.balanceFactor -= 1;
else parent.balanceFactor += 1;
if (parent.balanceFactor < -1) {
if (parent.right.balanceFactor === 1) rotateRight(parent.right);
newRoot = rotateLeft(parent);
if (parent === this._root) this._root = newRoot;
parent = newRoot;
return Promise.resolve();
} else if (parent.balanceFactor > 1) {
if (parent.left.balanceFactor === -1) rotateLeft(parent.left);
newRoot = rotateRight(parent);
if (parent === this._root) this._root = newRoot;
return Promise.resolve();
}
if (parent.balanceFactor === -1 || parent.balanceFactor === 1) {
return Promise.resolve();
}
return this._rebalanceRemove(parent.parent, parent);
}
/**
* Removes the node from the tree. If not found, returns null.
* @param {Key} key
* @return {?Node}
*/
remove (key) {
if (!this._root) return null;
return this._findAsync(key, this._root)
.then(node => {
if (!node) return Promise.resolve();
var returnValue = node.key;
var max, min;
if (node.left) {
max = node.left;
while (max.left || max.right) {
while (max.right) max = max.right;
node.key = max.key;
node.data = max.data;
if (max.left) {
node = max;
max = max.left;
}
}
node.key = max.key;
node.data = max.data;
node = max;
}
if (node.right) {
min = node.right;
while (min.left || min.right) {
while (min.left) min = min.left;
node.key = min.key;
node.data = min.data;
if (min.right) {
node = min;
min = min.right;
}
}
node.key = min.key;
node.data = min.data;
node = min;
}
var parent = node.parent;
var pp = node;
return this._rebalanceRemove(parent, pp)
.then(() => {
if (node.parent) {
if (node.parent.left === node) node.parent.left = null;
else node.parent.right = null;
}
if (node === this._root) this._root = null;
this._size--;
return Promise.resolve(returnValue);
});
});
}
/**
* Bulk-load items
* @param {Array<Key>} keys
* @param {Array<Value>} [values]
* @return {AVLTree}
*/
load(keys = [], values = []) {
if (this._countCompareCalls) {
this._compareCallsCounter = 0;
}
if (!Array.isArray(keys)) return this;
const pairs = [];
keys.forEach((k, i) => pairs.push({ k, v: values[i] }));
return Promise.each(pairs, p => this._insert(p.k, p.v))
.then(() => this);
}
/**
* Returns true if the tree is balanced
* @return {boolean}
*/
isBalanced() {
return isBalanced(this._root);
}
/**
* String representation of the tree - primitive horizontal print-out
* @param {Function(Node):string} [printNode]
* @return {string}
*/
toString (printNode) {
return print(this._root, printNode);
}
}
AVLTree.default = AVLTree;