stuff
This commit is contained in:
455
buildfiles/app/node_modules/binary-search-tree/lib/avltree.js
generated
vendored
Normal file
455
buildfiles/app/node_modules/binary-search-tree/lib/avltree.js
generated
vendored
Normal file
@ -0,0 +1,455 @@
|
||||
/**
|
||||
* Self-balancing binary search tree using the AVL implementation
|
||||
*/
|
||||
var BinarySearchTree = require('./bst')
|
||||
, customUtils = require('./customUtils')
|
||||
, util = require('util')
|
||||
, _ = require('underscore')
|
||||
;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* We can't use a direct pointer to the root node (as in the simple binary search tree)
|
||||
* as the root will change during tree rotations
|
||||
* @param {Boolean} options.unique Whether to enforce a 'unique' constraint on the key or not
|
||||
* @param {Function} options.compareKeys Initialize this BST's compareKeys
|
||||
*/
|
||||
function AVLTree (options) {
|
||||
this.tree = new _AVLTree(options);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructor of the internal AVLTree
|
||||
* @param {Object} options Optional
|
||||
* @param {Boolean} options.unique Whether to enforce a 'unique' constraint on the key or not
|
||||
* @param {Key} options.key Initialize this BST's key with key
|
||||
* @param {Value} options.value Initialize this BST's data with [value]
|
||||
* @param {Function} options.compareKeys Initialize this BST's compareKeys
|
||||
*/
|
||||
function _AVLTree (options) {
|
||||
options = options || {};
|
||||
|
||||
this.left = null;
|
||||
this.right = null;
|
||||
this.parent = options.parent !== undefined ? options.parent : null;
|
||||
if (options.hasOwnProperty('key')) { this.key = options.key; }
|
||||
this.data = options.hasOwnProperty('value') ? [options.value] : [];
|
||||
this.unique = options.unique || false;
|
||||
|
||||
this.compareKeys = options.compareKeys || customUtils.defaultCompareKeysFunction;
|
||||
this.checkValueEquality = options.checkValueEquality || customUtils.defaultCheckValueEquality;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inherit basic functions from the basic binary search tree
|
||||
*/
|
||||
util.inherits(_AVLTree, BinarySearchTree);
|
||||
|
||||
/**
|
||||
* Keep a pointer to the internal tree constructor for testing purposes
|
||||
*/
|
||||
AVLTree._AVLTree = _AVLTree;
|
||||
|
||||
|
||||
/**
|
||||
* Check the recorded height is correct for every node
|
||||
* Throws if one height doesn't match
|
||||
*/
|
||||
_AVLTree.prototype.checkHeightCorrect = function () {
|
||||
var leftH, rightH;
|
||||
|
||||
if (!this.hasOwnProperty('key')) { return; } // Empty tree
|
||||
|
||||
if (this.left && this.left.height === undefined) { throw new Error("Undefined height for node " + this.left.key); }
|
||||
if (this.right && this.right.height === undefined) { throw new Error("Undefined height for node " + this.right.key); }
|
||||
if (this.height === undefined) { throw new Error("Undefined height for node " + this.key); }
|
||||
|
||||
leftH = this.left ? this.left.height : 0;
|
||||
rightH = this.right ? this.right.height : 0;
|
||||
|
||||
if (this.height !== 1 + Math.max(leftH, rightH)) { throw new Error("Height constraint failed for node " + this.key); }
|
||||
if (this.left) { this.left.checkHeightCorrect(); }
|
||||
if (this.right) { this.right.checkHeightCorrect(); }
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Return the balance factor
|
||||
*/
|
||||
_AVLTree.prototype.balanceFactor = function () {
|
||||
var leftH = this.left ? this.left.height : 0
|
||||
, rightH = this.right ? this.right.height : 0
|
||||
;
|
||||
return leftH - rightH;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check that the balance factors are all between -1 and 1
|
||||
*/
|
||||
_AVLTree.prototype.checkBalanceFactors = function () {
|
||||
if (Math.abs(this.balanceFactor()) > 1) { throw new Error('Tree is unbalanced at node ' + this.key); }
|
||||
|
||||
if (this.left) { this.left.checkBalanceFactors(); }
|
||||
if (this.right) { this.right.checkBalanceFactors(); }
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* When checking if the BST conditions are met, also check that the heights are correct
|
||||
* and the tree is balanced
|
||||
*/
|
||||
_AVLTree.prototype.checkIsAVLT = function () {
|
||||
_AVLTree.super_.prototype.checkIsBST.call(this);
|
||||
this.checkHeightCorrect();
|
||||
this.checkBalanceFactors();
|
||||
};
|
||||
AVLTree.prototype.checkIsAVLT = function () { this.tree.checkIsAVLT(); };
|
||||
|
||||
|
||||
/**
|
||||
* Perform a right rotation of the tree if possible
|
||||
* and return the root of the resulting tree
|
||||
* The resulting tree's nodes' heights are also updated
|
||||
*/
|
||||
_AVLTree.prototype.rightRotation = function () {
|
||||
var q = this
|
||||
, p = this.left
|
||||
, b
|
||||
, ah, bh, ch;
|
||||
|
||||
if (!p) { return this; } // No change
|
||||
|
||||
b = p.right;
|
||||
|
||||
// Alter tree structure
|
||||
if (q.parent) {
|
||||
p.parent = q.parent;
|
||||
if (q.parent.left === q) { q.parent.left = p; } else { q.parent.right = p; }
|
||||
} else {
|
||||
p.parent = null;
|
||||
}
|
||||
p.right = q;
|
||||
q.parent = p;
|
||||
q.left = b;
|
||||
if (b) { b.parent = q; }
|
||||
|
||||
// Update heights
|
||||
ah = p.left ? p.left.height : 0;
|
||||
bh = b ? b.height : 0;
|
||||
ch = q.right ? q.right.height : 0;
|
||||
q.height = Math.max(bh, ch) + 1;
|
||||
p.height = Math.max(ah, q.height) + 1;
|
||||
|
||||
return p;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Perform a left rotation of the tree if possible
|
||||
* and return the root of the resulting tree
|
||||
* The resulting tree's nodes' heights are also updated
|
||||
*/
|
||||
_AVLTree.prototype.leftRotation = function () {
|
||||
var p = this
|
||||
, q = this.right
|
||||
, b
|
||||
, ah, bh, ch;
|
||||
|
||||
if (!q) { return this; } // No change
|
||||
|
||||
b = q.left;
|
||||
|
||||
// Alter tree structure
|
||||
if (p.parent) {
|
||||
q.parent = p.parent;
|
||||
if (p.parent.left === p) { p.parent.left = q; } else { p.parent.right = q; }
|
||||
} else {
|
||||
q.parent = null;
|
||||
}
|
||||
q.left = p;
|
||||
p.parent = q;
|
||||
p.right = b;
|
||||
if (b) { b.parent = p; }
|
||||
|
||||
// Update heights
|
||||
ah = p.left ? p.left.height : 0;
|
||||
bh = b ? b.height : 0;
|
||||
ch = q.right ? q.right.height : 0;
|
||||
p.height = Math.max(ah, bh) + 1;
|
||||
q.height = Math.max(ch, p.height) + 1;
|
||||
|
||||
return q;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Modify the tree if its right subtree is too small compared to the left
|
||||
* Return the new root if any
|
||||
*/
|
||||
_AVLTree.prototype.rightTooSmall = function () {
|
||||
if (this.balanceFactor() <= 1) { return this; } // Right is not too small, don't change
|
||||
|
||||
if (this.left.balanceFactor() < 0) {
|
||||
this.left.leftRotation();
|
||||
}
|
||||
|
||||
return this.rightRotation();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Modify the tree if its left subtree is too small compared to the right
|
||||
* Return the new root if any
|
||||
*/
|
||||
_AVLTree.prototype.leftTooSmall = function () {
|
||||
if (this.balanceFactor() >= -1) { return this; } // Left is not too small, don't change
|
||||
|
||||
if (this.right.balanceFactor() > 0) {
|
||||
this.right.rightRotation();
|
||||
}
|
||||
|
||||
return this.leftRotation();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Rebalance the tree along the given path. The path is given reversed (as he was calculated
|
||||
* in the insert and delete functions).
|
||||
* Returns the new root of the tree
|
||||
* Of course, the first element of the path must be the root of the tree
|
||||
*/
|
||||
_AVLTree.prototype.rebalanceAlongPath = function (path) {
|
||||
var newRoot = this
|
||||
, rotated
|
||||
, i;
|
||||
|
||||
if (!this.hasOwnProperty('key')) { delete this.height; return this; } // Empty tree
|
||||
|
||||
// Rebalance the tree and update all heights
|
||||
for (i = path.length - 1; i >= 0; i -= 1) {
|
||||
path[i].height = 1 + Math.max(path[i].left ? path[i].left.height : 0, path[i].right ? path[i].right.height : 0);
|
||||
|
||||
if (path[i].balanceFactor() > 1) {
|
||||
rotated = path[i].rightTooSmall();
|
||||
if (i === 0) { newRoot = rotated; }
|
||||
}
|
||||
|
||||
if (path[i].balanceFactor() < -1) {
|
||||
rotated = path[i].leftTooSmall();
|
||||
if (i === 0) { newRoot = rotated; }
|
||||
}
|
||||
}
|
||||
|
||||
return newRoot;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Insert a key, value pair in the tree while maintaining the AVL tree height constraint
|
||||
* Return a pointer to the root node, which may have changed
|
||||
*/
|
||||
_AVLTree.prototype.insert = function (key, value) {
|
||||
var insertPath = []
|
||||
, currentNode = this
|
||||
;
|
||||
|
||||
// Empty tree, insert as root
|
||||
if (!this.hasOwnProperty('key')) {
|
||||
this.key = key;
|
||||
this.data.push(value);
|
||||
this.height = 1;
|
||||
return this;
|
||||
}
|
||||
|
||||
// Insert new leaf at the right place
|
||||
while (true) {
|
||||
// Same key: no change in the tree structure
|
||||
if (currentNode.compareKeys(currentNode.key, key) === 0) {
|
||||
if (currentNode.unique) {
|
||||
var err = new Error("Can't insert key " + key + ", it violates the unique constraint");
|
||||
err.key = key;
|
||||
err.errorType = 'uniqueViolated';
|
||||
throw err;
|
||||
} else {
|
||||
currentNode.data.push(value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
insertPath.push(currentNode);
|
||||
|
||||
if (currentNode.compareKeys(key, currentNode.key) < 0) {
|
||||
if (!currentNode.left) {
|
||||
insertPath.push(currentNode.createLeftChild({ key: key, value: value }));
|
||||
break;
|
||||
} else {
|
||||
currentNode = currentNode.left;
|
||||
}
|
||||
} else {
|
||||
if (!currentNode.right) {
|
||||
insertPath.push(currentNode.createRightChild({ key: key, value: value }));
|
||||
break;
|
||||
} else {
|
||||
currentNode = currentNode.right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this.rebalanceAlongPath(insertPath);
|
||||
};
|
||||
|
||||
// Insert in the internal tree, update the pointer to the root if needed
|
||||
AVLTree.prototype.insert = function (key, value) {
|
||||
var newTree = this.tree.insert(key, value);
|
||||
|
||||
// If newTree is undefined, that means its structure was not modified
|
||||
if (newTree) { this.tree = newTree; }
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Delete a key or just a value and return the new root of the tree
|
||||
* @param {Key} key
|
||||
* @param {Value} value Optional. If not set, the whole key is deleted. If set, only this value is deleted
|
||||
*/
|
||||
_AVLTree.prototype.delete = function (key, value) {
|
||||
var newData = [], replaceWith
|
||||
, self = this
|
||||
, currentNode = this
|
||||
, deletePath = []
|
||||
;
|
||||
|
||||
if (!this.hasOwnProperty('key')) { return this; } // Empty tree
|
||||
|
||||
// Either no match is found and the function will return from within the loop
|
||||
// Or a match is found and deletePath will contain the path from the root to the node to delete after the loop
|
||||
while (true) {
|
||||
if (currentNode.compareKeys(key, currentNode.key) === 0) { break; }
|
||||
|
||||
deletePath.push(currentNode);
|
||||
|
||||
if (currentNode.compareKeys(key, currentNode.key) < 0) {
|
||||
if (currentNode.left) {
|
||||
currentNode = currentNode.left;
|
||||
} else {
|
||||
return this; // Key not found, no modification
|
||||
}
|
||||
} else {
|
||||
// currentNode.compareKeys(key, currentNode.key) is > 0
|
||||
if (currentNode.right) {
|
||||
currentNode = currentNode.right;
|
||||
} else {
|
||||
return this; // Key not found, no modification
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete only a value (no tree modification)
|
||||
if (currentNode.data.length > 1 && value) {
|
||||
currentNode.data.forEach(function (d) {
|
||||
if (!currentNode.checkValueEquality(d, value)) { newData.push(d); }
|
||||
});
|
||||
currentNode.data = newData;
|
||||
return this;
|
||||
}
|
||||
|
||||
// Delete a whole node
|
||||
|
||||
// Leaf
|
||||
if (!currentNode.left && !currentNode.right) {
|
||||
if (currentNode === this) { // This leaf is also the root
|
||||
delete currentNode.key;
|
||||
currentNode.data = [];
|
||||
delete currentNode.height;
|
||||
return this;
|
||||
} else {
|
||||
if (currentNode.parent.left === currentNode) {
|
||||
currentNode.parent.left = null;
|
||||
} else {
|
||||
currentNode.parent.right = null;
|
||||
}
|
||||
return this.rebalanceAlongPath(deletePath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Node with only one child
|
||||
if (!currentNode.left || !currentNode.right) {
|
||||
replaceWith = currentNode.left ? currentNode.left : currentNode.right;
|
||||
|
||||
if (currentNode === this) { // This node is also the root
|
||||
replaceWith.parent = null;
|
||||
return replaceWith; // height of replaceWith is necessarily 1 because the tree was balanced before deletion
|
||||
} else {
|
||||
if (currentNode.parent.left === currentNode) {
|
||||
currentNode.parent.left = replaceWith;
|
||||
replaceWith.parent = currentNode.parent;
|
||||
} else {
|
||||
currentNode.parent.right = replaceWith;
|
||||
replaceWith.parent = currentNode.parent;
|
||||
}
|
||||
|
||||
return this.rebalanceAlongPath(deletePath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Node with two children
|
||||
// Use the in-order predecessor (no need to randomize since we actively rebalance)
|
||||
deletePath.push(currentNode);
|
||||
replaceWith = currentNode.left;
|
||||
|
||||
// Special case: the in-order predecessor is right below the node to delete
|
||||
if (!replaceWith.right) {
|
||||
currentNode.key = replaceWith.key;
|
||||
currentNode.data = replaceWith.data;
|
||||
currentNode.left = replaceWith.left;
|
||||
if (replaceWith.left) { replaceWith.left.parent = currentNode; }
|
||||
return this.rebalanceAlongPath(deletePath);
|
||||
}
|
||||
|
||||
// After this loop, replaceWith is the right-most leaf in the left subtree
|
||||
// and deletePath the path from the root (inclusive) to replaceWith (exclusive)
|
||||
while (true) {
|
||||
if (replaceWith.right) {
|
||||
deletePath.push(replaceWith);
|
||||
replaceWith = replaceWith.right;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
currentNode.key = replaceWith.key;
|
||||
currentNode.data = replaceWith.data;
|
||||
|
||||
replaceWith.parent.right = replaceWith.left;
|
||||
if (replaceWith.left) { replaceWith.left.parent = replaceWith.parent; }
|
||||
|
||||
return this.rebalanceAlongPath(deletePath);
|
||||
};
|
||||
|
||||
// Delete a value
|
||||
AVLTree.prototype.delete = function (key, value) {
|
||||
var newTree = this.tree.delete(key, value);
|
||||
|
||||
// If newTree is undefined, that means its structure was not modified
|
||||
if (newTree) { this.tree = newTree; }
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Other functions we want to use on an AVLTree as if it were the internal _AVLTree
|
||||
*/
|
||||
['getNumberOfKeys', 'search', 'betweenBounds', 'prettyPrint', 'executeOnEveryNode'].forEach(function (fn) {
|
||||
AVLTree.prototype[fn] = function () {
|
||||
return this.tree[fn].apply(this.tree, arguments);
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
// Interface
|
||||
module.exports = AVLTree;
|
543
buildfiles/app/node_modules/binary-search-tree/lib/bst.js
generated
vendored
Normal file
543
buildfiles/app/node_modules/binary-search-tree/lib/bst.js
generated
vendored
Normal file
@ -0,0 +1,543 @@
|
||||
/**
|
||||
* Simple binary search tree
|
||||
*/
|
||||
var customUtils = require('./customUtils');
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} options Optional
|
||||
* @param {Boolean} options.unique Whether to enforce a 'unique' constraint on the key or not
|
||||
* @param {Key} options.key Initialize this BST's key with key
|
||||
* @param {Value} options.value Initialize this BST's data with [value]
|
||||
* @param {Function} options.compareKeys Initialize this BST's compareKeys
|
||||
*/
|
||||
function BinarySearchTree (options) {
|
||||
options = options || {};
|
||||
|
||||
this.left = null;
|
||||
this.right = null;
|
||||
this.parent = options.parent !== undefined ? options.parent : null;
|
||||
if (options.hasOwnProperty('key')) { this.key = options.key; }
|
||||
this.data = options.hasOwnProperty('value') ? [options.value] : [];
|
||||
this.unique = options.unique || false;
|
||||
|
||||
this.compareKeys = options.compareKeys || customUtils.defaultCompareKeysFunction;
|
||||
this.checkValueEquality = options.checkValueEquality || customUtils.defaultCheckValueEquality;
|
||||
}
|
||||
|
||||
|
||||
// ================================
|
||||
// Methods used to test the tree
|
||||
// ================================
|
||||
|
||||
|
||||
/**
|
||||
* Get the descendant with max key
|
||||
*/
|
||||
BinarySearchTree.prototype.getMaxKeyDescendant = function () {
|
||||
if (this.right) {
|
||||
return this.right.getMaxKeyDescendant();
|
||||
} else {
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the maximum key
|
||||
*/
|
||||
BinarySearchTree.prototype.getMaxKey = function () {
|
||||
return this.getMaxKeyDescendant().key;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the descendant with min key
|
||||
*/
|
||||
BinarySearchTree.prototype.getMinKeyDescendant = function () {
|
||||
if (this.left) {
|
||||
return this.left.getMinKeyDescendant()
|
||||
} else {
|
||||
return this;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get the minimum key
|
||||
*/
|
||||
BinarySearchTree.prototype.getMinKey = function () {
|
||||
return this.getMinKeyDescendant().key;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check that all nodes (incl. leaves) fullfil condition given by fn
|
||||
* test is a function passed every (key, data) and which throws if the condition is not met
|
||||
*/
|
||||
BinarySearchTree.prototype.checkAllNodesFullfillCondition = function (test) {
|
||||
if (!this.hasOwnProperty('key')) { return; }
|
||||
|
||||
test(this.key, this.data);
|
||||
if (this.left) { this.left.checkAllNodesFullfillCondition(test); }
|
||||
if (this.right) { this.right.checkAllNodesFullfillCondition(test); }
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check that the core BST properties on node ordering are verified
|
||||
* Throw if they aren't
|
||||
*/
|
||||
BinarySearchTree.prototype.checkNodeOrdering = function () {
|
||||
var self = this;
|
||||
|
||||
if (!this.hasOwnProperty('key')) { return; }
|
||||
|
||||
if (this.left) {
|
||||
this.left.checkAllNodesFullfillCondition(function (k) {
|
||||
if (self.compareKeys(k, self.key) >= 0) {
|
||||
throw new Error('Tree with root ' + self.key + ' is not a binary search tree');
|
||||
}
|
||||
});
|
||||
this.left.checkNodeOrdering();
|
||||
}
|
||||
|
||||
if (this.right) {
|
||||
this.right.checkAllNodesFullfillCondition(function (k) {
|
||||
if (self.compareKeys(k, self.key) <= 0) {
|
||||
throw new Error('Tree with root ' + self.key + ' is not a binary search tree');
|
||||
}
|
||||
});
|
||||
this.right.checkNodeOrdering();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check that all pointers are coherent in this tree
|
||||
*/
|
||||
BinarySearchTree.prototype.checkInternalPointers = function () {
|
||||
if (this.left) {
|
||||
if (this.left.parent !== this) { throw new Error('Parent pointer broken for key ' + this.key); }
|
||||
this.left.checkInternalPointers();
|
||||
}
|
||||
|
||||
if (this.right) {
|
||||
if (this.right.parent !== this) { throw new Error('Parent pointer broken for key ' + this.key); }
|
||||
this.right.checkInternalPointers();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Check that a tree is a BST as defined here (node ordering and pointer references)
|
||||
*/
|
||||
BinarySearchTree.prototype.checkIsBST = function () {
|
||||
this.checkNodeOrdering();
|
||||
this.checkInternalPointers();
|
||||
if (this.parent) { throw new Error("The root shouldn't have a parent"); }
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get number of keys inserted
|
||||
*/
|
||||
BinarySearchTree.prototype.getNumberOfKeys = function () {
|
||||
var res;
|
||||
|
||||
if (!this.hasOwnProperty('key')) { return 0; }
|
||||
|
||||
res = 1;
|
||||
if (this.left) { res += this.left.getNumberOfKeys(); }
|
||||
if (this.right) { res += this.right.getNumberOfKeys(); }
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
|
||||
|
||||
// ============================================
|
||||
// Methods used to actually work on the tree
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Create a BST similar (i.e. same options except for key and value) to the current one
|
||||
* Use the same constructor (i.e. BinarySearchTree, AVLTree etc)
|
||||
* @param {Object} options see constructor
|
||||
*/
|
||||
BinarySearchTree.prototype.createSimilar = function (options) {
|
||||
options = options || {};
|
||||
options.unique = this.unique;
|
||||
options.compareKeys = this.compareKeys;
|
||||
options.checkValueEquality = this.checkValueEquality;
|
||||
|
||||
return new this.constructor(options);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create the left child of this BST and return it
|
||||
*/
|
||||
BinarySearchTree.prototype.createLeftChild = function (options) {
|
||||
var leftChild = this.createSimilar(options);
|
||||
leftChild.parent = this;
|
||||
this.left = leftChild;
|
||||
|
||||
return leftChild;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create the right child of this BST and return it
|
||||
*/
|
||||
BinarySearchTree.prototype.createRightChild = function (options) {
|
||||
var rightChild = this.createSimilar(options);
|
||||
rightChild.parent = this;
|
||||
this.right = rightChild;
|
||||
|
||||
return rightChild;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Insert a new element
|
||||
*/
|
||||
BinarySearchTree.prototype.insert = function (key, value) {
|
||||
// Empty tree, insert as root
|
||||
if (!this.hasOwnProperty('key')) {
|
||||
this.key = key;
|
||||
this.data.push(value);
|
||||
return;
|
||||
}
|
||||
|
||||
// Same key as root
|
||||
if (this.compareKeys(this.key, key) === 0) {
|
||||
if (this.unique) {
|
||||
var err = new Error("Can't insert key " + key + ", it violates the unique constraint");
|
||||
err.key = key;
|
||||
err.errorType = 'uniqueViolated';
|
||||
throw err;
|
||||
} else {
|
||||
this.data.push(value);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.compareKeys(key, this.key) < 0) {
|
||||
// Insert in left subtree
|
||||
if (this.left) {
|
||||
this.left.insert(key, value);
|
||||
} else {
|
||||
this.createLeftChild({ key: key, value: value });
|
||||
}
|
||||
} else {
|
||||
// Insert in right subtree
|
||||
if (this.right) {
|
||||
this.right.insert(key, value);
|
||||
} else {
|
||||
this.createRightChild({ key: key, value: value });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Search for all data corresponding to a key
|
||||
*/
|
||||
BinarySearchTree.prototype.search = function (key) {
|
||||
if (!this.hasOwnProperty('key')) { return []; }
|
||||
|
||||
if (this.compareKeys(this.key, key) === 0) { return this.data; }
|
||||
|
||||
if (this.compareKeys(key, this.key) < 0) {
|
||||
if (this.left) {
|
||||
return this.left.search(key);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
} else {
|
||||
if (this.right) {
|
||||
return this.right.search(key);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Return a function that tells whether a given key matches a lower bound
|
||||
*/
|
||||
BinarySearchTree.prototype.getLowerBoundMatcher = function (query) {
|
||||
var self = this;
|
||||
|
||||
// No lower bound
|
||||
if (!query.hasOwnProperty('$gt') && !query.hasOwnProperty('$gte')) {
|
||||
return function () { return true; };
|
||||
}
|
||||
|
||||
if (query.hasOwnProperty('$gt') && query.hasOwnProperty('$gte')) {
|
||||
if (self.compareKeys(query.$gte, query.$gt) === 0) {
|
||||
return function (key) { return self.compareKeys(key, query.$gt) > 0; };
|
||||
}
|
||||
|
||||
if (self.compareKeys(query.$gte, query.$gt) > 0) {
|
||||
return function (key) { return self.compareKeys(key, query.$gte) >= 0; };
|
||||
} else {
|
||||
return function (key) { return self.compareKeys(key, query.$gt) > 0; };
|
||||
}
|
||||
}
|
||||
|
||||
if (query.hasOwnProperty('$gt')) {
|
||||
return function (key) { return self.compareKeys(key, query.$gt) > 0; };
|
||||
} else {
|
||||
return function (key) { return self.compareKeys(key, query.$gte) >= 0; };
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Return a function that tells whether a given key matches an upper bound
|
||||
*/
|
||||
BinarySearchTree.prototype.getUpperBoundMatcher = function (query) {
|
||||
var self = this;
|
||||
|
||||
// No lower bound
|
||||
if (!query.hasOwnProperty('$lt') && !query.hasOwnProperty('$lte')) {
|
||||
return function () { return true; };
|
||||
}
|
||||
|
||||
if (query.hasOwnProperty('$lt') && query.hasOwnProperty('$lte')) {
|
||||
if (self.compareKeys(query.$lte, query.$lt) === 0) {
|
||||
return function (key) { return self.compareKeys(key, query.$lt) < 0; };
|
||||
}
|
||||
|
||||
if (self.compareKeys(query.$lte, query.$lt) < 0) {
|
||||
return function (key) { return self.compareKeys(key, query.$lte) <= 0; };
|
||||
} else {
|
||||
return function (key) { return self.compareKeys(key, query.$lt) < 0; };
|
||||
}
|
||||
}
|
||||
|
||||
if (query.hasOwnProperty('$lt')) {
|
||||
return function (key) { return self.compareKeys(key, query.$lt) < 0; };
|
||||
} else {
|
||||
return function (key) { return self.compareKeys(key, query.$lte) <= 0; };
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Append all elements in toAppend to array
|
||||
function append (array, toAppend) {
|
||||
var i;
|
||||
|
||||
for (i = 0; i < toAppend.length; i += 1) {
|
||||
array.push(toAppend[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get all data for a key between bounds
|
||||
* Return it in key order
|
||||
* @param {Object} query Mongo-style query where keys are $lt, $lte, $gt or $gte (other keys are not considered)
|
||||
* @param {Functions} lbm/ubm matching functions calculated at the first recursive step
|
||||
*/
|
||||
BinarySearchTree.prototype.betweenBounds = function (query, lbm, ubm) {
|
||||
var res = [];
|
||||
|
||||
if (!this.hasOwnProperty('key')) { return []; } // Empty tree
|
||||
|
||||
lbm = lbm || this.getLowerBoundMatcher(query);
|
||||
ubm = ubm || this.getUpperBoundMatcher(query);
|
||||
|
||||
if (lbm(this.key) && this.left) { append(res, this.left.betweenBounds(query, lbm, ubm)); }
|
||||
if (lbm(this.key) && ubm(this.key)) { append(res, this.data); }
|
||||
if (ubm(this.key) && this.right) { append(res, this.right.betweenBounds(query, lbm, ubm)); }
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Delete the current node if it is a leaf
|
||||
* Return true if it was deleted
|
||||
*/
|
||||
BinarySearchTree.prototype.deleteIfLeaf = function () {
|
||||
if (this.left || this.right) { return false; }
|
||||
|
||||
// The leaf is itself a root
|
||||
if (!this.parent) {
|
||||
delete this.key;
|
||||
this.data = [];
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.parent.left === this) {
|
||||
this.parent.left = null;
|
||||
} else {
|
||||
this.parent.right = null;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Delete the current node if it has only one child
|
||||
* Return true if it was deleted
|
||||
*/
|
||||
BinarySearchTree.prototype.deleteIfOnlyOneChild = function () {
|
||||
var child;
|
||||
|
||||
if (this.left && !this.right) { child = this.left; }
|
||||
if (!this.left && this.right) { child = this.right; }
|
||||
if (!child) { return false; }
|
||||
|
||||
// Root
|
||||
if (!this.parent) {
|
||||
this.key = child.key;
|
||||
this.data = child.data;
|
||||
|
||||
this.left = null;
|
||||
if (child.left) {
|
||||
this.left = child.left;
|
||||
child.left.parent = this;
|
||||
}
|
||||
|
||||
this.right = null;
|
||||
if (child.right) {
|
||||
this.right = child.right;
|
||||
child.right.parent = this;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.parent.left === this) {
|
||||
this.parent.left = child;
|
||||
child.parent = this.parent;
|
||||
} else {
|
||||
this.parent.right = child;
|
||||
child.parent = this.parent;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Delete a key or just a value
|
||||
* @param {Key} key
|
||||
* @param {Value} value Optional. If not set, the whole key is deleted. If set, only this value is deleted
|
||||
*/
|
||||
BinarySearchTree.prototype.delete = function (key, value) {
|
||||
var newData = [], replaceWith
|
||||
, self = this
|
||||
;
|
||||
|
||||
if (!this.hasOwnProperty('key')) { return; }
|
||||
|
||||
if (this.compareKeys(key, this.key) < 0) {
|
||||
if (this.left) { this.left.delete(key, value); }
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.compareKeys(key, this.key) > 0) {
|
||||
if (this.right) { this.right.delete(key, value); }
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.compareKeys(key, this.key) === 0) { return; }
|
||||
|
||||
// Delete only a value
|
||||
if (this.data.length > 1 && value !== undefined) {
|
||||
this.data.forEach(function (d) {
|
||||
if (!self.checkValueEquality(d, value)) { newData.push(d); }
|
||||
});
|
||||
self.data = newData;
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete the whole node
|
||||
if (this.deleteIfLeaf()) {
|
||||
return;
|
||||
}
|
||||
if (this.deleteIfOnlyOneChild()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We are in the case where the node to delete has two children
|
||||
if (Math.random() >= 0.5) { // Randomize replacement to avoid unbalancing the tree too much
|
||||
// Use the in-order predecessor
|
||||
replaceWith = this.left.getMaxKeyDescendant();
|
||||
|
||||
this.key = replaceWith.key;
|
||||
this.data = replaceWith.data;
|
||||
|
||||
if (this === replaceWith.parent) { // Special case
|
||||
this.left = replaceWith.left;
|
||||
if (replaceWith.left) { replaceWith.left.parent = replaceWith.parent; }
|
||||
} else {
|
||||
replaceWith.parent.right = replaceWith.left;
|
||||
if (replaceWith.left) { replaceWith.left.parent = replaceWith.parent; }
|
||||
}
|
||||
} else {
|
||||
// Use the in-order successor
|
||||
replaceWith = this.right.getMinKeyDescendant();
|
||||
|
||||
this.key = replaceWith.key;
|
||||
this.data = replaceWith.data;
|
||||
|
||||
if (this === replaceWith.parent) { // Special case
|
||||
this.right = replaceWith.right;
|
||||
if (replaceWith.right) { replaceWith.right.parent = replaceWith.parent; }
|
||||
} else {
|
||||
replaceWith.parent.left = replaceWith.right;
|
||||
if (replaceWith.right) { replaceWith.right.parent = replaceWith.parent; }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Execute a function on every node of the tree, in key order
|
||||
* @param {Function} fn Signature: node. Most useful will probably be node.key and node.data
|
||||
*/
|
||||
BinarySearchTree.prototype.executeOnEveryNode = function (fn) {
|
||||
if (this.left) { this.left.executeOnEveryNode(fn); }
|
||||
fn(this);
|
||||
if (this.right) { this.right.executeOnEveryNode(fn); }
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Pretty print a tree
|
||||
* @param {Boolean} printData To print the nodes' data along with the key
|
||||
*/
|
||||
BinarySearchTree.prototype.prettyPrint = function (printData, spacing) {
|
||||
spacing = spacing || "";
|
||||
|
||||
console.log(spacing + "* " + this.key);
|
||||
if (printData) { console.log(spacing + "* " + this.data); }
|
||||
|
||||
if (!this.left && !this.right) { return; }
|
||||
|
||||
if (this.left) {
|
||||
this.left.prettyPrint(printData, spacing + " ");
|
||||
} else {
|
||||
console.log(spacing + " *");
|
||||
}
|
||||
if (this.right) {
|
||||
this.right.prettyPrint(printData, spacing + " ");
|
||||
} else {
|
||||
console.log(spacing + " *");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
// Interface
|
||||
module.exports = BinarySearchTree;
|
41
buildfiles/app/node_modules/binary-search-tree/lib/customUtils.js
generated
vendored
Normal file
41
buildfiles/app/node_modules/binary-search-tree/lib/customUtils.js
generated
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Return an array with the numbers from 0 to n-1, in a random order
|
||||
*/
|
||||
function getRandomArray (n) {
|
||||
var res, next;
|
||||
|
||||
if (n === 0) { return []; }
|
||||
if (n === 1) { return [0]; }
|
||||
|
||||
res = getRandomArray(n - 1);
|
||||
next = Math.floor(Math.random() * n);
|
||||
res.splice(next, 0, n - 1); // Add n-1 at a random position in the array
|
||||
|
||||
return res;
|
||||
};
|
||||
module.exports.getRandomArray = getRandomArray;
|
||||
|
||||
|
||||
/*
|
||||
* Default compareKeys function will work for numbers, strings and dates
|
||||
*/
|
||||
function defaultCompareKeysFunction (a, b) {
|
||||
if (a < b) { return -1; }
|
||||
if (a > b) { return 1; }
|
||||
if (a === b) { return 0; }
|
||||
|
||||
var err = new Error("Couldn't compare elements");
|
||||
err.a = a;
|
||||
err.b = b;
|
||||
throw err;
|
||||
}
|
||||
module.exports.defaultCompareKeysFunction = defaultCompareKeysFunction;
|
||||
|
||||
|
||||
/**
|
||||
* Check whether two values are equal (used in non-unique deletion)
|
||||
*/
|
||||
function defaultCheckValueEquality (a, b) {
|
||||
return a === b;
|
||||
}
|
||||
module.exports.defaultCheckValueEquality = defaultCheckValueEquality;
|
Reference in New Issue
Block a user