From 65a68c911719809e44f3c0c0deb26b8b2371458e Mon Sep 17 00:00:00 2001 From: Eyas Ranjous Date: Thu, 26 Mar 2020 14:21:03 -0600 Subject: [PATCH 01/11] update --- .travis.yml | 6 ++++-- Gruntfile.js | 6 +++--- LICENSE | 2 +- package.json | 14 +++++++------- 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index 623a256..32b2ca6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,10 @@ language: node_js node_js: - - "6" - - "7" - "8" + - "9" + - "10" + - "11" + - "12" install: - npm install -g grunt-cli - npm install diff --git a/Gruntfile.js b/Gruntfile.js index 468355e..bc33154 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,14 +1,14 @@ module.exports = (grunt) => { grunt.initConfig({ eslint: { - src: ['./*.js', './*.test.js'] + src: ['src/*.js', 'test/*.test.js'] }, mochaTest: { - files: ['./*.test.js'] + files: ['test/*.test.js'] }, mocha_istanbul: { coverage: { - src: './', + src: 'test', options: { mask: '*.test.js' } diff --git a/LICENSE b/LICENSE index 3a48586..64ed8f6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2018 Eyas Ranjous +Copyright (c) 2020 Eyas Ranjous Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/package.json b/package.json index 824515b..4536914 100644 --- a/package.json +++ b/package.json @@ -24,15 +24,15 @@ }, "homepage": "https://github.com/datastructures-js/binary-search-tree#readme", "devDependencies": { - "chai": "^4.1.2", - "eslint": "^4.19.1", - "eslint-config-airbnb-base": "^12.1.0", - "eslint-plugin-import": "^2.12.0", - "grunt-eslint": "^20.2.0", - "grunt": "^1.0.1", + "chai": "^4.2.0", + "eslint": "^6.7.2", + "eslint-config-airbnb-base": "^14.0.0", + "eslint-plugin-import": "^2.19.1", + "grunt": "^1.0.4", + "grunt-eslint": "^22.0.0", "grunt-mocha-istanbul": "^5.0.2", "grunt-mocha-test": "^0.13.3", "istanbul": "^0.4.5", - "mocha": "^5.0.0" + "mocha": "^6.2.2" } } From a2566b432e40a33457e7ecc49c1f93ff78bb8ed3 Mon Sep 17 00:00:00 2001 From: Eyas Ranjous Date: Thu, 26 Mar 2020 14:21:34 -0600 Subject: [PATCH 02/11] add BinarySearchTreeNode --- src/binarySearchTreeNode.js | 140 ++++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 src/binarySearchTreeNode.js diff --git a/src/binarySearchTreeNode.js b/src/binarySearchTreeNode.js new file mode 100644 index 0000000..867cb41 --- /dev/null +++ b/src/binarySearchTreeNode.js @@ -0,0 +1,140 @@ +/** + * datastructures-js/binary-search-tree + * @copyright 2020 Eyas Ranjous + * @license MIT + */ + +/** + * @class BinarySearchTreeNode + */ + class BinarySearchTreeNode { + constructor(key, value) { + this.key = key; + this.value = value; + this.left = null; + this.right = null; + this.parent = null; + this.height = 1; + } + + /** + * @internal + * @param {number|string} + */ + setKey(key) { + this.key = key; + } + + /** + * @public + * @return {number|string} + */ + getKey() { + return this.key; + } + + /** + * @public + * @param {object} + */ + setValue(value) { + this.value = value; + } + + /** + * @public + * @return {object} + */ + getValue() { + return this.value; + } + + /** + * @internal + * @param {BinarySearchTreeNode} + */ + setLeft(left) { + return this.left; + } + + /** + * @public + * @return {BinarySearchTreeNode} + */ + getLeft() { + return this.left; + } + + /** + * @internal + * @param {BinarySearchTreeNode} + */ + setRight(right) { + this.right = right; + } + + /** + * @public + * @return {BinarySearchTreeNode} + */ + getRight() { + return this.right; + } + + /** + * @internal + * @param {BinarySearchTreeNode} + */ + setParent(parent) { + this.parent = parent; + } + + /** + * @public + * @return {BinarySearchTreeNode} + */ + getParent() { + return this.parent; + } + + /** + * @public + * @return {number} + */ + getLeftHeight() { + return this.left !== null ? this.left.getHeight() || 0; + } + + /** + * @public + * @return {number} + */ + getRightHeight() { + return this.right !== null ? this.right.getHeight() || 0; + } + + /** + * @public + */ + updateHeight() { + this.height = Math.max(this.getLeftHeight(), this.getRightHeight()) + 1; + } + + /** + * @public + * @return {number} + */ + getHeight() { + return this.height; + } + + /** + * @public + * @return {number} + */ + getBalance() { + return this.getLeftHeight() - this.getRightHeight(); + } +} + +module.exports = BinarySearchTreeNode; From 881a6739d33412b89abee1a326a3cad29fcd7a7f Mon Sep 17 00:00:00 2001 From: Eyas Ranjous Date: Fri, 27 Mar 2020 00:39:00 -0600 Subject: [PATCH 03/11] lint --- src/binarySearchTreeNode.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/binarySearchTreeNode.js b/src/binarySearchTreeNode.js index 867cb41..ceca90f 100644 --- a/src/binarySearchTreeNode.js +++ b/src/binarySearchTreeNode.js @@ -7,7 +7,7 @@ /** * @class BinarySearchTreeNode */ - class BinarySearchTreeNode { +class BinarySearchTreeNode { constructor(key, value) { this.key = key; this.value = value; @@ -54,7 +54,7 @@ * @param {BinarySearchTreeNode} */ setLeft(left) { - return this.left; + this.left = left; } /** @@ -102,7 +102,7 @@ * @return {number} */ getLeftHeight() { - return this.left !== null ? this.left.getHeight() || 0; + return this.left !== null ? this.left.getHeight() : 0; } /** @@ -110,7 +110,7 @@ * @return {number} */ getRightHeight() { - return this.right !== null ? this.right.getHeight() || 0; + return this.right !== null ? this.right.getHeight() : 0; } /** From 27f83161a6fd5dbb2188a95e44076600a84ec65f Mon Sep 17 00:00:00 2001 From: Eyas Ranjous Date: Fri, 27 Mar 2020 00:39:34 -0600 Subject: [PATCH 04/11] add BinarySearchTree class --- src/binarySearchTree.js | 259 ++++++++++++++++++++++++++++++++++ test/binarySearchTree.test.js | 167 ++++++++++++++++++++++ 2 files changed, 426 insertions(+) create mode 100644 src/binarySearchTree.js create mode 100644 test/binarySearchTree.test.js diff --git a/src/binarySearchTree.js b/src/binarySearchTree.js new file mode 100644 index 0000000..5efa23a --- /dev/null +++ b/src/binarySearchTree.js @@ -0,0 +1,259 @@ +/** + * datastructures-js/binary-search-tree + * @copyright 2020 Eyas Ranjous + * @license MIT + */ + +const BinarySearchTreeNode = require('./binarySearchTreeNode'); + +/** + * @class BinarySearchTree + */ +class BinarySearchTree { + constructor() { + this.rootNode = null; + this.nodesCount = 0; + } + + /** + * @public + * inserts a node with a key/value into the tree + * @param {number|string} key + * @param {object} vaue + * @return {BinartSearchTreeNode} + */ + insert(key, value, node = this.rootNode) { + const newNode = new BinarySearchTreeNode(key, value); + + if (node === null) { + this.rootNode = newNode; + this.nodesCount += 1; + return newNode; + } + + if (key < node.getKey() && node.getLeft() === null) { + node.setLeft(newNode); + newNode.setParent(node); + this.nodesCount += 1; + return newNode; + } + + if (key > node.getKey() && node.getRight() === null) { + node.setRight(newNode); + newNode.setParent(node); + this.nodesCount += 1; + return newNode; + } + + if (key === node.getKey()) { + node.setValue(value); + return newNode; + } + + if (key < node.getKey()) { + return this.insert(key, value, node.getLeft()); + } + + return this.insert(key, value, node.getRight()); + } + + /** + * @public + * check if a value exists in the tree by its key + * @param {number|string} key + * @return {boolean} + */ + has(key, node = this.rootNode) { + if (node === null) return false; + + if (key === node.getKey()) return true; + + if (key < node.getKey()) return this.has(key, node.getLeft()); + + return this.has(key, node.getRight()); + } + + /** + * @public + * finds the key's node in the tree + * @param {number|string} key + * @return {BinarySearchTreeNode} + */ + find(key, node = this.rootNode) { + if (node === null) return null; + + if (key === node.getKey()) return node; + + if (key < node.getKey()) return this.find(key, node.getLeft()); + + return this.find(key, node.getRight()); + } + + /** + * @public + * finds the node with max key (most right) in the tree + * @param {number|string} key + * @return {BinarySearchTreeNode} + */ + max(node = this.rootNode) { + if (node === null) return null; + + if (node.getRight() === null) return node; + + return this.max(node.getRight()); + } + + /** + * @public + * finds the node with min key (most left) in the tree + * @param {number|string} key + * @return {BinarySearchTreeNode} + */ + min(node = this.rootNode) { + if (node === null) return null; + + if (node.getLeft() === null) return node; + + return this.min(node.getLeft()); + } + + /** + * @public + * gets the tree root node + * @return {BinarySearchTreeNode} + */ + root() { + return this.rootNode; + } + + /** + * @public + * gets nodes count in the tree + * @return {number} + */ + count() { + return this.nodesCount; + } + + /** + * @public + * remove a node by its key + * @param {number|string} key + * @return {boolean} + */ + remove(key, node = this.rootNode) { + if (node === null) return false; + + if (key < node.getKey()) { + return this.remove(key, node.getLeft()); + } + + if (key > node.getKey()) { + return this.remove(key, node.getRight()); + } + + if (node.getLeft() === null && node.getRight() === null) { + if (node.getParent() === null) { + this.rootNode = null; + } else if (node.getKey() < node.getParent().getKey()) { + node.getParent().setLeft(null); + } else { + node.getParent().setRight(null); + } + this.nodesCount -= 1; + return true; + } + + if (node.getRight() === null) { + if (node.getParent() === null) { + this.rootNode = node.getLeft(); + } else if (node.getKey() < node.getParent().getKey()) { + node.getParent().setLeft(node.getLeft()); + } else { + node.getParent().setRight(node.getLeft()); + } + node.getLeft().setParent(node.getParent()); + this.nodesCount -= 1; + return true; + } + + if (node.getLeft() === null) { + if (node.getParent() === null) { + this.rootNode = node.getRight(); + } else if (node.getKey() < node.getParent().getKey()) { + node.getParent().setLeft(node.getRight()); + } else { + node.getParent().setRight(node.getRight()); + } + node.getRight().setParent(node.getParent()); + this.nodesCount -= 1; + return true; + } + + const minRight = this.min(node.getRight()); + node.setKey(minRight.getKey()); + return this.remove(minRight.getKey(), minRight); + } + + /** + * @public + * traverse the tree in-order (left-node-right) + * @param {function} cb + */ + traverseInOrder(cb, node = this.rootNode) { + if (typeof cb !== 'function') { + throw new Error('.traverseInOrder(cb) expects a callback'); + } + + if (node === null) return; + + this.traverseInOrder(cb, node.getLeft()); + cb(node); + this.traverseInOrder(cb, node.getRight()); + } + + /** + * @public + * traverse the tree pre-order (node-left-right) + * @param {function} cb + */ + traversePreOrder(cb, node = this.rootNode) { + if (typeof cb !== 'function') { + throw new Error('.traversePreOrder(cb) expects a callback'); + } + + if (node === null) return; + + cb(node); + this.traversePreOrder(cb, node.getLeft()); + this.traversePreOrder(cb, node.getRight()); + } + + /** + * @public + * traverse the tree post-order (left-right-node) + * @param {function} cb + */ + traversePostOrder(cb, node = this.rootNode) { + if (typeof cb !== 'function') { + throw new Error('.traversePostOrder(cb) expects a callback'); + } + + if (node === null) return; + + this.traversePostOrder(cb, node.getLeft()); + this.traversePostOrder(cb, node.getRight()); + cb(node); + } + + /** + * @public + * clears the tree + */ + clear() { + this.rootNode = null; + this.nodesCount = 0; + } +} + +module.exports = BinarySearchTree; diff --git a/test/binarySearchTree.test.js b/test/binarySearchTree.test.js new file mode 100644 index 0000000..ae5c42d --- /dev/null +++ b/test/binarySearchTree.test.js @@ -0,0 +1,167 @@ +const { expect } = require('chai'); +const BinarySearchTreeNode = require('../src/binarySearchTreeNode'); +const BinarySearchTree = require('../src/binarySearchTree'); + +describe('binarySearchTree tests', () => { + const bst = new 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, 'n20'); + bst.insert(20, 'n7'); // updates value of existing node + }); + }); + + describe('.root()', () => { + it('should get the root node', () => { + 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('.count()', () => { + it('get the count of nodes in the tree', () => { + expect(bst.count()).to.be.equal(7); + }); + }); + + describe('.has(key)', () => { + it('checks if a node exists by key', () => { + expect(bst.has(50)).to.equal(true); + expect(bst.has(80)).to.equal(true); + expect(bst.has(30)).to.equal(true); + expect(bst.has(90)).to.equal(true); + expect(bst.has(50)).to.equal(true); + expect(bst.has(40)).to.equal(true); + expect(bst.has(20)).to.equal(true); + expect(bst.has(100)).to.equal(false); + }); + }); + + describe('.find(key)', () => { + it('should search a node by its key in the tree', () => { + expect(bst.find(50)).to.be.instanceof(BinarySearchTreeNode); + expect(bst.find(80)).to.be.instanceof(BinarySearchTreeNode); + expect(bst.find(30)).to.be.instanceof(BinarySearchTreeNode); + expect(bst.find(90)).to.be.instanceof(BinarySearchTreeNode); + expect(bst.find(50)).to.be.instanceof(BinarySearchTreeNode); + expect(bst.find(40)).to.be.instanceof(BinarySearchTreeNode); + expect(bst.find(20)).to.be.instanceof(BinarySearchTreeNode); + expect(bst.find(100)).to.equal(null); + }); + }); + + describe('.max()', () => { + it('should get the node with max value', () => { + const max = bst.max(); + expect(max.getKey()).to.equal(90); + expect(max.getValue()).to.equal('n4'); + }); + }); + + describe('.min()', () => { + it('should get the node with min value', () => { + const min = bst.min(); + expect(min.getKey()).to.equal(20); + expect(min.getValue()).to.equal('n7'); + }); + }); + + describe('.traverseInOrder(cb)', () => { + it('traverse the tree in-order', () => { + const keys = []; + bst.traverseInOrder((node) => keys.push(node.getKey())); + expect(keys).to.deep.equal([20, 30, 40, 50, 60, 80, 90]); + }); + }); + + describe('.traversePreOrder(cb)', () => { + it('traverse the tree pre-order', () => { + const keys = []; + bst.traversePreOrder((node) => keys.push(node.getKey())); + expect(keys).to.deep.equal([50, 30, 20, 40, 80, 60, 90]); + }); + }); + + describe('.traversePostOrder(cb)', () => { + it('traverse the tree post-order', () => { + const keys = []; + bst.traversePostOrder((node) => keys.push(node.getKey())); + expect(keys).to.deep.equal([20, 40, 30, 60, 90, 80, 50]); + }); + }); + + describe('.remove(key)', () => { + it('should remove a leaf node', () => { + bst.remove(20); + expect(bst.has(20)).to.equal(false); + 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.has(30)).to.equal(false); + 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.has(40)).to.equal(false); + 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.has(80)).to.equal(false); + expect(bst.root().getRight().getKey()).to.equal(90); + expect(bst.find(90).getRight()).to.equal(null); + expect(bst.find(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(false); + }); +}); From 8207a9bd8e59db40fcc8f0045853cca9425798d9 Mon Sep 17 00:00:00 2001 From: Eyas Ranjous Date: Fri, 27 Mar 2020 00:40:10 -0600 Subject: [PATCH 05/11] update --- index.js | 335 +------------------------------------------------- index.test.js | 133 -------------------- 2 files changed, 2 insertions(+), 466 deletions(-) delete mode 100644 index.test.js diff --git a/index.js b/index.js index 102786b..b5c1762 100644 --- a/index.js +++ b/index.js @@ -1,334 +1,3 @@ -/** - * datastructures-js/binary-search-tree - * @copyright 2018 Eyas Ranjous - * @license MIT - */ +const BinarySearchTree = require('./src/binarySearchTree'); -/** - * binary tree node - * @function - */ -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 - */ - const setValue = (val) => { - value = val; - }; - - /** - * @returns {object} - */ - const getValue = () => value; - - /** - * @param {object} pr - */ - const setParent = (pr) => { - parent = pr; - }; - - /** - * @returns {object} node - */ - const getParent = () => parent; - - /** - * @param {BinaryNode} node - */ - const setLeft = (lf) => { - left = lf; - }; - - /** - * @returns {object} node - */ - const getLeft = () => left; - - /** - * @param {object} rg - */ - const setRight = (rg) => { - right = rg; - }; - - /** - * @returns {BinaryNode} - */ - const getRight = () => right; - - // binary tree node api - return { - setKey, - getKey, - setValue, - getValue, - setParent, - getParent, - setRight, - getRight, - setLeft, - getLeft - }; -}; - -/** - * binary search tree - * @function - */ -const binarySearchTree = () => { - let rootNode = null; - let nodesCount = 0; - - /** - * @returns {object} node - */ - const root = () => rootNode; - - /** - * @returns {number} - */ - const count = () => nodesCount; - - /** - * gets max value node in the tree - * @returns {object} node - */ - const max = (startingNode) => { - let currentNode = startingNode || rootNode; - while (currentNode !== null && currentNode.getRight() !== null) { - currentNode = currentNode.getRight(); - } - return currentNode; - }; - - /** - * gets min value node in the tree - * @returns {object} node - */ - const min = (startingNode) => { - let currentNode = startingNode || rootNode; - while (currentNode !== null && currentNode.getLeft() !== null) { - currentNode = currentNode.getLeft(); - } - return currentNode; - }; - - /** - * finds a node in the tree by a given value - * @param {(string|number)} value - * @returns {object} node - */ - const search = (key) => { - let currentNode = rootNode; - while (currentNode !== null) { - if (key > currentNode.getKey()) { - currentNode = currentNode.getRight(); - } else if (key < currentNode.getKey()) { - currentNode = currentNode.getLeft(); - } else { - return currentNode; - } - } - return null; - }; - - /** - * inserts a node by a given (key, value) into the tree - * @param {(string|number)} key - * @param {object} value - */ - const insert = (key, value) => { - const insertFn = (currentNode) => { - if (currentNode === null) { - rootNode = node(key, value); - nodesCount += 1; - } else if (key < currentNode.getKey()) { - if (currentNode.getLeft() === null) { - currentNode.setLeft(node(key, value, currentNode)); - nodesCount += 1; - } else { - insertFn(currentNode.getLeft()); - } - } else if (key > currentNode.getKey()) { - if (currentNode.getRight() === null) { - currentNode.setRight(node(key, value, currentNode)); - nodesCount += 1; - } else { - insertFn(currentNode.getRight()); - } - } - }; - insertFn(rootNode); - }; - - /** - * removes a node by a given value from the tree - * @param {(string|number)} key - */ - const remove = (key) => { - const removeFn = (k, currentNode) => { - if (currentNode !== null) { - const left = currentNode.getLeft(); - const right = currentNode.getRight(); - 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.getKey() >= parent.getKey()) { - parent.setRight(null); - } else { - parent.setLeft(null); - } - nodesCount -= 1; - } else if (right === null) { - // remove a node with a left child - if (parent === null) { - rootNode = left; - } else if (currentNode.getKey() > parent.getKey()) { - parent.setRight(left); - } else { - parent.setLeft(left); - } - left.setParent(parent); - nodesCount -= 1; - } else if (left === null) { - // remove a node with a right child - if (parent === null) { - rootNode = right; - } else if (currentNode.getKey() > parent.getKey()) { - parent.setRight(right); - } else { - parent.setLeft(right); - } - right.setParent(parent); - nodesCount -= 1; - } else { - // remove a node with two children - const minRight = min(right); - currentNode.setKey(minRight.getKey()); - removeFn(minRight.getKey(), minRight); - } - } - } - }; - removeFn(key, rootNode); - }; - - /** - * traverse the binary tree in-order (left-parent-right) - * @param {function} cb - called with each node value - */ - const traverseInOrder = (cb) => { - const traverseInOrderFn = (currentNode) => { - if (currentNode !== null) { - traverseInOrderFn(currentNode.getLeft()); - cb(currentNode); - traverseInOrderFn(currentNode.getRight()); - } - }; - traverseInOrderFn(rootNode); - }; - - /** - * traverse the binary tree pre-order (parent-left-right) - * @param {function} cb - called with each node value - */ - const traversePreOrder = (cb) => { - const traversePreOrderFn = (currentNode) => { - if (currentNode !== null) { - cb(currentNode); - traversePreOrderFn(currentNode.getLeft()); - traversePreOrderFn(currentNode.getRight()); - } - }; - traversePreOrderFn(rootNode); - }; - - /** - * traverse the binary tree post-order (left-right-parent) - * @param {function} cb - called with each node value - */ - const traversePostOrder = (cb) => { - const traversePostOrderFn = (currentNode) => { - if (currentNode !== null) { - traversePostOrderFn(currentNode.getLeft()); - traversePostOrderFn(currentNode.getRight()); - cb(currentNode); - } - }; - traversePostOrderFn(rootNode); - }; - - /** - * traverse the binary tree - * @param {function} cb - called with each node value - * @param {string} type - 'inOrder' | 'preOrder' | 'postOrder' - */ - const traverse = (cb, type) => { - switch (type) { - case 'inOrder': - traverseInOrder(cb); - break; - case 'preOrder': - traversePreOrder(cb); - break; - case 'postOrder': - traversePostOrder(cb); - break; - default: - traverseInOrder(cb); - } - }; - - /** - * clears the tree - */ - const clear = () => { - rootNode = null; - nodesCount = 0; - }; - - // binary tree api - return { - node, - root, - count, - clear, - max, - min, - search, - insert, - remove, - traverseInOrder, - traversePreOrder, - traversePostOrder, - traverse - }; -}; - -module.exports = binarySearchTree; +module.exports = { BinarySearchTree }; diff --git a/index.test.js b/index.test.js deleted file mode 100644 index 5b6caa2..0000000 --- a/index.test.js +++ /dev/null @@ -1,133 +0,0 @@ -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); - }); -}); From dcbde7d745fadbf7281c3bf3fbcc29dd6a1635aa Mon Sep 17 00:00:00 2001 From: Eyas Ranjous Date: Fri, 27 Mar 2020 20:41:33 -0600 Subject: [PATCH 06/11] update --- README.md | 206 ++++++++++++++++++------------------------------------ 1 file changed, 68 insertions(+), 138 deletions(-) diff --git a/README.md b/README.md index c5a489a..340f266 100644 --- a/README.md +++ b/README.md @@ -4,52 +4,86 @@ [![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 **key** data type: **string**, **number**. -node's **value** data type: any. +javascript implementation of Binary Search Tree. Binary Search Tree -## Usage -``` +# Table of Contents +* [Install](#install) +* [API](#api) + * [require](#require) + * [import](#import) + * [Creating a Tree](#create-a-tree) + * [.insert(key, value)](#insertkey-value) + * [.has(key)](#haskey) + * [.find(key)](#findkey) + * [.min()](#min) + * [.max()](#max) + * [.root()](#root) + * [.count()](#count) + * [.traverseInOrder(cb)](#traverseinordercb) + * [.traversePreOrder(cb)](#traversepreordercb) + * [.traversePostOrder(cb)](#traversepostordercb) + * [.remove(key)](#removekey) + * [.clear()](#clear) + * [Build](#build) + * [License](#license) + + +## install +```sh npm install --save @datastructures-js/binary-search-tree ``` -then +## API +### require ```js -const binarySearchTree = require('@datastructures-js/binary-search-tree'); -const bst = binarySearchTree(); +const { BinarySearchTree } = require('@datastructures-js/binary-search-tree'); ``` -## API - -### .node(key, value, parent, left, right) -creates a bst node with the following api. +### import +```js +import { BinarySearchTree } from '@datastructures-js/binary-search-tree'; +``` -* .setKey(key) -* .getKey() -* .setValue(value) -* .getValue() -* .setParent(node) -* .getParent() -* .setLeft(node) -* .getLeft() -* .setRight(node) -* .getRight() +### Create a Tree ```js -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 +const bst = new BinarySearchTree(); ``` ### .insert(key, value) inserts a node with key/value into the tree. -```javascript + + + + + + + + + + + + +
runtimeparamsreturn
O(log(n)) + key: {number} or {string} +

+ value: {object} +
+ {BinarySearchTreeNode} the inserted node +

+ .getKey() {number|string} returns the node's key that is used to compare with other nodes. + .setValue(value) change the value that is associated with a node. + .getValue() {object} returns the value that is associated with a node. + .getLeft() {BinarySearchTreeNode} returns node's left child node. + .getRight() {BinarySearchTreeNode} returns node's right child node. + .getParent() {BinarySearchTreeNode} returns node's parent node. +
+ +```js bst.insert(50, 'v1'); bst.insert(80, 'v2'); bst.insert(30, 'v3'); @@ -59,133 +93,29 @@ bst.insert(40, 'v6'); bst.insert(20, 'v7'); ``` -### .root() +### .has(key) -gets the root node -```javascript -console.log(bst.root().getKey()); // 50 -``` +### .find(key) ### .min() -finds the min key node (most left). -```javascript -console.log(bst.min().getKey()); // 20 -``` - -### .max() +### .max() -finds the max key node (most right). -```javascript -console.log(bst.max().getKey()); // 90 -``` +### .root() ### .count() -gets nodes count. -```javascript -console.log(bst.count()); // 7 -``` - -### .search(key) - -finds a node by key or returns null if not found. -```javascript -const n = bst.search(30); -console.log(n.getKey()); // 30 -console.log(n.getRight().getKey()); // 40 -console.log(n.getLeft().getKey()); // 20 -``` - ### .traverseInOrder(cb) -```js -// in-order traverse (left-parent-right) -bst.traverseInOrder(node => console.log(node.getKey())); - -// 20 -// 30 -// 40 -// 50 -// 60 -// 80 -// 90 -``` ### .traversePreOrder(cb) -```js -// pre-order traverse (parent-left-right) -bst.traversePreOrder(node => console.log(node.getKey())); - -// 50 -// 30 -// 20 -// 40 -// 80 -// 60 -// 90 -``` - ### .traversePostOrder(cb) -```js -// post-order traverse (left-right-parent) -bst.traverse(node => console.log(node.getKey())); - -// 20 -// 40 -// 30 -// 60 -// 90 -// 80 -// 50 -``` - -### .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.getKey())); // in-order - -// 20 -// 30 -// 40 -// 50 -// 60 -// 80 -// 90 - -bst.traverse(node => console.log(node.getKey()), 'preOrder'); - -// 50 -// 30 -// 20 -// 40 -// 80 -// 60 -// 90 -``` - - ### .remove(key) -removes a node by its key (if exists) from the tree. -```javascript -console.log(bst.search(30).getKey()); // 30 -bst.remove(30); -console.log(bst.search(30)); // null -``` - -### .clear() +###.clear() -clears the tree. -```javascript -bst.clear(); -console.log(bst.count()); // 0 -``` +### .root() ## Build ``` From b1b5e14a0df85699478fa73c4b265aa602faaf1c Mon Sep 17 00:00:00 2001 From: Eyas Ranjous Date: Fri, 27 Mar 2020 22:53:56 -0600 Subject: [PATCH 07/11] update --- README.md | 240 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 230 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 340f266..5597430 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,6 @@ javascript implementation of Binary Search Tree. * [Build](#build) * [License](#license) - ## install ```sh npm install --save @datastructures-js/binary-search-tree @@ -66,18 +65,18 @@ inserts a node with key/value into the tree. O(log(n)) - key: {number} or {string} + key: {number} or {string}

- value: {object} + value: {object} {BinarySearchTreeNode} the inserted node

- .getKey() {number|string} returns the node's key that is used to compare with other nodes. - .setValue(value) change the value that is associated with a node. - .getValue() {object} returns the value that is associated with a node. - .getLeft() {BinarySearchTreeNode} returns node's left child node. - .getRight() {BinarySearchTreeNode} returns node's right child node. + .getKey() {number|string} returns the node's key that is used to compare with other nodes.
+ .setValue(value) change the value that is associated with a node.
+ .getValue() {object} returns the value that is associated with a node.
+ .getLeft() {BinarySearchTreeNode} returns node's left child node.
+ .getRight() {BinarySearchTreeNode} returns node's right child node.
.getParent() {BinarySearchTreeNode} returns node's parent node. @@ -94,28 +93,249 @@ bst.insert(20, 'v7'); ``` ### .has(key) +checks if a value exists by its key. + + + + + + + + + + + + +
runtimeparamsreturn
O(log(n)) + key: {number} or {string} + + {boolean} +
+ +```js +bst.has(50); // true +bst.has(100); // false +``` ### .find(key) +finds a node in the tree by its key. + + + + + + + + + + + + +
runtimeparamsreturn
O(log(n)) + key: {number} or {string} + + {BinarySearchTreeNode} +
+ +```js +const n50 = bst.find(60); +console.log(n50.getValue()); // v7 +``` ### .min() +finds the node with min key in the tree. + + + + + + + + + + +
runtimereturn
O(log(n)) + {BinarySearchTreeNode} +
+ +```js +const min = bst.min(); +console.log(min.getValue()); // v5 +``` ### .max() +finds the node with max key in the tree. + + + + + + + + + + +
runtimereturn
O(log(n)) + {BinarySearchTreeNode} +
+```js +const max = bst.max(); +console.log(max.getValue()); // v4 +``` ### .root() +returns the root node of the tree. + + + + + + + + + + +
runtimereturn
O(1) + {BinarySearchTreeNode} +
+ +```js +const root = bst.root(); +console.log(root.getValue()); // v1 +``` ### .count() +returns the count of nodes in the tree. + + + + + + + + + + +
runtimereturn
O(1) + {number} +
+ +```js +console.log(bst.count()); // 7 +``` ### .traverseInOrder(cb) +traverses the tree in order (left-node-right). + + + + + + + + + + +
runtimeparam
O(n) + cb: {function} +
+ +```js +bst.traverseInOrder((node) => console.log(node.getKey())); + +/* + +*/ +``` ### .traversePreOrder(cb) +traverses the tree pre order (node-left-right). + + + + + + + + + + +
runtimeparam
O(n) + cb: {function} +
+ +```js +bst.traversePreOrder((node) => console.log(node.getKey())); + +/* + +*/ +``` ### .traversePostOrder(cb) +traverses the tree post order (left-right-node). + + + + + + + + + + +
runtimeparam
O(n) + cb: {function} +
+ +```js +bst.traversePostOrder((node) => console.log(node.getKey())); + +/* + +*/ +``` ### .remove(key) +removes a node from the tree by its key. + + + + + + + + + + + + +
runtimeparamsreturn
O(log(n)) + key: {number} or {string} + + {boolean} +
-###.clear() +```js +bst.remove(20); // true +bst.remove(100); // false +``` -### .root() +### .clear() +clears the tree. + + + + + + + + +
runtime
O(1)
+ +```js +bst.clear(); +``` ## Build ``` From d8f52d19b6a4cb43a77e525b82a5c4a4c7ad4dc3 Mon Sep 17 00:00:00 2001 From: Eyas Ranjous Date: Fri, 27 Mar 2020 23:07:38 -0600 Subject: [PATCH 08/11] update --- README.md | 45 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 5597430..c848413 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ const bst = new BinarySearchTree(); ### .insert(key, value) -inserts a node with key/value into the tree. +inserts a node with key/value into the tree. Inserting an node with existing key, would update the existing node's value with the new inserted one. @@ -93,7 +93,7 @@ bst.insert(20, 'v7'); ``` ### .has(key) -checks if a value exists by its key. +checks if a node exists by its key.
@@ -138,8 +138,11 @@ finds a node in the tree by its key.
```js -const n50 = bst.find(60); -console.log(n50.getValue()); // v7 +const n60 = bst.find(60); +console.log(n60.getKey()); // 60 +console.log(n60.getValue()); // v5 + +console.log(bst.find(100)); // null ``` ### .min() @@ -160,7 +163,8 @@ finds the node with min key in the tree. ```js const min = bst.min(); -console.log(min.getValue()); // v5 +console.log(min.getKey()); // 20 +console.log(min.getValue()); // v7 ``` ### .max() @@ -181,6 +185,7 @@ finds the node with max key in the tree. ```js const max = bst.max(); +console.log(max.getKey()); // 90 console.log(max.getValue()); // v4 ``` ### .root() @@ -201,6 +206,7 @@ returns the root node of the tree. ```js const root = bst.root(); +console.log(root.getKey()); // 50 console.log(root.getValue()); // v1 ``` @@ -244,7 +250,13 @@ traverses the tree in order (left-node-right). bst.traverseInOrder((node) => console.log(node.getKey())); /* - +20 +30 +40 +50 +60 +80 +90 */ ``` @@ -268,7 +280,13 @@ traverses the tree pre order (node-left-right). bst.traversePreOrder((node) => console.log(node.getKey())); /* - +50 +30 +20 +40 +80 +60 +90 */ ``` @@ -292,7 +310,13 @@ traverses the tree post order (left-right-node). bst.traversePostOrder((node) => console.log(node.getKey())); /* - +20 +40 +30 +60 +90 +80 +50 */ ``` @@ -318,7 +342,8 @@ removes a node from the tree by its key. ```js bst.remove(20); // true -bst.remove(100); // false +bst.remove(100); // false +console.log(bst.count()); // 6 ``` ### .clear() @@ -335,6 +360,8 @@ clears the tree. ```js bst.clear(); +console.log(bst.count()); // 0 +console.log(bst.root()); // null ``` ## Build From 17b05f58b3134b9bc3618026e34c0f48ccc8abc9 Mon Sep 17 00:00:00 2001 From: Eyas Ranjous Date: Fri, 27 Mar 2020 23:50:42 -0600 Subject: [PATCH 09/11] v3.0.0 --- CHANGELOG.md | 11 +++++++++++ package.json | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ba0e126 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [3.0.0] - 2020-03-22 +### Changed +- New release diff --git a/package.json b/package.json index 4536914..5ab9938 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@datastructures-js/binary-search-tree", - "version": "2.0.1", + "version": "3.0.0", "description": "binary search tree implementation in javascript", "main": "index.js", "scripts": { From 3dffb04508d8986871631baddb504d400a63c142 Mon Sep 17 00:00:00 2001 From: Eyas Ranjous Date: Fri, 27 Mar 2020 23:52:49 -0600 Subject: [PATCH 10/11] jsdoc --- src/binarySearchTree.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/binarySearchTree.js b/src/binarySearchTree.js index 5efa23a..1a817ec 100644 --- a/src/binarySearchTree.js +++ b/src/binarySearchTree.js @@ -92,7 +92,6 @@ class BinarySearchTree { /** * @public * finds the node with max key (most right) in the tree - * @param {number|string} key * @return {BinarySearchTreeNode} */ max(node = this.rootNode) { @@ -106,7 +105,6 @@ class BinarySearchTree { /** * @public * finds the node with min key (most left) in the tree - * @param {number|string} key * @return {BinarySearchTreeNode} */ min(node = this.rootNode) { From a425124ef867e2585dda0f3252b501e415d4bc39 Mon Sep 17 00:00:00 2001 From: Eyas Ranjous Date: Fri, 27 Mar 2020 23:53:54 -0600 Subject: [PATCH 11/11] jsdoc --- src/binarySearchTree.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/binarySearchTree.js b/src/binarySearchTree.js index 1a817ec..045d3b5 100644 --- a/src/binarySearchTree.js +++ b/src/binarySearchTree.js @@ -20,7 +20,7 @@ class BinarySearchTree { * inserts a node with a key/value into the tree * @param {number|string} key * @param {object} vaue - * @return {BinartSearchTreeNode} + * @return {BinarySearchTreeNode} */ insert(key, value, node = this.rootNode) { const newNode = new BinarySearchTreeNode(key, value);