This commit is contained in:
2022-09-30 05:39:11 +00:00
parent 41ee9463ae
commit 4687fa49bc
11418 changed files with 1312504 additions and 0 deletions

View File

@ -0,0 +1,16 @@
lib-cov
*.seed
*.log
*.csv
*.dat
*.out
*.pid
*.gz
pids
logs
results
npm-debug.log
node_modules

View File

@ -0,0 +1,7 @@
test:
@echo "Launching tests"
@ ./node_modules/.bin/mocha --timeout 10000 --reporter spec
@echo "Tests finished"
.PHONY: test

View File

@ -0,0 +1,123 @@
# Binary search trees for Node.js
Two implementations of binary search tree: <a href="http://en.wikipedia.org/wiki/Binary_search_tree" target="_blank">basic</a> and <a href="http://en.wikipedia.org/wiki/AVL_tree" target="_blank">AVL</a> (a kind of self-balancing binmary search tree). I wrote this module primarily to store indexes for <a href="https://github.com/louischatriot/nedb" target="_blank">NeDB</a> (a javascript dependency-less database).
## Installation and tests
Package name is `binary-search-tree`.
```bash
npm install binary-search-tree --save
make test
```
## Usage
The API mainly provides 3 functions: `insert`, `search` and `delete`. If you do not create a unique-type binary search tree, you can store multiple pieces of data for the same key. Doing so with a unique-type BST will result in an error being thrown. Data is always returned as an array, and you can delete all data relating to a given key, or just one piece of data.
```javascript
var BinarySearchTree = require('binary-search-tree').BinarySearchTree
, AVLTree = require('binary-search-tree').AVLTree // Same API as BinarySearchTree
// Creating a binary search tree
var bst = new BinarySearchTree();
// Inserting some data
bst.insert(15, 'some data for key 15');
bst.insert(12, 'something else');
bst.insert(18, 'hello');
// You can insert multiple pieces of data for the same key
// if your tree doesn't enforce a unique constraint
bst.insert(18, 'world');
// Retrieving data (always returned as an array of all data stored for this key)
bst.search(15); // Equal to ['some data for key 15']
bst.search(18); // Equal to ['hello', 'world']
bst.search(1); // Equal to []
// Search between bounds with a MongoDB-like query
// Data is returned in key order
// Note the difference between $lt (less than) and $gte (less than OR EQUAL)
bst.betweenBounds({ $lt: 18, $gte: 12}); // Equal to ['something else', 'some data for key 15']
// Deleting all the data relating to a key
bst.delete(15); // bst.search(15) will now give []
bst.delete(18, 'world'); // bst.search(18) will now give ['hello']
```
There are three optional parameters you can pass the BST constructor, allowing you to enforce a key-uniqueness constraint, use a custom function to compare keys and use a custom function to check whether values are equal. These parameters are all passed in an object.
### Uniqueness
```javascript
var bst = new BinarySearchTree({ unique: true });
bst.insert(10, 'hello');
bst.insert(10, 'world'); // Will throw an error
```
### Custom key comparison
```javascript
// Custom key comparison function
// It needs to return a negative number if a is less than b,
// a positive number if a is greater than b
// and 0 if they are equal
// If none is provided, the default one can compare numbers, dates and strings
// which are the most common usecases
function compareKeys (a, b) {
if (a.age < b.age) { return -1; }
if (a.age > b.age) { return 1; }
return 0;
}
// Now we can use objects with an 'age' property as keys
var bst = new BinarySearchTree({ compareKeys: compareKeys });
bst.insert({ age: 23 }, 'Mark');
bst.insert({ age: 47 }, 'Franck');
```
### Custom value checking
```javascript
// Custom value equality checking function used when we try to just delete one piece of data
// Returns true if a and b are considered the same, false otherwise
// The default function is able to compare numbers and strings
function checkValueEquality (a, b) {
return a.length === b.length;
}
var bst = new BinarySearchTree({ checkValueEquality: checkValueEquality });
bst.insert(10, 'hello');
bst.insert(10, 'world');
bst.insert(10, 'howdoyoudo');
bst.delete(10, 'abcde');
bst.search(10); // Returns ['howdoyoudo']
```
## License
(The MIT License)
Copyright (c) 2013 Louis Chatriot &lt;louis.chatriot@gmail.com&gt;
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,2 @@
module.exports.BinarySearchTree = require('./lib/bst');
module.exports.AVLTree = require('./lib/avltree');

View 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;

View 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;

View 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;

View File

@ -0,0 +1,63 @@
{
"_args": [
[
"binary-search-tree@0.2.5",
"/home/shihaam/www/freezer.shihaam.me/app"
]
],
"_from": "binary-search-tree@0.2.5",
"_id": "binary-search-tree@0.2.5",
"_inBundle": false,
"_integrity": "sha1-fbs7IQ/coIJFDa0jNMMErzm9x4Q=",
"_location": "/binary-search-tree",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "binary-search-tree@0.2.5",
"name": "binary-search-tree",
"escapedName": "binary-search-tree",
"rawSpec": "0.2.5",
"saveSpec": null,
"fetchSpec": "0.2.5"
},
"_requiredBy": [
"/nedb"
],
"_resolved": "https://registry.npmjs.org/binary-search-tree/-/binary-search-tree-0.2.5.tgz",
"_spec": "0.2.5",
"_where": "/home/shihaam/www/freezer.shihaam.me/app",
"author": {
"name": "Louis Chatriot",
"email": "louis.chatriot@gmail.com"
},
"bugs": {
"url": "https://github.com/louischatriot/node-binary-search-tree/issues"
},
"dependencies": {
"underscore": "~1.4.4"
},
"description": "Different binary search tree implementations, including a self-balancing one (AVL)",
"devDependencies": {
"chai": "1.0.x",
"mocha": "1.4.x"
},
"homepage": "https://github.com/louischatriot/node-binary-search-tree",
"keywords": [
"AVL tree",
"binary search tree",
"self-balancing",
"AVL tree"
],
"licence": "MIT",
"main": "index",
"name": "binary-search-tree",
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/louischatriot/node-binary-search-tree.git"
},
"scripts": {
"test": "make test"
},
"version": "0.2.5"
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff