From c2d3e6b31a9a146d7a69dda62e6e936b8b91b3c5 Mon Sep 17 00:00:00 2001 From: Antti Saarinen Date: Thu, 27 Feb 2014 23:19:50 +0200 Subject: [PATCH] added searchAfter and searchBefore with some tests --- README.md | 15 ++++++++ lib/avltree.js | 2 +- lib/bst.js | 96 ++++++++++++++++++++++++++++++++++++++++++++++++ test/bst.test.js | 52 ++++++++++++++++++++++++++ 4 files changed, 164 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 573eff8..a06851a 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,21 @@ 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. +### Browsing throuhg all keys in the tree + +You can use `searchAfter` and `searchBefore` to browse through all keys in the tree, like this: + +```javascript +// this assumes that each value in the BST includes 'key' attribute +var node = bst.search(bst.getMinKey()); +for( ; node.length > 0; node = bst.searchAfter(node[0].key) ) { + // do something with each node here + // node is now an array of values, as with search() +} +``` + +You can use `getMinKey` and `getMaxKey` to get started from the beginning or the end, or you can also use any other key value as a starting point. + ### Uniqueness ```javascript diff --git a/lib/avltree.js b/lib/avltree.js index 91b2a10..ba7602e 100644 --- a/lib/avltree.js +++ b/lib/avltree.js @@ -444,7 +444,7 @@ AVLTree.prototype.delete = function (key, value) { /** * Other functions we want to use on an AVLTree as if it were the internal _AVLTree */ -['getNumberOfKeys', 'search', 'betweenBounds', 'prettyPrint', 'executeOnEveryNode'].forEach(function (fn) { +['getNumberOfKeys', 'getMinKey', 'getMaxKey', 'search', 'searchAfter', 'searchBefore', 'betweenBounds', 'prettyPrint', 'executeOnEveryNode'].forEach(function (fn) { AVLTree.prototype[fn] = function () { return this.tree[fn].apply(this.tree, arguments); }; diff --git a/lib/bst.js b/lib/bst.js index ef90d45..c8f7905 100644 --- a/lib/bst.js +++ b/lib/bst.js @@ -265,6 +265,102 @@ BinarySearchTree.prototype.search = function (key) { } }; +/** + * Search for data coming right after a specific key + */ +BinarySearchTree.prototype.searchAfter = function (key) { + if (!this.hasOwnProperty('key')) { return []; } + + if (this.compareKeys(this.key, key) === 0) { + // if there's a right child, the next key will be there + var cur = this.right; + if( cur ) { + // within the right branch, traverse left until leaf + while( cur.left ) + cur = cur.left; + return cur.data; + } + + // traverse up until you find a bigger key + cur = this.parent; + while( cur ) { + if (this.compareKeys(key, cur.key) < 0) + return cur.data; + cur = cur.parent; + } + return []; + } + + if (this.compareKeys(key, this.key) < 0) { + if (this.left) { + return this.left.searchAfter(key); + } else { + return this.data; + } + } else { + if (this.right) { + return this.right.searchAfter(key); + } else { + // traverse up until you find a bigger key + var cur = this.parent; + while( cur ) { + if (this.compareKeys(key, cur.key) < 0) + return cur.data; + cur = cur.parent; + } + return []; + } + } +}; + +/** + * Search for data coming right before a specific key + */ +BinarySearchTree.prototype.searchBefore = function (key) { + if (!this.hasOwnProperty('key')) { return []; } + + if (this.compareKeys(this.key, key) === 0) { + // if there's a left child, the previous key will be there + var cur = this.left; + if( cur ) { + // within the left branch, traverse right until leaf + while( cur.right ) + cur = cur.right; + return cur.data; + } + + // traverse up until you find a smaller key + cur = this.parent; + while( cur ) { + if (this.compareKeys(key, cur.key) > 0) + return cur.data; + cur = cur.parent; + } + return []; + } + + if (this.compareKeys(key, this.key) < 0) { + if (this.left) { + return this.left.searchBefore(key); + } else { + // traverse up until you find a smaller key + var cur = this.parent; + while( cur ) { + if (this.compareKeys(key, cur.key) > 0) + return cur.data; + cur = cur.parent; + } + return []; + } + } else { + if (this.right) { + return this.right.searchBefore(key); + } else { + return this.data; + } + } +}; + /** * Return a function that tells whether a given key matches a lower bound diff --git a/test/bst.test.js b/test/bst.test.js index ceb20a9..ecc0e3d 100644 --- a/test/bst.test.js +++ b/test/bst.test.js @@ -412,6 +412,58 @@ describe('Binary search tree', function () { bst.search(63).length.should.equal(0); }); + it('Can find ascending ordered data in a BST', function () { + var bst = new BinarySearchTree() + , i; + + customUtils.getRandomArray(100).forEach(function (n) { + bst.insert(n, { key: n, value: 'some data for ' + n }); + }); + + bst.checkIsBST(); + + var key = bst.getMinKey(); + key.should.equal(0); + for (i = 1; i <= 100; i += 1) { + var next = bst.searchAfter(key); + if( i == 100 ) + next.should.deep.equal([]); + else { + next.length.should.equal(1); + next = next[0]; + next.key.should.equal(i); + next.key.should.above(key); + key = next.key; + } + } + }); + + it('Can find descending ordered data in a BST', function () { + var bst = new BinarySearchTree() + , i; + + customUtils.getRandomArray(100).forEach(function (n) { + bst.insert(n, { key: n, value: 'some data for ' + n }); + }); + + bst.checkIsBST(); + + var key = bst.getMaxKey(); + key.should.equal(99); + for (i = 1; i <= 100; i += 1) { + var next = bst.searchBefore(key); + if( i == 100 ) + next.should.deep.equal([]); + else { + next.length.should.equal(1); + next = next[0]; + next.key.should.equal(99-i); + next.key.should.below(key); + key = next.key; + } + } + }); + it('Can search for data between two bounds', function () { var bst = new BinarySearchTree();