diff --git a/Gruntfile.js b/Gruntfile.js index 59c6d61..468355e 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,16 +1,16 @@ module.exports = (grunt) => { grunt.initConfig({ eslint: { - src: ['./*.js', './*.spec.js'] + src: ['./*.js', './*.test.js'] }, mochaTest: { - files: ['./*.spec.js'] + files: ['./*.test.js'] }, mocha_istanbul: { coverage: { src: './', options: { - mask: '*.spec.js' + mask: '*.test.js' } } } diff --git a/README.md b/README.md index 3d72a49..c38bad6 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,18 @@ [![npm](https://img.shields.io/npm/v/@datastructures-js/binary-search-tree.svg)](https://www.npmjs.com/package/@datastructures-js/binary-search-tree) [![npm](https://img.shields.io/npm/dm/@datastructures-js/binary-search-tree.svg)](https://www.npmjs.com/package/@datastructures-js/binary-search-tree) [![npm](https://img.shields.io/badge/node-%3E=%206.0-blue.svg)](https://www.npmjs.com/package/@datastructures-js/binary-search-tree) -node's data type: **string**, **number**. +node's **key** data type: **string**, **number**. +node's **value** data type: any. Binary Search Tree ## Usage +``` +npm install --save @datastructures-js/binary-search-tree +``` + +then + ```js const binarySearchTree = require('@datastructures-js/binary-search-tree'); const bst = binarySearchTree(); @@ -16,82 +23,84 @@ const bst = binarySearchTree(); ## API -**.node(value, parent, left, right)** - -creates a bst node. +### .node(key, value, parent, left, right) +creates a bst node with the following api. -* **.setValue(value)** sets the node's value. -* **.getValue()** gets the node's value. -* **.setParent(parent)** sets the parent node. -* **.getParent()** gets the parent node. -* **.setLeft(left)** sets the node's left child. -* **.getLeft()** gets the node's left child. -* **.setRight(right)** sets the node's right child. -* **.getRight()** gets the node's right child. +* .setKey(key) +* .getKey() +* .setValue(value) +* .getValue() +* .setParent(node) +* .getParent() +* .setLeft(node) +* .getLeft() +* .setRight(node) +* .getRight() ```js -const n = bst.node('test'); +const n = bst.node(1, 'test'); +console.log(n.getKey()); // 1 console.log(n.getValue()); // test console.log(n.getParent()); // null console.log(n.getLeft()); // null console.log(n.getRight()); // null ``` -**.insert(value)** +### .insert(key, value) -inserts a value into the tree. +inserts a node with key/value into the tree. ```javascript -bst.insert(50); -bst.insert(80); -bst.insert(30); -bst.insert(90); -bst.insert(60); -bst.insert(40); -bst.insert(20); +bst.insert(50, 'v1'); +bst.insert(80, 'v2'); +bst.insert(30, 'v3'); +bst.insert(90, 'v4'); +bst.insert(60, 'v5'); +bst.insert(40, 'v6'); +bst.insert(20, 'v7'); ``` -**.root()** +### .root() gets the root node ```javascript -console.log(bst.root().getValue()); // 50 +console.log(bst.root().getKey()); // 50 ``` -**.min()** +### .min() -finds the min value node (most left). +finds the min key node (most left). ```javascript -console.log(bst.min().getValue()); // 20 +console.log(bst.min().getKey()); // 20 ``` -**.max()** +### .max() -finds the min value node (most right). +finds the max key node (most right). ```javascript -console.log(bst.max().getValue()); // 90 +console.log(bst.max().getKey()); // 90 ``` -**.count()** +### .count() gets nodes count. ```javascript console.log(bst.count()); // 7 ``` -**.find(value)** +### .search(key) -finds the value's node or returns null if not found. +finds a node by key or returns null if not found. ```javascript -let n = bst.find(30); -console.log(n.getValue()); // 30 -console.log(n.getRight().getValue()); // 40 -console.log(n.getLeft().getValue()); // 20 +const n = bst.search(30); +console.log(n.getKey()); // 30 +console.log(n.getRight().getKey()); // 40 +console.log(n.getLeft().getKey()); // 20 ``` -**.traverseInOrder(cb)** +### .traverseInOrder(cb) ```js // in-order traverse (left-parent-right) -bst.traverseInOrder(node => console.log(node.getValue())); +bst.traverseInOrder(node => console.log(node.getKey())); // 20 // 30 @@ -102,11 +111,11 @@ bst.traverseInOrder(node => console.log(node.getValue())); // 90 ``` -**.traversePreOrder(cb)** +### .traversePreOrder(cb) ```js // pre-order traverse (parent-left-right) -bst.traversePreOrder(node => console.log(node.getValue())); +bst.traversePreOrder(node => console.log(node.getKey())); // 50 // 30 @@ -117,11 +126,11 @@ bst.traversePreOrder(node => console.log(node.getValue())); // 90 ``` -**.traversePostOrder(cb)** +### .traversePostOrder(cb) ```js // post-order traverse (left-right-parent) -bst.traverse(node => console.log(node.getValue())); +bst.traverse(node => console.log(node.getKey())); // 20 // 40 @@ -132,14 +141,14 @@ bst.traverse(node => console.log(node.getValue())); // 50 ``` -**.traverse(cb, order)** +### .traverse(cb, order) traverse the tree in the defined order and apply a callback on each node. order values: `inOrder`, `preOrder` OR `postOrder`. default is `inOrder` ```js -bst.traverse(node => console.log(node.getValue())); // in-order +bst.traverse(node => console.log(node.getKey())); // in-order // 20 // 30 @@ -149,7 +158,7 @@ bst.traverse(node => console.log(node.getValue())); // in-order // 80 // 90 -bst.traverse(node => console.log(node.getValue()), 'preOrder'); +bst.traverse(node => console.log(node.getKey()), 'preOrder'); // 50 // 30 @@ -161,16 +170,16 @@ bst.traverse(node => console.log(node.getValue()), 'preOrder'); ``` -**.remove(value)** +### .remove(value) removes a value's node (if exists) from the tree. ```javascript -console.log(bst.find(30).getValue()); // 30 +console.log(bst.search(30).getKey()); // 30 bst.remove(30); -console.log(bst.find(30)); // null +console.log(bst.search(30)); // null ``` -**.clear()** +### .clear() clears the tree. ```javascript diff --git a/index.js b/index.js index c73139f..102786b 100644 --- a/index.js +++ b/index.js @@ -8,12 +8,25 @@ * binary tree node * @function */ -const node = (v, p, l, r) => { +const node = (k, v, p, l, r) => { + let key = k; let value = v; let parent = p || null; let left = l || null; let right = r || null; + /** + * @returns {string|number} + */ + const getKey = () => key; + + /** + * @param {string|number} + */ + const setKey = (ky) => { + key = ky; + }; + /** * @param {object} value */ @@ -22,7 +35,7 @@ const node = (v, p, l, r) => { }; /** - * @returns {string|number} + * @returns {object} */ const getValue = () => value; @@ -64,6 +77,8 @@ const node = (v, p, l, r) => { // binary tree node api return { + setKey, + getKey, setValue, getValue, setParent, @@ -122,12 +137,12 @@ const binarySearchTree = () => { * @param {(string|number)} value * @returns {object} node */ - const find = (value) => { + const search = (key) => { let currentNode = rootNode; while (currentNode !== null) { - if (value > currentNode.getValue()) { + if (key > currentNode.getKey()) { currentNode = currentNode.getRight(); - } else if (value < currentNode.getValue()) { + } else if (key < currentNode.getKey()) { currentNode = currentNode.getLeft(); } else { return currentNode; @@ -137,24 +152,25 @@ const binarySearchTree = () => { }; /** - * inserts a node by a given value into the tree - * @param {(string|number)} value + * inserts a node by a given (key, value) into the tree + * @param {(string|number)} key + * @param {object} value */ - const insert = (value) => { + const insert = (key, value) => { const insertFn = (currentNode) => { if (currentNode === null) { - rootNode = node(value); + rootNode = node(key, value); nodesCount += 1; - } else if (value < currentNode.getValue()) { + } else if (key < currentNode.getKey()) { if (currentNode.getLeft() === null) { - currentNode.setLeft(node(value, currentNode)); + currentNode.setLeft(node(key, value, currentNode)); nodesCount += 1; } else { insertFn(currentNode.getLeft()); } - } else if (value > currentNode.getValue()) { + } else if (key > currentNode.getKey()) { if (currentNode.getRight() === null) { - currentNode.setRight(node(value, currentNode)); + currentNode.setRight(node(key, value, currentNode)); nodesCount += 1; } else { insertFn(currentNode.getRight()); @@ -166,24 +182,24 @@ const binarySearchTree = () => { /** * removes a node by a given value from the tree - * @param {(string|number)} value + * @param {(string|number)} key */ - const remove = (value) => { - const removeFn = (val, currentNode) => { + const remove = (key) => { + const removeFn = (k, currentNode) => { if (currentNode !== null) { const left = currentNode.getLeft(); const right = currentNode.getRight(); - if (val > currentNode.getValue()) { - removeFn(val, right); - } else if (val < currentNode.getValue()) { - removeFn(val, left); + if (k > currentNode.getKey()) { + removeFn(k, right); + } else if (k < currentNode.getKey()) { + removeFn(k, left); } else { const parent = currentNode.getParent(); if (right === null && left === null) { // remove a node with no children if (parent === null) { rootNode = null; - } else if (currentNode.getValue() >= parent.getValue()) { + } else if (currentNode.getKey() >= parent.getKey()) { parent.setRight(null); } else { parent.setLeft(null); @@ -193,7 +209,7 @@ const binarySearchTree = () => { // remove a node with a left child if (parent === null) { rootNode = left; - } else if (currentNode.getValue() > parent.getValue()) { + } else if (currentNode.getKey() > parent.getKey()) { parent.setRight(left); } else { parent.setLeft(left); @@ -204,7 +220,7 @@ const binarySearchTree = () => { // remove a node with a right child if (parent === null) { rootNode = right; - } else if (currentNode.getValue() > parent.getValue()) { + } else if (currentNode.getKey() > parent.getKey()) { parent.setRight(right); } else { parent.setLeft(right); @@ -214,13 +230,13 @@ const binarySearchTree = () => { } else { // remove a node with two children const minRight = min(right); - currentNode.setValue(minRight.getValue()); - removeFn(minRight.getValue(), minRight); + currentNode.setKey(minRight.getKey()); + removeFn(minRight.getKey(), minRight); } } } }; - removeFn(value, rootNode); + removeFn(key, rootNode); }; /** @@ -305,7 +321,7 @@ const binarySearchTree = () => { clear, max, min, - find, + search, insert, remove, traverseInOrder, diff --git a/index.spec.js b/index.spec.js deleted file mode 100644 index 379cc27..0000000 --- a/index.spec.js +++ /dev/null @@ -1,130 +0,0 @@ -const { expect } = require('chai'); -const binarySearchTree = require('./index'); - -describe('binarySearchTree tests', () => { - const bst = binarySearchTree(); - - describe('.insert(value)', () => - it('should insert nodes to the tree', () => { - bst.insert(50); - bst.insert(80); - bst.insert(30); - bst.insert(90); - bst.insert(60); - bst.insert(40); - bst.insert(20); - bst.insert(20); // should not be inserted. - expect(bst.count()).to.be.equal(7); - expect(bst.root().getValue()).to.equal(50); - expect(bst.root().getRight().getValue()).to.equal(80); - expect(bst.root().getLeft().getValue()).to.equal(30); - })); - - describe('.min()', () => - it('should get the node with min value', () => - expect(bst.min().getValue(20)))); - - describe('.max()', () => - it('should get the node with max value', () => - expect(bst.max().getValue(90)))); - - describe('.root()', () => - it('should get the root node', () => - expect(bst.root().getValue(50)))); - - describe('.find(value)', () => - it('should find a value in the tree', () => { - expect(bst.find(40).getValue()).to.equal(40); - expect(bst.find(100)).to.equal(null); - })); - - describe('.traverse(cb, type)', () => { - it('should traverse the tree in order', () => { - const values = []; - bst.traverse(node => values.push(node.getValue()), 'inOrder'); - expect(values).to.deep.equal([20, 30, 40, 50, 60, 80, 90]); - }); - - it('should traverse the tree pre order', () => { - const values = []; - bst.traverse(node => values.push(node.getValue()), 'preOrder'); - expect(values).to.deep.equal([50, 30, 20, 40, 80, 60, 90]); - }); - - it('should traverse the tree post order', () => { - const values = []; - bst.traverse(node => values.push(node.getValue()), 'postOrder'); - expect(values).to.deep.equal([20, 40, 30, 60, 90, 80, 50]); - }); - - it('should traverse the tree in order by default', () => { - const values = []; - bst.traverse(node => values.push(node.getValue())); - expect(values).to.deep.equal([20, 30, 40, 50, 60, 80, 90]); - }); - }); - - describe('.remove(value)', () => { - it('should remove a leaf node', () => { - bst.remove(20); - expect(bst.find(20)).to.equal(null); - expect(bst.find(30).getLeft()).to.equal(null); - expect(bst.count()).to.equal(6); - }); - - it('should remove a node with a right child only', () => { - bst.remove(30); - expect(bst.find(30)).to.equal(null); - expect(bst.root().getLeft().getValue()).to.equal(40); - expect(bst.count()).to.equal(5); - }); - - it('should remove a node with a left child only', () => { - bst.insert(30); - bst.remove(40); - expect(bst.find(40)).to.equal(null); - expect(bst.root().getLeft().getValue()).to.equal(30); - expect(bst.count()).to.equal(5); - }); - - it('should remove a node with two children', () => { - bst.remove(80); - expect(bst.find(80)).to.equal(null); - expect(bst.root().getRight().getValue()).to.equal(90); - expect(bst.find(90).getRight()).to.equal(null); - expect(bst.find(90).getLeft().getValue()).to.equal(60); - expect(bst.count()).to.equal(4); - }); - - it('should remove root node with right child', () => { - bst.insert(100); - bst.remove(60); - bst.remove(90); - bst.remove(30); - bst.remove(50); - expect(bst.root().getValue()).to.equal(100); - }); - - it('should remove root node with left child', () => { - bst.insert(20); - bst.insert(30); - bst.insert(25); - bst.remove(30); - bst.remove(25); - bst.remove(100); - expect(bst.root().getValue()).to.equal(20); - }); - - it('should remove root node', () => { - bst.remove(20); - expect(bst.root()).to.equal(null); - }); - }); - - describe('.clear()', () => { - bst.clear(); - expect(bst.count()).to.equal(0); - expect(bst.root()).to.equal(null); - expect(bst.remove(10)).to.equal(undefined); - }); -}); diff --git a/index.test.js b/index.test.js new file mode 100644 index 0000000..5b6caa2 --- /dev/null +++ b/index.test.js @@ -0,0 +1,133 @@ +const { expect } = require('chai'); +const binarySearchTree = require('./index'); + +describe('binarySearchTree tests', () => { + const bst = binarySearchTree(); + + describe('.insert(key, value)', () => + it('should insert nodes to the tree', () => { + bst.insert(50, 'n1'); + bst.insert(80, 'n2'); + bst.insert(30, 'n3'); + bst.insert(90, 'n4'); + bst.insert(60, 'n5'); + bst.insert(40, 'n6'); + bst.insert(20, 'n7'); + bst.insert(20, 'n8'); // should not be inserted. + expect(bst.count()).to.be.equal(7); + expect(bst.root().getKey()).to.equal(50); + expect(bst.root().getValue()).to.equal('n1'); + expect(bst.root().getRight().getKey()).to.equal(80); + expect(bst.root().getRight().getValue()).to.equal('n2'); + expect(bst.root().getLeft().getKey()).to.equal(30); + expect(bst.root().getLeft().getValue()).to.equal('n3'); + })); + + describe('.min()', () => + it('should get the node with min value', () => + expect(bst.min().getKey(20)))); + + describe('.max()', () => + it('should get the node with max value', () => + expect(bst.max().getKey(90)))); + + describe('.root()', () => + it('should get the root node', () => + expect(bst.root().getKey(50)))); + + describe('.search(key)', () => + it('should search a node by its key in the tree', () => { + expect(bst.search(40).getKey()).to.equal(40); + expect(bst.search(100)).to.equal(null); + })); + + describe('.traverse(cb, type)', () => { + it('should traverse the tree in order', () => { + const keys = []; + bst.traverse(node => keys.push(node.getKey()), 'inOrder'); + expect(keys).to.deep.equal([20, 30, 40, 50, 60, 80, 90]); + }); + + it('should traverse the tree pre order', () => { + const keys = []; + bst.traverse(node => keys.push(node.getKey()), 'preOrder'); + expect(keys).to.deep.equal([50, 30, 20, 40, 80, 60, 90]); + }); + + it('should traverse the tree post order', () => { + const keys = []; + bst.traverse(node => keys.push(node.getKey()), 'postOrder'); + expect(keys).to.deep.equal([20, 40, 30, 60, 90, 80, 50]); + }); + + it('should traverse the tree in order by default', () => { + const keys = []; + bst.traverse(node => keys.push(node.getKey())); + expect(keys).to.deep.equal([20, 30, 40, 50, 60, 80, 90]); + }); + }); + + describe('.remove(key)', () => { + it('should remove a leaf node', () => { + bst.remove(20); + expect(bst.search(20)).to.equal(null); + expect(bst.search(30).getLeft()).to.equal(null); + expect(bst.count()).to.equal(6); + }); + + it('should remove a node with a right child only', () => { + bst.remove(30); + expect(bst.search(30)).to.equal(null); + expect(bst.root().getLeft().getKey()).to.equal(40); + expect(bst.count()).to.equal(5); + }); + + it('should remove a node with a left child only', () => { + bst.insert(30); + bst.remove(40); + expect(bst.search(40)).to.equal(null); + expect(bst.root().getLeft().getKey()).to.equal(30); + expect(bst.count()).to.equal(5); + }); + + it('should remove a node with two children', () => { + bst.remove(80); + expect(bst.search(80)).to.equal(null); + expect(bst.root().getRight().getKey()).to.equal(90); + expect(bst.search(90).getRight()).to.equal(null); + expect(bst.search(90).getLeft().getKey()).to.equal(60); + expect(bst.count()).to.equal(4); + }); + + it('should remove root node with right child', () => { + bst.insert(100); + bst.remove(60); + bst.remove(90); + bst.remove(30); + bst.remove(50); + expect(bst.root().getKey()).to.equal(100); + }); + + it('should remove root node with left child', () => { + bst.insert(20); + bst.insert(30); + bst.insert(25); + bst.remove(30); + bst.remove(25); + bst.remove(100); + expect(bst.root().getKey()).to.equal(20); + }); + + it('should remove root node', () => { + bst.remove(20); + expect(bst.root()).to.equal(null); + }); + }); + + describe('.clear()', () => { + bst.clear(); + expect(bst.count()).to.equal(0); + expect(bst.root()).to.equal(null); + expect(bst.remove(10)).to.equal(undefined); + }); +}); diff --git a/package.json b/package.json index 292f344..b1576e1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@datastructures-js/binary-search-tree", - "version": "1.0.9", + "version": "2.0.0", "description": "binary search tree implementation in javascript", "main": "index.js", "scripts": {