btree-js
Version:
A rebalancing binary tree for JS
596 lines (468 loc) • 16.4 kB
JavaScript
'use strict';
var chai = require('chai');
var expect = chai.expect;
var BinaryTree = require('../src/index');
var Tree = BinaryTree.Tree;
var Node = BinaryTree.Node;
describe('constructor', function () {
it('can index based on the key passed in', function () {
var tree = new Tree({ key: 'value' });
var root = new Node({ key: 30, value: 50 });
var leaf = new Node({ key: 50, value: 30 });
tree.insert(root);
tree.insert(leaf);
expect(tree.search(root.value)).to.eq(root);
expect(tree.search(leaf.value)).to.eq(leaf);
});
});
describe('#isEmpty', function () {
it('returns true when no nodes have been inserted', function () {
var tree = new Tree();
expect(tree.isEmpty()).to.eq(true);
});
it('returns false when nodes have been inserted', function () {
var tree = new Tree();
tree.insert(new Node({ key: 10, value: 'plop' }));
expect(tree.isEmpty()).to.eq(false);
tree.delete(10);
expect(tree.isEmpty()).to.eq(true);
});
});
describe('#findPaths', function () {
it('returns all the paths from root to leaf', function () {
var tree = new Tree();
tree.bulkInsert(1, 2, 3, 4, 5, 6, 7, 8, 9);
var paths = tree.findPaths();
expect(paths.length).to.eq(5);
var expected_paths = [
[4, 2, 1],
[4, 2, 3],
[4, 6, 5],
[4, 6, 8, 7],
[4, 6, 8, 9]
];
var path_index = 0;
paths.forEach(function (path) {
var node_index = 0;
path.forEach(function (node) {
expect(expected_paths[path_index][node_index++]).to.eq(node.key);
});
path_index++;
});
});
});
describe('#next', function () {
it('returns the next node in the tree', function () {
var tree = new Tree();
tree.bulkInsert(6, 7, 8, 1, 2, 3, 4, 5);
for (var i = 1; i < 8; i++) {
expect(tree.next(tree.search(i))).to.eq(tree.search(i + 1));
}
});
it('returns null if no node passed in', function () {
var tree = new Tree();
expect(tree.next(null)).to.eq(null);
});
it('returns null if no next element', function () {
var tree = new Tree();
tree.insert(5);
expect(tree.next(tree.search(5))).to.eq(null);
});
});
describe('#prev', function () {
it('returns the prev node in the tree', function () {
var tree = new Tree();
tree.bulkInsert(6, 7, 8, 1, 2, 3, 4, 5);
for (var i = 2; i <= 8; i++) {
expect(tree.prev(tree.search(i))).to.eq(tree.search(i - 1));
}
});
it('returns null if no node passed in', function () {
var tree = new Tree();
expect(tree.prev(null)).to.eq(null);
});
it('returns null if no prev element', function () {
var tree = new Tree();
tree.insert(5);
expect(tree.prev(tree.search(5))).to.eq(null);
});
});
describe('#delete', function () {
var tree;
beforeEach(function () {
tree = new Tree();
tree.bulkInsert(100, 50, 150, 175, 125);
});
it('can delete the root', function () {
tree.delete(tree.root.key);
expect(tree.root.key).to.eq(150);
});
it('can delete the root when many elements in tree', function () {
tree = new Tree();
tree.bulkInsert(
1, 3, 5, 2, 4, 6, 7, 8, 9, 15, 11, 13, 17, 18, 14, 19, 25, 30, 22, 21, 10, 16
);
tree.delete(tree.root);
tree.delete(tree.root);
tree.delete(tree.root);
expect(tree.root.key).to.eq(9);
});
it('can delete when node has one child on left', function () {
tree = new Tree();
tree.bulkInsert(50, 25, 75, 12);
tree.delete(25);
expect(tree.root.left.key).to.eq(12);
expect(tree.root.left.parent).to.eq(tree.root);
});
it('can delete when node has one child on right', function () {
tree = new Tree();
tree.bulkInsert(50, 25, 75, 33);
tree.delete(25);
expect(tree.root.left.key).to.eq(33);
expect(tree.root.left.parent).to.eq(tree.root);
});
it('can delete when node has two children', function () {
tree.delete(150);
expect(tree.root.right.key).to.eq(125);
expect(tree.root.right.parent).to.eq(tree.root);
expect(tree.root.right.right.key).to.eq(175);
expect(tree.root.right.right.parent).to.eq(tree.root.right);
});
it('can delete when node is a leaf', function () {
tree.delete(tree.root.left.key);
// rebalancing results in 100 on left
expect(tree.root.left.key).to.eq(100);
});
it('throws error if node not found', function () {
function runner() { tree.delete(99); }
expect(runner).to.throw('Cannot delete non-existent node');
});
});
describe('#deleteAll', function () {
var tree;
beforeEach(function () {
tree = new Tree();
tree.bulkInsert(1, 2, 3, 4, 5, 6, 7, 8, 9);
});
it('removes all nodes including the root', function () {
tree.deleteAll();
expect(tree.height()).to.eq(0);
expect(tree.isEmpty()).to.eq(true);
});
});
describe('#min', function () {
it('returns the root when it is the only element', function () {
var tree = new Tree();
tree.bulkInsert(9);
expect(tree.min().key).to.eq(9);
});
it('returns node with smallest key when tree has multiple levels', function () {
var tree = new Tree();
tree.bulkInsert(9, 10, 3, 5, 0, 4, 2, 7);
expect(tree.min().key).to.eq(0);
});
});
describe('#max', function () {
it('returns the root when it is the only element', function () {
var tree = new Tree();
tree.bulkInsert(9);
expect(tree.max().key).to.eq(9);
});
it('returns node with smallest key when tree has multiple levels', function () {
var tree = new Tree();
tree.bulkInsert(9, 10, 3, 5, 0, 4, 2, 7);
expect(tree.max().key).to.eq(10);
});
});
describe('#height', function () {
it('returns 0 when there is no root', function () {
expect(new Tree().height()).to.eq(0);
});
it('returns 1 when there is only a root', function () {
var root = new Node({ key: 10 });
var tree = new Tree();
tree.insert(root);
expect(tree.height()).to.eq(1);
});
it('returns 2 when there is a second level of tree', function () {
var root = new Node({ key: 10 });
var leaf = new Node({ key: 15 });
var leaf2 = new Node({ key: 8 });
var tree = new Tree();
tree.insert(root);
tree.insert(leaf);
tree.insert(leaf2);
expect(tree.height()).to.eq(2);
});
it('returns height of subtree when subtree passed in', function () {
var root = new Node({ key: 10 });
var leaf = new Node({ key: 15 });
var leaf2 = new Node({ key: 8 });
var tree = new Tree();
tree.insert(root);
tree.insert(leaf);
tree.insert(leaf2);
expect(tree.height(leaf)).to.eq(1);
});
});
describe('#search', function () {
var tree;
beforeEach(function () {
tree = new Tree();
});
it('can return the matching node from left tree', function () {
tree.bulkInsert(5, 3, 6, 4);
expect(tree.search(4).key).to.eq(4);
});
it('can return the matching node from right tree', function () {
tree.bulkInsert(5, 3, 6, 4);
expect(tree.search(6).key).to.eq(6);
});
it('can return the matching node from root', function () {
tree.bulkInsert(5);
expect(tree.search(5).key).to.eq(5);
});
it('returns null when the node is not found', function () {
tree.bulkInsert(5, 3, 6, 4);
expect(tree.search(8)).to.eq(null);
});
});
describe('bulk insert', function () {
it('turns an array of numbers into a tree with nodes', function () {
var tree = new Tree();
tree.bulkInsert(10, 5, 15, 3, 6, 12, 18, 21);
expect(tree.root.key).to.eq(10);
expect(tree.root.left.key).to.eq(5);
expect(tree.root.left.left.key).to.eq(3);
expect(tree.root.left.right.key).to.eq(6);
expect(tree.root.right.key).to.eq(15);
expect(tree.root.right.left.key).to.eq(12);
expect(tree.root.right.right.key).to.eq(18);
expect(tree.root.right.right.right.key).to.eq(21);
});
});
describe('#insert', function () {
it('can accept a number', function () {
var tree = new Tree();
tree.insert(5);
expect(tree.root.key).to.eq(5);
});
it('can accept a number when there is custom key', function () {
var tree = new Tree({ key: 'index' });
tree.insert(5);
expect(tree.root.index).to.eq(5);
});
it('inserts to root when there is no root', function () {
var tree = new Tree();
var node = new Node({ key: 10 });
tree.insert(node);
expect(tree.root).to.eq(node);
});
it('throws error when key present and unique: true', function () {
var tree = new Tree({ unique: true });
tree.bulkInsert(4, 5, 6);
expect(function () { tree.bulkInsert(5); }).to.throw('Duplicate key violation');
});
it('does not throw error when key present and unique: false', function () {
var tree = new Tree();
tree.bulkInsert(4, 5, 6);
expect(function () { tree.bulkInsert(5); }).not.to.throw();
});
it('inserts to correct subtree when root present', function () {
var tree = new Tree();
var node1 = new Node({ key: 10 });
tree.insert(node1);
var node2 = new Node({ key: 20 });
tree.insert(node2);
expect(tree.root.right).to.eq(node2);
var node3 = new Node({ key: 5 });
tree.insert(node3);
expect(tree.root.left).to.eq(node3);
});
it('sets the correct parent on the inserted node', function () {
var tree = new Tree();
tree.bulkInsert(10, 5, 15, 2, 8);
expect(tree.search(2).parent).to.eq(tree.search(5));
});
it('rebalances tree to left when right side gets too long', function () {
var tree = new Tree();
var node1 = new Node({ key: 10 });
var node2 = new Node({ key: 20 });
var node3 = new Node({ key: 30 });
tree.insert(node1);
tree.insert(node2);
tree.insert(node3);
expect(tree.root).to.eq(node2);
expect(tree.root.left).to.eq(node1);
expect(tree.root.right).to.eq(node3);
expect(tree.height()).to.eq(2);
});
it('rebalances tree to left when right side gets too long, case 2', function () {
var tree = new Tree();
var node1 = new Node({ key: 10 });
var node2 = new Node({ key: 20 });
var node3 = new Node({ key: 15 });
tree.insert(node1);
tree.insert(node2);
tree.insert(node3);
expect(tree.root).to.eq(node3);
expect(tree.root.left).to.eq(node1);
expect(tree.root.right).to.eq(node2);
expect(tree.height()).to.eq(2);
});
it('rebalances tree to left when right side gets too long, case 3', function () {
var tree = new Tree();
tree.bulkInsert(1, 2, 3, 4, 5, 6);
expect(tree.root.key).to.eq(4);
expect(tree.root.left.key).to.eq(2);
expect(tree.root.left.parent).to.eq(tree.root);
expect(tree.root.left.left.key).to.eq(1);
expect(tree.root.left.left.parent).to.eq(tree.root.left);
expect(tree.root.left.right.key).to.eq(3);
expect(tree.root.left.right.parent).to.eq(tree.root.left);
expect(tree.root.right.key).to.eq(5);
expect(tree.root.right.parent).to.eq(tree.root);
expect(tree.root.right.right.key).to.eq(6);
expect(tree.root.right.right.parent).to.eq(tree.root.right);
});
it('rebalances tree to right when left side gets too long', function () {
var tree = new Tree();
var node1 = new Node({ key: 30 });
var node2 = new Node({ key: 20 });
var node3 = new Node({ key: 10 });
tree.insert(node1);
tree.insert(node2);
tree.insert(node3);
expect(tree.root).to.eq(node2);
expect(tree.root.right).to.eq(node1);
expect(tree.root.left).to.eq(node3);
expect(tree.height()).to.eq(2);
});
it('rebalances tree to right when left side gets too long, case 2', function () {
var tree = new Tree();
var node1 = new Node({ key: 30 });
var node2 = new Node({ key: 10 });
var node3 = new Node({ key: 20 });
tree.insert(node1);
tree.insert(node2);
tree.insert(node3);
expect(tree.root).to.eq(node3);
expect(tree.root.right).to.eq(node1);
expect(tree.root.left).to.eq(node2);
expect(tree.height()).to.eq(2);
});
it('rebalances tree to right when left side gets too long, case 3', function () {
var tree = new Tree();
tree.bulkInsert(6, 5, 4, 3, 2, 1);
expect(tree.root.key).to.eq(3);
expect(tree.root.left.key).to.eq(2);
expect(tree.root.left.parent).to.eq(tree.root);
expect(tree.root.left.left.key).to.eq(1);
expect(tree.root.left.left.parent).to.eq(tree.root.left);
expect(tree.root.right.key).to.eq(5);
expect(tree.root.right.parent).to.eq(tree.root);
expect(tree.root.right.left.key).to.eq(4);
expect(tree.root.right.left.parent).to.eq(tree.root.right);
expect(tree.root.right.right.key).to.eq(6);
expect(tree.root.right.right.parent).to.eq(tree.root.right);
});
});
describe('#swap', function () {
var tree;
var root;
beforeEach(function () {
tree = new Tree();
tree.bulkInsert(50, 25, 75);
root = tree.root;
});
it('swaps the two elements when child is the left child', function () {
tree.swap(root, root.left);
expect(tree.root.key).to.eq(25);
expect(tree.root.left.key).to.eq(50);
expect(tree.root.right.key).to.eq(75);
});
it('swaps the two elements when child is the right child', function () {
tree.swap(root, root.right);
expect(tree.root.key).to.eq(75);
expect(tree.root.left.key).to.eq(25);
expect(tree.root.right.key).to.eq(50);
});
it('swaps two elements when first is child and second is parent', function () {
tree.swap(root.right, root);
expect(tree.root.key).to.eq(75);
expect(tree.root.left.key).to.eq(25);
expect(tree.root.right.key).to.eq(50);
});
it('correctly updates the parents on the swapped elements', function () {
tree.swap(tree.root, tree.root.right);
expect(tree.root.right.parent).to.eq(tree.root);
tree.swap(tree.root, tree.root.left);
expect(tree.root.left.parent).to.eq(tree.root);
});
it('can swap at more than 1 level deep', function () {
tree.bulkInsert(60, 80);
tree.swap(root.right, root.right.left);
expect(tree.root.right.key).to.eq(60);
tree.swap(tree.root.right, tree.root.right.right);
expect(tree.root.right.key).to.eq(80);
tree.swap(tree.root, tree.root.right);
expect(tree.root.key).to.eq(80);
expect(tree.root.right.key).to.eq(50);
expect(tree.root.right.parent).to.eq(tree.root);
expect(tree.root.left.parent).to.eq(tree.root);
tree.swap(tree.root.left, tree.root);
expect(tree.root.key).to.eq(25);
expect(tree.root.left.key).to.eq(80);
expect(tree.root.left.parent).to.eq(tree.root);
expect(tree.root.right.parent).to.eq(tree.root);
});
it('can swap any two nodes (not just parent/child)', function () {
tree.bulkInsert(12, 33);
// swap siblings
tree.swap(tree.root.left.left, tree.root.left.right);
expect(tree.root.left.left.key).to.eq(33);
expect(tree.root.left.left.parent.key).to.eq(25);
expect(tree.root.left.right.key).to.eq(12);
expect(tree.root.left.right.parent.key).to.eq(25);
});
});
describe('#invert', function () {
it('puts the max value at the root', function () {
var tree = new Tree();
tree.bulkInsert(20, 25);
tree.invert();
expect(tree.root.key).to.eq(25);
});
it('inverts all the subtrees', function () {
var tree = new Tree();
tree.bulkInsert(20, 25, 15, 22, 28);
expect(tree.root.key).to.eq(20);
expect(tree.root.left.key).to.eq(15);
expect(tree.root.right.key).to.eq(25);
tree.invert();
expect(tree.root.key).to.eq(28);
expect(tree.root.left.key).to.eq(15);
expect(tree.root.right.key).to.eq(20);
expect(tree.root.right.left.key).to.eq(22);
expect(tree.root.right.right.key).to.eq(25);
});
});
describe('#inOrderTraversal', function () {
it('yields the tree nodes in order, with metadata', function () {
var tree = new Tree();
tree.bulkInsert(50, 25, 75, 12, 80, 30);
var node_order = [
{ node: tree.root.left.left, depth: 3 },
{ node: tree.root.left, depth: 2 },
{ node: tree.root.left.right, depth: 3 },
{ node: tree.root, depth: 1 },
{ node: tree.root.right, depth: 2 },
{ node: tree.root.right.right, depth: 3 }
];
tree.inOrderTraversal(function (node, sequence, depth) {
var node_order_entry = node_order[sequence - 1];
expect(node).to.eq(node_order_entry.node);
expect(depth).to.eq(node_order_entry.depth);
});
});
});