├── dist ├── Persist.js ├── BSTNode.js ├── CLList.js ├── LList.js ├── Heap.js └── BSTree.js ├── Gruntfile.js ├── .gitignore ├── src ├── binary_trees │ ├── BSTNode.es6 │ └── BSTree.es6 ├── lists │ ├── CLList.es6 │ └── LList.es6 └── heaps │ └── Heap.es6 ├── CONTRIBUTING.md ├── package.json ├── __tests__ └── integration │ ├── lists │ ├── CLList.es6 │ └── LList.es6 │ ├── heaps │ └── Heap.es6 │ └── binary_trees │ └── BSTree.es6 ├── README.md ├── LICENSE └── STYLE_GUIDE.md /dist/Persist.js: -------------------------------------------------------------------------------- 1 | var Persist = { 2 | BSTNode: require('./BSTNode'), 3 | BSTree: require('./BSTree'), 4 | LinkedList: require('./LList'), 5 | CircularLinkedList: require('./CLList'), 6 | Heap: require('./Heap') 7 | }; 8 | 9 | module.exports = Persist; 10 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | grunt.initConfig({ 4 | babel: { 5 | dist: { 6 | files: { 7 | 'dist/BSTNode.js': 'src/binary_trees/BSTNode.es6', 8 | 'dist/BSTree.js': 'src/binary_trees/BSTree.es6', 9 | 'dist/LList.js': 'src/lists/LList.es6', 10 | 'dist/CLList.js': 'src/lists/CLList.es6', 11 | 'dist/Heap.js': 'src/heaps/Heap.es6' 12 | } 13 | } 14 | } 15 | }); 16 | 17 | require('load-grunt-tasks')(grunt); 18 | 19 | grunt.registerTask('default', ['babel']); 20 | }; 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | 29 | # Floobits pairing 30 | .floo* -------------------------------------------------------------------------------- /src/binary_trees/BSTNode.es6: -------------------------------------------------------------------------------- 1 | import 'core-js/shim'; 2 | const IM = require('immutable'); 3 | 4 | export default class BSTNode { 5 | constructor(key, value, left, right, id) { 6 | if (IM.Map.isMap(key)) { 7 | this._store = key; 8 | } else { 9 | this._store = IM.Map({ 10 | '_key': key, 11 | '_value': value, 12 | '_left': left, 13 | '_right': right, 14 | '_id': id }); 15 | } 16 | Object.freeze(this); 17 | } 18 | 19 | get store() { 20 | return this._store; 21 | } 22 | 23 | get key() { 24 | return this.store.get('_key'); 25 | } 26 | 27 | get value() { 28 | return this.store.get('_value'); 29 | } 30 | 31 | get left() { 32 | return this.store.get('_left', null); 33 | } 34 | 35 | get right() { 36 | return this.store.get('_right', null); 37 | } 38 | 39 | get id() { 40 | return this.store.get('_id'); 41 | } 42 | 43 | //Returns an array with the node's children 44 | get children() { 45 | let children = []; 46 | if (this.left) children.push(['_left', this.left]); 47 | if (this.right) children.push(['_right', this.right]); 48 | return children; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to ih 2 | 3 | ## General Workflow 4 | 5 | 1. Fork the repo 6 | 1. Cut namespaced feature branch with initials from master 7 | - FL_bug/... 8 | - FL_feat/... 9 | - FL_test/... 10 | - FL_doc/... 11 | - FL_refactor/... 12 | 1. Make commits to your feature branch (only make changes that are relevant to this branch) 13 | - commit messages should start with a capital letter 14 | - commit messages should be in the present tense 15 | - commit messages should not end with a '.' 16 | 1. When you've finished with your fix or feature: 17 | - `git fetch upstream master` 18 | - `git rebase upstream/master` 19 | - submit a pull request directly to master. Include a description of your changes. 20 | 1. Your pull request will be reviewed by another maintainer. The point of code reviews is to help keep the codebase clean and of high quality. 21 | 1. Fix any issues raised by your code reviewer, and push your fixes as a single new commit. 22 | 1. Once the pull request has been reviewed, it will be merged by another member of the team. Do not merge your own pull requests. 23 | 24 | ### Guidelines 25 | 26 | 1. Uphold the current code standard: 27 | - Keep your code DRY. 28 | - Follow [STYLE_GUIDE.md](STYLE_GUIDE.md) 29 | 1. Run the tests before submitting a pull request. 30 | 1. Submit tests if your pull request contains new, testable behavior. 31 | 32 | 33 | **Thanks for contributing!** -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "persistence-js", 3 | "version": "0.0.1", 4 | "description": "specialized persistent collections in javascript -- immutable-js addons", 5 | "homepage": "https://github.com/persistence-js/persist", 6 | "author": { 7 | "name": "PersistenceJS" 8 | }, 9 | "contributors": [ 10 | { 11 | "name": "Clark Feusier", 12 | "email": "cfeusier@gmail.com" 13 | }, 14 | { 15 | "name": "Daniel Tsui", 16 | "email": "danielt213@gmail.com" 17 | } 18 | ], 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/persistence-js/persist.git" 22 | }, 23 | "bugs": { 24 | "url": "https://github.com/persistence-js/persist/issues" 25 | }, 26 | "main": "./dist/Persist.js", 27 | "scripts": { 28 | "test": "jest", 29 | "test-debug": "node-debug --nodejs --harmony ./node_modules/jest-cli/bin/jest.js --runInBand" 30 | }, 31 | "dependencies": { 32 | "core-js": "^0.6.0", 33 | "immutable": "3.7.2" 34 | }, 35 | "devDependencies": { 36 | "babel": "^4.6.0", 37 | "babel-jest": "*", 38 | "grunt": "^0.4.5", 39 | "grunt-babel": "^5.0.1", 40 | "grunt-cli": "^0.1.13", 41 | "jest-cli": "^0.4.0", 42 | "load-grunt-tasks": "^3.2.0" 43 | }, 44 | "engines": { 45 | "node": ">=0.8.0" 46 | }, 47 | "keywords": [ 48 | "immutable", 49 | "persistent", 50 | "data", 51 | "datastructure", 52 | "functional", 53 | "collection", 54 | "sequence", 55 | "iteration", 56 | "heap", 57 | "bst", 58 | "binary search tree", 59 | "linked list", 60 | "circular linked list" 61 | ], 62 | "license": "Apache 2.0", 63 | "jest": { 64 | "scriptPreprocessor": "node_modules/babel-jest", 65 | "testFileExtensions": [ 66 | "es6", 67 | "js" 68 | ], 69 | "moduleFileExtensions": [ 70 | "js", 71 | "json", 72 | "es6" 73 | ] 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/lists/CLList.es6: -------------------------------------------------------------------------------- 1 | import 'core-js/shim'; 2 | const IM = require('immutable'); 3 | const LList = require('./LList'); 4 | 5 | export default class CLList extends LList { 6 | constructor(itemOrList = [], options = {}) { 7 | options.circular = true; 8 | super(itemOrList, options) 9 | } 10 | 11 | //Returns a new list, removing one node after the specified node. 12 | removeAfter(nodeToRemove) { 13 | return this.remove(nodeToRemove.next); 14 | } 15 | 16 | //Returns a new list, removing one node before the specified node. 17 | removeBefore(nodeToRemoveBefore) { 18 | let isTarget = (nodeToCheck) => { 19 | return nodeToCheck.next === nodeToRemoveBefore; 20 | } 21 | return this.remove(this.filter(isTarget)[0]); 22 | } 23 | 24 | //Returns a new list, adding one node after the specified node. 25 | addAfter(atNode, addition) { 26 | return this.addBefore(atNode, addition, false); 27 | } 28 | 29 | //Returns a new list, adding one node before the specified node. 30 | addBefore(atNode, addition, before = true) { 31 | let additionList = new LList(addition); 32 | let insert = () => { 33 | let newList = []; 34 | this.forEach((node) => { 35 | if(node === atNode && !!before){ 36 | newList = newList.concat(additionList.map(LList.getData)); 37 | } 38 | newList.push(node.data); 39 | if(node === atNode && !before){ 40 | newList = newList.concat(additionList.map(LList.getData)); 41 | } 42 | }); 43 | 44 | return new CLList(newList); 45 | }.bind(this); 46 | 47 | return (LList.isNode(atNode)) ? insert() : 48 | new Error("Error, inputs must be LList Nodes."); 49 | } 50 | 51 | //Helper functions 52 | remove(nodeToRemove){ 53 | let notNode = (nodeToCheck) => { 54 | return nodeToCheck !== nodeToRemove; 55 | } 56 | return new CLList(this.filter(notNode).map(LList.getData)); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /dist/BSTNode.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | 7 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 8 | 9 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 10 | 11 | require('core-js/shim'); 12 | 13 | var IM = require('immutable'); 14 | 15 | var BSTNode = (function () { 16 | function BSTNode(key, value, left, right, id) { 17 | _classCallCheck(this, BSTNode); 18 | 19 | if (IM.Map.isMap(key)) { 20 | this._store = key; 21 | } else { 22 | this._store = IM.Map({ 23 | '_key': key, 24 | '_value': value, 25 | '_left': left, 26 | '_right': right, 27 | '_id': id }); 28 | } 29 | Object.freeze(this); 30 | } 31 | 32 | _createClass(BSTNode, [{ 33 | key: 'store', 34 | get: function () { 35 | return this._store; 36 | } 37 | }, { 38 | key: 'key', 39 | get: function () { 40 | return this.store.get('_key'); 41 | } 42 | }, { 43 | key: 'value', 44 | get: function () { 45 | return this.store.get('_value'); 46 | } 47 | }, { 48 | key: 'left', 49 | get: function () { 50 | return this.store.get('_left', null); 51 | } 52 | }, { 53 | key: 'right', 54 | get: function () { 55 | return this.store.get('_right', null); 56 | } 57 | }, { 58 | key: 'id', 59 | get: function () { 60 | return this.store.get('_id'); 61 | } 62 | }, { 63 | key: 'children', 64 | 65 | //Returns an array with the node's children 66 | get: function () { 67 | var children = []; 68 | if (this.left) children.push(['_left', this.left]); 69 | if (this.right) children.push(['_right', this.right]); 70 | return children; 71 | } 72 | }]); 73 | 74 | return BSTNode; 75 | })(); 76 | 77 | exports['default'] = BSTNode; 78 | module.exports = exports['default']; 79 | -------------------------------------------------------------------------------- /__tests__/integration/lists/CLList.es6: -------------------------------------------------------------------------------- 1 | jest.autoMockOff(); 2 | var IM = require('immutable'), 3 | CLList = require('../../../src/lists/CLList'); 4 | 5 | describe('Circular Linked Lists', function() { 6 | let oneToFive = [1,2,3,4,5]; 7 | let cLL = new CLList(oneToFive); 8 | let midNode = cLL.head.next.next; 9 | 10 | describe('CLL-Specific Basic Instance Methods & Properties', function() { 11 | 12 | it('is circular', function() { 13 | expect(cLL.tail.next).toBe(cLL.head); 14 | }); 15 | 16 | it('has correct values midpoint, head, tail, and size values', function() { 17 | expect(midNode.data).toBe(3); 18 | expect(cLL.head.data).toBe(1); 19 | expect(cLL.tail.data).toBe(5); 20 | expect(cLL.size).toBe(5); 21 | }); 22 | 23 | it('appends, not modifying the original list', function() { 24 | expect(cLL.append('NEW').tail.data).toBe('NEW'); 25 | expect(cLL.head.data).toNotBe('NEW'); 26 | }); 27 | 28 | it('prepends, not modifying the original list', function() { 29 | expect(cLL.prepend('NEW').head.data).toBe('NEW'); 30 | expect(cLL.head.data).toNotBe('NEW'); 31 | }); 32 | 33 | }); 34 | 35 | describe('Add before', function() { 36 | it('when called with head, behaves exactly as prepend should, ', function() { 37 | expect(cLL.addBefore(cLL.head, 0).head.data).toBe(cLL.prepend(0).head.data); 38 | }); 39 | 40 | it('returns a new list, with the correct inserted value', function() { 41 | let nList = cLL.addBefore(midNode, 0); 42 | expect(cLL.size).toNotBe(nList.size); 43 | expect(nList.head.next.next.data).toBe(0); 44 | }); 45 | 46 | }); 47 | 48 | describe('Remove before', function() { 49 | let removeBeforeResult = cLL.removeBefore(cLL.head); 50 | let removeTailResult = cLL.removeTail(); 51 | 52 | it('removes tail, when called with head', function() { 53 | expect(removeBeforeResult.tail.data).toBe(removeTailResult.tail.data); 54 | expect(removeBeforeResult.head.data).toBe(removeTailResult.head.data); 55 | }); 56 | 57 | it('returns a new list, with the correct length', function() { 58 | expect(removeBeforeResult.size).toBe(cLL.size - 1); 59 | }); 60 | 61 | it('does not contain the removed node', function() { 62 | let tailValue = cLL.tail; 63 | let isTail = (node) => { 64 | return node === tailValue; 65 | } 66 | expect(removeBeforeResult.filter(isTail).length).toBeFalsy(); 67 | }); 68 | 69 | }); 70 | 71 | describe('Add after', function() { 72 | it('when called with tail, behaves exactly as append should', function() { 73 | expect(cLL.addAfter(cLL.tail, 0).head.data).toBe(cLL.append(0).head.data); 74 | 75 | }); 76 | 77 | it('returns a new list, with the correct inserted value', function() { 78 | let nList = cLL.addAfter(midNode, 0); 79 | expect(cLL.size).toNotBe(nList.size); 80 | expect(nList.head.next.next.next.data).toBe(0); 81 | 82 | }); 83 | 84 | }); 85 | 86 | describe('Remove after', function() { 87 | let removeAfterResult = cLL.removeAfter(cLL.tail); 88 | let removeHeadResult = cLL.removeHead(); 89 | 90 | it('removes head, when called with tail', function() { 91 | expect(removeAfterResult.tail.data).toBe(removeHeadResult.tail.data); 92 | expect(removeAfterResult.head.data).toBe(removeHeadResult.head.data); 93 | 94 | }); 95 | 96 | it('returns a new list, with the correct length', function() { 97 | expect(removeAfterResult.size).toBe(cLL.size-1); 98 | }); 99 | 100 | it('does not contain the removed node', function() { 101 | let headValue = cLL.head; 102 | let isHead = (node) => { 103 | return node === headValue; 104 | } 105 | expect(removeAfterResult.filter(isHead).length).toBeFalsy(); 106 | }); 107 | }); 108 | }); 109 | -------------------------------------------------------------------------------- /__tests__/integration/heaps/Heap.es6: -------------------------------------------------------------------------------- 1 | jest.autoMockOff(); 2 | const IM = require('immutable'); 3 | const Heap = require('../../../src/heaps/Heap'); 4 | 5 | describe("Heap Operations", () => { 6 | describe("Instantiation", () => { 7 | let newHeap = new Heap(); 8 | 9 | it("instantiates a min-heap", () => { 10 | expect(newHeap.isMaxHeap).toBe(false); 11 | }); 12 | 13 | it("can create a max-heap", () => { 14 | let maxHeap = new Heap(undefined,true); 15 | expect(maxHeap.isMaxHeap).toBe(true); 16 | }); 17 | 18 | it("instantiates empty, checks size", () => { 19 | expect(newHeap.size).toBe(0); 20 | }); 21 | 22 | it("instantiates with a comparator", () => { 23 | let compare = () => 0; 24 | let compareHeap = new Heap(undefined, undefined, compare); 25 | expect(compareHeap.comparator).toEqual(compare); 26 | }); 27 | 28 | }); 29 | 30 | describe("Internal Methods", () => { 31 | describe("Directional Sifting", () => { 32 | let newMinHeap = new Heap(); 33 | newMinHeap = newMinHeap.push(99).push(8).push(9).push(4).push(5); 34 | let newMaxHeap = new Heap(undefined,true); 35 | newMaxHeap = newMaxHeap.push(99).push(8).push(9).push(4).push(5); 36 | 37 | it("sifts down I", () => { 38 | let siftedList = newMaxHeap.siftDown(newMaxHeap._heapStorage,0); 39 | expect(siftedList.first()).toBe(99); 40 | expect(siftedList.last()).toBe(5); 41 | expect(siftedList.get(2)).toBe(9); 42 | }); 43 | 44 | it("sifts down II", () => { 45 | let siftedList = newMinHeap.siftDown(newMinHeap._heapStorage,0); 46 | expect(siftedList.first()).toBe(4); 47 | expect(siftedList.last()).toBe(8); 48 | expect(siftedList.get(2)).toBe(9); 49 | }); 50 | 51 | }); 52 | 53 | }); 54 | 55 | describe("Basic Instance Methods", () => { 56 | let newHeap = new Heap(); 57 | newHeap = newHeap.push(1).push(0).push(3); 58 | let newHeap2 = new Heap(); 59 | newHeap2 = newHeap2.push(1).push(2).push(3).push(4).push(5).push(0); 60 | 61 | it("Pushes/Inserts", () => { 62 | expect(newHeap.size).toBe(3); 63 | }); 64 | 65 | it("Peeks", () => { 66 | expect(newHeap.peek()).toBe(0); 67 | expect(newHeap2.peek()).toBe(0); 68 | }); 69 | 70 | it("Pops/Extracts", () => { 71 | expect(newHeap.pop().size).toBe(2); 72 | expect(newHeap.pop().peek()).toBe(1); 73 | expect(newHeap.size).toBe(3); 74 | }); 75 | 76 | it("Replaces", () => { 77 | expect(newHeap.replace(0).size).toBe(3); 78 | expect(newHeap.replace(0).peek()).toBe(0); 79 | }); 80 | 81 | }); 82 | 83 | describe("Creation & Operations", () => { 84 | let numbers25 = []; 85 | let numbers50 = []; 86 | for (let i = 25; i >0; i--){ 87 | numbers25.push(i); 88 | } 89 | for (let i = 50; i >25; i--){ 90 | numbers50.push(i); 91 | } 92 | 93 | describe("BuildHeap", () => { 94 | it("builds max heaps from an array", () => { 95 | let heap = new Heap(numbers25, true); 96 | expect(heap.size).toBe(25); 97 | expect(heap.peek()).toBe(25); 98 | expect(heap.pop().pop().peek()).toBe(23); 99 | }); 100 | 101 | it("builds min heaps from an array", () => { 102 | let heap = new Heap(numbers25); 103 | expect(heap.size).toBe(25); 104 | expect(heap.peek()).toBe(1); 105 | expect(heap.pop().pop().peek()).toBe(3); 106 | }); 107 | 108 | it("accepts heaps", () => { 109 | let heap1 = new Heap(numbers25); 110 | let heap2 = new Heap(heap1); 111 | expect(heap2.size).toBe(25); 112 | expect(heap2.peek()).toBe(1); 113 | expect(heap2.pop().pop().peek()).toBe(3); 114 | }); 115 | 116 | }); 117 | 118 | describe("Merge", () => { 119 | it("merges with other heaps", () => { 120 | let heap1 = new Heap(numbers25); 121 | let heap2 = new Heap(numbers50); 122 | let heap3 = heap2.merge(heap1); 123 | expect(heap3.peek()).toBe(1); 124 | expect(heap3.size).toBe(50); 125 | }); 126 | 127 | }); 128 | 129 | describe("HeapSort", () => { 130 | it('sorts an array of numbers', () => { 131 | let tenNumbers = [1,2,3,4,5,6,7,8,9,10].sort(() => { 132 | return .5 - Math.random(); 133 | }); 134 | let newHeap = new Heap(tenNumbers); 135 | let sortedArray = newHeap.heapSort(); 136 | for (let i = 0; i < sortedArray.size; i++){ 137 | expect(sortedArray.get(i)).toEqual(i+1); 138 | } 139 | }); 140 | }); 141 | }); 142 | }); 143 | -------------------------------------------------------------------------------- /dist/CLList.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | 7 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 8 | 9 | var _get = function get(_x4, _x5, _x6) { var _again = true; _function: while (_again) { var object = _x4, property = _x5, receiver = _x6; desc = parent = getter = undefined; _again = false; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x4 = parent; _x5 = property; _x6 = receiver; _again = true; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } }; 10 | 11 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 12 | 13 | function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; } 14 | 15 | require('core-js/shim'); 16 | 17 | var IM = require('immutable'); 18 | var LList = require('./LList'); 19 | 20 | var CLList = (function (_LList) { 21 | function CLList() { 22 | var itemOrList = arguments[0] === undefined ? [] : arguments[0]; 23 | var options = arguments[1] === undefined ? {} : arguments[1]; 24 | 25 | _classCallCheck(this, CLList); 26 | 27 | options.circular = true; 28 | _get(Object.getPrototypeOf(CLList.prototype), 'constructor', this).call(this, itemOrList, options); 29 | } 30 | 31 | _inherits(CLList, _LList); 32 | 33 | _createClass(CLList, [{ 34 | key: 'removeAfter', 35 | 36 | //Returns a new list, removing one node after the specified node. 37 | value: function removeAfter(nodeToRemove) { 38 | return this.remove(nodeToRemove.next); 39 | } 40 | }, { 41 | key: 'removeBefore', 42 | 43 | //Returns a new list, removing one node before the specified node. 44 | value: function removeBefore(nodeToRemoveBefore) { 45 | var isTarget = function isTarget(nodeToCheck) { 46 | return nodeToCheck.next === nodeToRemoveBefore; 47 | }; 48 | return this.remove(this.filter(isTarget)[0]); 49 | } 50 | }, { 51 | key: 'addAfter', 52 | 53 | //Returns a new list, adding one node after the specified node. 54 | value: function addAfter(atNode, addition) { 55 | return this.addBefore(atNode, addition, false); 56 | } 57 | }, { 58 | key: 'addBefore', 59 | 60 | //Returns a new list, adding one node before the specified node. 61 | value: function addBefore(atNode, addition) { 62 | var _this = this; 63 | 64 | var before = arguments[2] === undefined ? true : arguments[2]; 65 | 66 | var additionList = new LList(addition); 67 | var insert = (function () { 68 | var newList = []; 69 | _this.forEach(function (node) { 70 | if (node === atNode && !!before) { 71 | newList = newList.concat(additionList.map(LList.getData)); 72 | } 73 | newList.push(node.data); 74 | if (node === atNode && !before) { 75 | newList = newList.concat(additionList.map(LList.getData)); 76 | } 77 | }); 78 | 79 | return new CLList(newList); 80 | }).bind(this); 81 | 82 | return LList.isNode(atNode) ? insert() : new Error('Error, inputs must be LList Nodes.'); 83 | } 84 | }, { 85 | key: 'remove', 86 | 87 | //Helper functions 88 | value: function remove(nodeToRemove) { 89 | var notNode = function notNode(nodeToCheck) { 90 | return nodeToCheck !== nodeToRemove; 91 | }; 92 | return new CLList(this.filter(notNode).map(LList.getData)); 93 | } 94 | }]); 95 | 96 | return CLList; 97 | })(LList); 98 | 99 | exports['default'] = CLList; 100 | module.exports = exports['default']; 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PersistenceJS 2 | 3 | **PersistenceJS** provides specialized, immutable, persistent data structures built on-top of ImmutableJS. PersistenceJS offers highly efficient immutable _linked-lists_, _heaps_, and _search trees_, with more data structures coming soon. 4 | 5 | [ ](https://github.com/persistence-js/persist/releases) 6 | [ ](https://codeship.com/projects/86120/) 7 | [ ](https://www.npmjs.com/package/persistence-js) 8 | 9 | > Immutable data cannot be changed once created... Persistent data presents a mutative API which does not update the data in-place, but instead always yields new updated data ([source](https://facebook.github.io/immutable-js/)). 10 | 11 | --- 12 | 13 | To learn more about immutable data, persistent data structures, or any of the individual data structures implemented by PersistenceJS, please explore the [appendix](#appendix). 14 | 15 | --- 16 | 17 | > **Created by [Clark Feusier](http://clarkfeusier.com/pages/about) and [Daniel Tsui](http://sdtsui.com)** 18 | 19 | 20 | --- 21 | 22 | 1. [Dependencies](#dependencies) 23 | 1. [Installation](#installation) 24 | 1. [Documentation](#documentation) 25 | 1. [Roadmap](#roadmap) 26 | 1. [Contributing](#contributing-to-jkif-parser) 27 | 1. [Development Requirements](#development-requirements) 28 | 1. [Installing Dependencies](#installing-dependencies) 29 | 1. [Running Tests](#running-tests) 30 | 1. [License](#license) 31 | 1. [Appendix](#appendix) 32 | 33 | --- 34 | 35 | #### Dependencies 36 | 37 | - [immutable](https://facebook.github.io/immutable-js/) — basic immutable collections on which PersistenceJS is constructed 38 | - [core-js](https://www.npmjs.com/package/core-js/) — ES5/6/7 polyfills, shims, and other goodies 39 | 40 | --- 41 | 42 | ## Installation 43 | 44 | **PersistenceJS** is available as an npm package. 45 | 46 | ***Install module from command-line*** 47 | 48 | ```sh 49 | npm install persistence-js 50 | ``` 51 | 52 | ***Require module for use in desired file*** 53 | 54 | ```js 55 | var Persist = require('persistence-js'); 56 | ``` 57 | 58 | --- 59 | 60 | ## Documentation 61 | 62 | ### *PersistenceJS* 63 | 64 | This object provides all of the data structures offered by PersistenceJS. 65 | 66 | ```js 67 | var Persist = require('persistence-js'); 68 | ``` 69 | 70 | ## Data Structures 71 | 72 | - [Linked List](src/lists/LList.es6) 73 | 74 | ```js 75 | var LList = Persist.LinkedList; 76 | var exampleLList = new LList(); 77 | ``` 78 | 79 | - [Circular Linked List](src/lists/CLList.es6) 80 | 81 | ```js 82 | var CLList = Persist.CircularLinkedList; 83 | var exampleCLList = new CLList(); 84 | ``` 85 | 86 | - [Heap](src/heaps/Heap.es6) 87 | 88 | ```js 89 | var Heap = Persist.Heap; 90 | var exampleHeap = new Heap(); 91 | ``` 92 | 93 | - [Binary Search Tree](src/binary_trees/BSTree.es6) 94 | 95 | ```js 96 | var BSTree = Persist.BSTree; 97 | var exampleBST = new BSTree(); 98 | ``` 99 | 100 | --- 101 | 102 | ## Roadmap 103 | 104 | The future of PersistenceJS is managed through this repository's **issues** — [view the roadmap here](https://github.com/persistence-js/persist/issues). 105 | 106 | ## Contributing to PersistenceJS 107 | 108 | We welcome contributions, but please read our [contribution guidelines](CONTRIBUTING.md) before submitting your work. The development requirements and instructions are below. 109 | 110 | ## Development Requirements 111 | 112 | - Node 0.10.x 113 | - npm 2.x.x 114 | - core-js 115 | - immutable 116 | - babel (global install) 117 | - babel-jest 118 | - jest-cli (global install) 119 | - grunt (global install) 120 | - grunt-cli (global install) 121 | - load-grunt-tasks 122 | - grunt-babel 123 | 124 | ### Installing Dependencies 125 | 126 | Install Node (bundled with npm) using [Homebrew](http://brew.sh/): 127 | 128 | ```sh 129 | brew install node 130 | ``` 131 | 132 | Install project and development dependencies using npm: 133 | 134 | ```sh 135 | npm install 136 | ``` 137 | 138 | ### Running Tests 139 | 140 | After installing the above dependencies, tests can be run using the following command: 141 | 142 | ```sh 143 | npm test 144 | ``` 145 | 146 | ## [License](LICENSE.md) 147 | 148 | **PersistenceJS** - specialized persistent collections in javascript 149 | 150 | _Copyright 2015 Clark Feusier & Sze-Hung Daniel Tsui_ 151 | 152 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 153 | 154 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 155 | 156 | [**COMPLETE LICENSE**](LICENSE) 157 | 158 | --- 159 | 160 | ## Appendix 161 | 162 | [Persistence](https://en.wikipedia.org/wiki/Persistent_data_structure) 163 | 164 | > A persistent data structure ... always preserves the previous version of itself when modified. 165 | 166 | [Immutability](https://en.wikipedia.org/wiki/Immutable_object) 167 | 168 | > An immutable object is an object whose state cannot be modified after it is created. 169 | 170 | Note, persistent data structures are generally immutable, since the API returns a new structure, despite appearing mutable. 171 | 172 | #### [Back to Top](#) 173 | 174 | -------------------------------------------------------------------------------- /src/lists/LList.es6: -------------------------------------------------------------------------------- 1 | import 'core-js/shim'; 2 | const IM = require('immutable'); 3 | 4 | export default class LList { 5 | /** 6 | * Accepts items and list-like objects. 7 | * Converts them into Immutable Seq's of length >1 before 8 | * creating nodes. 9 | * @param {Array} itemOrList [description] 10 | * @param {Object} options [circular (bool), prependTo(node), oldSize(num)] 11 | * @return {[type]} [description] 12 | */ 13 | constructor(itemOrList = [], options = { circular: false }) { 14 | let items = LList.convertToSeq(itemOrList); 15 | this.head = LList.makeHead(items); 16 | this.size = items.size; 17 | let prepend = options.prependTo && LList.isNode(options.prependTo); 18 | if (prepend){ 19 | if (this.size === 0){ 20 | this.head = options.prependTo; 21 | } else { 22 | this.tail.next = options.prependTo; 23 | } 24 | this.size = this.size + options.oldSize; 25 | } 26 | if (options.circular){ 27 | this.tail.next = this.head; 28 | this.circular = options.circular; 29 | } 30 | this.forEach(Object.freeze); 31 | Object.freeze(this); 32 | } 33 | 34 | /** 35 | * Find the final node in the Linked List in O(n). 36 | * @return {[Node]} [last node in the linked list, null if list is empty] 37 | */ 38 | get tail() { 39 | let pointsAt = (point) => { 40 | return (element) => { 41 | return element.next === point; 42 | } 43 | } 44 | let sentinel = (this.circular) ? this.head : null; 45 | return (this.size < 1) ? null : this.filter(pointsAt(sentinel))[0]; 46 | } 47 | 48 | /** 49 | * Returns a new list, with the current list as the tail of the input. 50 | * Utilizes tail-sharing. 51 | * @param {Item, Array, List, Node, or LList} toPrepend [] 52 | * @return {[type]} [description] 53 | */ 54 | prepend(toPrepend = []) { 55 | let opts = { 56 | circular : this.circular, 57 | prependTo : this.head, 58 | oldSize : this.size, 59 | }; 60 | //If circular, can't use tail-sharing. 61 | if (this.circular){ 62 | toPrepend = LList.convertToSeq(toPrepend); 63 | return new LList(toPrepend.concat(this.map(LList.getData)).toArray(), 64 | { circular: this.circular }); 65 | } 66 | //Else, prepend in O(1); 67 | return ( 68 | LList.isNode(toPrepend) ? new LList(LList.getData(toPrepend), opts) : 69 | LList.isLList(toPrepend) ? new LList(toPrepend.map(LList.getData), opts) : 70 | new LList(toPrepend, opts) 71 | ); 72 | } 73 | 74 | /** 75 | * Returns a new list in O(n) by recollecting elements of both 76 | * into a Seq, and passing that Seq to the LList constructor. 77 | * @param {[Item, Array, List, Node, or LList]} toAppend [description] 78 | * @return {[type]} [description] 79 | */ 80 | append(toAppend) { 81 | let opts = { circular : this.circular,} 82 | return ( 83 | new LList( 84 | this.map(LList.getData).concat( 85 | LList.isNode(toAppend) ? LList.getData(toAppend) : 86 | LList.isLList(toAppend) ? toAppend.map(LList.getData) : 87 | LList.convertToSeq(toAppend).toArray() 88 | ), opts 89 | ) 90 | ); 91 | } 92 | 93 | /** 94 | * Returns a new list, with copies of the old list's elements, pointed 95 | * in reverse order 96 | * @return {[type]} [description] 97 | */ 98 | reverse(){ 99 | let reversed = []; 100 | let unShiftToList = (element) => { reversed.unshift(element)} 101 | this.map(LList.getData).forEach(unShiftToList); 102 | return new LList(reversed, { circular: this.circular }); 103 | } 104 | 105 | /** 106 | * Returns a new list, sans the current list's head. 107 | * Uses tail-sharing. 108 | * @return {[type]} [description] 109 | */ 110 | removeHead() { 111 | let notFirst = (node) => { 112 | return (node !== this.head); 113 | } 114 | return new LList(this.filter(notFirst).map(LList.getData), 115 | { circular: this.circular }); 116 | } 117 | 118 | /** 119 | * Returns a new list in O(n), sans the current list's tail. 120 | * @return {[type]} [description] 121 | */ 122 | removeTail() { 123 | let notLast = (node) => { 124 | return (node !== this.tail) 125 | } 126 | return new LList(this.filter(notLast).map(LList.getData), 127 | { circular: this.circular }); 128 | } 129 | 130 | //Functional Helpers: 131 | forEach(cb) { 132 | let current = this.head; 133 | while (current !== null){ 134 | cb(current); 135 | current = current.next; 136 | //for circular lists: 137 | if (current === this.head){ break;} 138 | } 139 | } 140 | 141 | map(cb){ 142 | let mapped = []; 143 | let pushResult = (node) => { mapped.push(cb(node));} 144 | this.forEach(pushResult); 145 | return mapped; 146 | } 147 | 148 | filter(predicate) { 149 | let filtered = []; 150 | this.forEach((node) => { 151 | if(!!predicate(node)){ 152 | filtered.push(node); 153 | } 154 | }); 155 | return filtered; 156 | } 157 | 158 | isEmpty() { 159 | return (this.size <= 0); 160 | } 161 | 162 | /** 163 | * Creates individual nodes, from anything that can be stored 164 | * in an immutable Seq. 165 | * Can be passed null to create tails. 166 | * @param {[Primitive, Object]} data [] 167 | * @param {[New Node, null]} next [] 168 | * @return {[object]} [] 169 | */ 170 | static makeNode(data, next) { 171 | let node = { 172 | data: data, 173 | next: next, 174 | }; 175 | node[LList._LLNODE_SENTINEL_()] = true; 176 | return node; 177 | } 178 | 179 | static makeHead(seq) { 180 | if (seq === null || seq.size === 0) { 181 | return null; 182 | } else { 183 | let rest = seq.rest(); 184 | rest = (rest.size === 0) ? null : rest; 185 | return LList.makeNode(seq.first(), LList.makeHead(rest)); 186 | } 187 | } 188 | 189 | //Extracts data from Nodes 190 | static getData(node) { 191 | return ( 192 | (LList.isNode(node)) ? node.data : 193 | new Error('getData only accepts nodes.') 194 | ); 195 | } 196 | 197 | static isLList(maybeLList){ 198 | return !!(maybeLList && maybeLList[LList._LL_SENTINEL_()]); 199 | } 200 | 201 | static isNode(maybeNode){ 202 | return !!(maybeNode && maybeNode[LList._LLNODE_SENTINEL_()]); 203 | } 204 | 205 | static convertToSeq(itemOrList){ 206 | return Array.isArray(itemOrList) ? IM.Seq(itemOrList) : 207 | IM.Seq([].concat(itemOrList)); 208 | } 209 | 210 | static _LL_SENTINEL_(){ 211 | return "@@__LINKED_LIST__@@" 212 | } 213 | 214 | static _LLNODE_SENTINEL_(){ 215 | return "@@__LL_NODE__@@" 216 | } 217 | } 218 | 219 | LList.prototype[LList._LL_SENTINEL_()] = true; 220 | -------------------------------------------------------------------------------- /__tests__/integration/lists/LList.es6: -------------------------------------------------------------------------------- 1 | jest.autoMockOff(); 2 | var IM = require('immutable'), 3 | LList = require('../../../src/lists/LList'); 4 | 5 | describe('LList', () => { 6 | describe('new instance initialization', () => { 7 | let sLLEmpty = new LList(); 8 | it('starts with a null head', () => { 9 | expect(sLLEmpty.head).toBeNull(); 10 | }); 11 | 12 | it('starts with a null tail', () => { 13 | expect(sLLEmpty.tail).toBeNull(); 14 | }); 15 | 16 | it('starts with 0 size', () => { 17 | expect(sLLEmpty.size).toBe(0); 18 | }); 19 | 20 | it('returns true for empty size', function() { 21 | expect(sLLEmpty.isEmpty()).toBeTruthy(); 22 | }); 23 | 24 | it('stores a number on initialization', () => { 25 | let sLLNumber = new LList(1); 26 | expect(sLLNumber.size).toBe(1); 27 | expect(sLLNumber.isEmpty()).toBeFalsy(); 28 | expect(sLLNumber.head.data).toEqual(1); 29 | expect(sLLNumber.head.next).toBeNull(); 30 | expect(sLLNumber.tail.data).toEqual(1); 31 | }); 32 | 33 | it('stores a string on initialization', () => { 34 | let sLLString = new LList('string value'); 35 | expect(sLLString.size).toBe(1); 36 | expect(sLLString.head.next).toBeNull(); 37 | expect(sLLString.head.data).toEqual('string value'); 38 | expect(sLLString.tail.data).toEqual('string value'); 39 | expect(sLLString.tail.next).toBeNull(); 40 | 41 | }); 42 | 43 | it('stores an object on initialization', () => { 44 | let sLLObject = new LList({ name: 'clark' }); 45 | expect(sLLObject.size).toBe(1); 46 | expect(sLLObject.head.next).toBeNull(); 47 | expect(sLLObject.tail.next).toBeNull(); 48 | expect(sLLObject.head.data).toEqual({ name: 'clark' }); 49 | expect(sLLObject.tail.data).toEqual({ name: 'clark' }); 50 | }); 51 | 52 | it('stores an array on initialization', () => { 53 | let sLLArray = new LList([ 54 | 'random string', 55 | {'asdfasdf':'asdf'}, 56 | { name: 'anna' }, 57 | 26, 58 | ['tail value'], 59 | ]); 60 | expect(sLLArray.size).toBe(5); 61 | expect(sLLArray.head.data).toEqual('random string'); 62 | expect(sLLArray.head.next).toNotEqual(null); 63 | expect(sLLArray.head.next.data['asdfasdf']).toBe('asdf'); 64 | expect(sLLArray.head.next.next.next.data).toBe(26); 65 | expect(sLLArray.tail.next).toBeNull(); 66 | expect(sLLArray.tail.data).toEqual(['tail value']); 67 | }); 68 | 69 | it('contains immutable nodes', () => { 70 | let sLLNumber = new LList(1); 71 | let changeSomething = () =>{ 72 | sLLNumber.head.data = 2; 73 | } 74 | expect(changeSomething).toThrow(); 75 | expect(sLLNumber.head.data).toEqual(1); 76 | }); 77 | }); 78 | 79 | describe('public interface instance methods', () => { 80 | let sLLNumber = new LList(1); 81 | let sLLArray = new LList([ 82 | 'random string', 83 | {'asdfasdf':'asdf'}, 84 | { name: 'anna' }, 85 | ]); 86 | 87 | describe('prepends', () => { 88 | 89 | it('returns a new list when prepending undefined', () => { 90 | let nList = sLLNumber.prepend(); 91 | expect(nList).toNotBe(sLLNumber); 92 | expect(nList.head.data).toBe(1); 93 | }); 94 | 95 | it('prepends single elements', () => { 96 | let nList = sLLArray.prepend(0); 97 | expect(nList.head.data).toEqual(0); 98 | expect(nList.size).toBe(sLLArray.size+1); 99 | }); 100 | 101 | it('prepends an array', () => { 102 | let nList = sLLArray.prepend([1,2,3]); 103 | expect(nList.head.next.data).toEqual(2); 104 | expect(nList.size).toEqual(sLLArray.size+3); 105 | }); 106 | 107 | it('prepends nested arrays', () => { 108 | expect(sLLArray.prepend([[1,2,3],[1,2]]).head.data).toEqual([1,2,3]); 109 | }); 110 | 111 | it('prepends nodes, copying only a node\'s data', () => { 112 | let nList = sLLArray.prepend(sLLArray.head); 113 | expect(nList.head.data).toEqual('random string'); 114 | expect(nList.size).toBe(sLLArray.size+1); 115 | }); 116 | 117 | it('prepends other LLs', () => { 118 | let nList = sLLArray.prepend(sLLArray); 119 | expect(nList.size).toEqual(sLLArray.size*2); 120 | expect(nList.tail.data).toEqual(sLLArray.tail.data); 121 | }); 122 | 123 | it('prepends via tail-sharing', () => { 124 | let nList = sLLArray.prepend(sLLArray); 125 | expect(nList.head).toNotBe(sLLArray.head); 126 | expect(nList.tail.next).toBeNull(); 127 | let currentNode = nList.head; 128 | //increment the size of the previous array, check if tail is old head 129 | for (let i = 0; i < sLLArray.size; i++){ 130 | if (i === sLLArray.size-1){ 131 | expect(currentNode.next).toBe(sLLArray.head); 132 | } 133 | currentNode = currentNode.next; 134 | } 135 | }); 136 | }); 137 | 138 | describe('appends', () => { 139 | 140 | it('returns a new list when appends undefined', () => { 141 | let nList = sLLNumber.append(); 142 | expect(nList).toNotBe(sLLNumber); 143 | expect(nList.head.data).toBe(1); 144 | expect(nList.size).toBe(sLLNumber.size+1); 145 | }); 146 | 147 | it('appends single numbers', () => { 148 | let sLLNumber2 = sLLNumber.append(2); 149 | // shouldn't mutate original list 150 | expect(sLLNumber.tail.data).toEqual(1); 151 | expect(sLLNumber2.head.data).toEqual(1); 152 | expect(sLLNumber2.tail.data).toEqual(2); 153 | }); 154 | 155 | it('appends an array', () => { 156 | let nList = sLLArray.append([1,2,3]); 157 | expect(nList.tail.data).toEqual(3); 158 | expect(nList.size).toEqual(sLLArray.size+3); 159 | }); 160 | 161 | it('appends nested arrays', () => { 162 | expect(sLLArray.append([[1,2,3],[1,2]]).tail.data).toEqual([1,2]); 163 | }); 164 | 165 | it('appends nodes, copying only a node\'s data', () => { 166 | let nList = sLLArray.prepend(sLLArray.head); 167 | expect(nList.head.data).toEqual('random string'); 168 | expect(nList.head).toNotBe(sLLArray.head); 169 | expect(nList.size).toBe(sLLArray.size+1); 170 | 171 | }); 172 | 173 | it('appends other LLs', () => { 174 | let nList = sLLArray.append(sLLArray); 175 | expect(nList.size).toEqual(sLLArray.size*2); 176 | expect(nList.tail.data).toEqual(sLLArray.tail.data); 177 | }); 178 | 179 | }); 180 | 181 | describe('removal methods', () => { 182 | 183 | it('removes-head', () => { 184 | let sLLNumber2 = sLLNumber.append(2); 185 | let sLLNumber3 = sLLNumber.removeHead(); 186 | let sLLNumber4 = sLLNumber2.removeHead(); 187 | expect(sLLNumber.head.data).toEqual(1); 188 | expect(sLLNumber2.head.data).toEqual(1); 189 | expect(sLLNumber3.head).toBeNull(); 190 | expect(sLLNumber4.head.data).toEqual(2); 191 | expect(sLLNumber4.tail.data).toEqual(2); 192 | }); 193 | 194 | it('removes-tail', () => { 195 | let sLLNumber2 = sLLNumber.append(2); 196 | let sLLNumber3 = sLLNumber2.removeTail(); 197 | let sLLEmpty = sLLNumber.removeTail(); 198 | expect(sLLNumber.head.data).toEqual(1); 199 | expect(sLLNumber2.head.data).toEqual(1); 200 | expect(sLLNumber3.head.data).toEqual(1); 201 | expect(sLLEmpty.tail).toBeNull(); 202 | }); 203 | 204 | }); 205 | 206 | describe('functional methods', () => { 207 | describe('reverse', () => { 208 | it('reverses', () =>{ 209 | expect(sLLArray.reverse().head.data['name']).toEqual('anna'); 210 | }); 211 | }); 212 | }); 213 | }); 214 | }); 215 | -------------------------------------------------------------------------------- /dist/LList.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | 7 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 8 | 9 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 10 | 11 | require('core-js/shim'); 12 | 13 | var IM = require('immutable'); 14 | 15 | var LList = (function () { 16 | /** 17 | * Accepts items and list-like objects. 18 | * Converts them into Immutable Seq's of length >1 before 19 | * creating nodes. 20 | * @param {Array} itemOrList [description] 21 | * @param {Object} options [circular (bool), prependTo(node), oldSize(num)] 22 | * @return {[type]} [description] 23 | */ 24 | 25 | function LList() { 26 | var itemOrList = arguments[0] === undefined ? [] : arguments[0]; 27 | var options = arguments[1] === undefined ? { circular: false } : arguments[1]; 28 | 29 | _classCallCheck(this, LList); 30 | 31 | var items = LList.convertToSeq(itemOrList); 32 | this.head = LList.makeHead(items); 33 | this.size = items.size; 34 | var prepend = options.prependTo && LList.isNode(options.prependTo); 35 | if (prepend) { 36 | if (this.size === 0) { 37 | this.head = options.prependTo; 38 | } else { 39 | this.tail.next = options.prependTo; 40 | } 41 | this.size = this.size + options.oldSize; 42 | } 43 | if (options.circular) { 44 | this.tail.next = this.head; 45 | this.circular = options.circular; 46 | } 47 | this.forEach(Object.freeze); 48 | Object.freeze(this); 49 | } 50 | 51 | _createClass(LList, [{ 52 | key: 'prepend', 53 | 54 | /** 55 | * Returns a new list, with the current list as the tail of the input. 56 | * Utilizes tail-sharing. 57 | * @param {Item, Array, List, Node, or LList} toPrepend [] 58 | * @return {[type]} [description] 59 | */ 60 | value: function prepend() { 61 | var toPrepend = arguments[0] === undefined ? [] : arguments[0]; 62 | 63 | var opts = { 64 | circular: this.circular, 65 | prependTo: this.head, 66 | oldSize: this.size 67 | }; 68 | //If circular, can't use tail-sharing. 69 | if (this.circular) { 70 | toPrepend = LList.convertToSeq(toPrepend); 71 | return new LList(toPrepend.concat(this.map(LList.getData)).toArray(), { circular: this.circular }); 72 | } 73 | //Else, prepend in O(1); 74 | return LList.isNode(toPrepend) ? new LList(LList.getData(toPrepend), opts) : LList.isLList(toPrepend) ? new LList(toPrepend.map(LList.getData), opts) : new LList(toPrepend, opts); 75 | } 76 | }, { 77 | key: 'append', 78 | 79 | /** 80 | * Returns a new list in O(n) by recollecting elements of both 81 | * into a Seq, and passing that Seq to the LList constructor. 82 | * @param {[Item, Array, List, Node, or LList]} toAppend [description] 83 | * @return {[type]} [description] 84 | */ 85 | value: function append(toAppend) { 86 | var opts = { circular: this.circular }; 87 | return new LList(this.map(LList.getData).concat(LList.isNode(toAppend) ? LList.getData(toAppend) : LList.isLList(toAppend) ? toAppend.map(LList.getData) : LList.convertToSeq(toAppend).toArray()), opts); 88 | } 89 | }, { 90 | key: 'reverse', 91 | 92 | /** 93 | * Returns a new list, with copies of the old list's elements, pointed 94 | * in reverse order 95 | * @return {[type]} [description] 96 | */ 97 | value: function reverse() { 98 | var reversed = []; 99 | var unShiftToList = function unShiftToList(element) { 100 | reversed.unshift(element); 101 | }; 102 | this.map(LList.getData).forEach(unShiftToList); 103 | return new LList(reversed, { circular: this.circular }); 104 | } 105 | }, { 106 | key: 'removeHead', 107 | 108 | /** 109 | * Returns a new list, sans the current list's head. 110 | * Uses tail-sharing. 111 | * @return {[type]} [description] 112 | */ 113 | value: function removeHead() { 114 | var _this = this; 115 | 116 | var notFirst = function notFirst(node) { 117 | return node !== _this.head; 118 | }; 119 | return new LList(this.filter(notFirst).map(LList.getData), { circular: this.circular }); 120 | } 121 | }, { 122 | key: 'removeTail', 123 | 124 | /** 125 | * Returns a new list in O(n), sans the current list's tail. 126 | * @return {[type]} [description] 127 | */ 128 | value: function removeTail() { 129 | var _this2 = this; 130 | 131 | var notLast = function notLast(node) { 132 | return node !== _this2.tail; 133 | }; 134 | return new LList(this.filter(notLast).map(LList.getData), { circular: this.circular }); 135 | } 136 | }, { 137 | key: 'forEach', 138 | 139 | //Functional Helpers: 140 | value: function forEach(cb) { 141 | var current = this.head; 142 | while (current !== null) { 143 | cb(current); 144 | current = current.next; 145 | //for circular lists: 146 | if (current === this.head) { 147 | break; 148 | } 149 | } 150 | } 151 | }, { 152 | key: 'map', 153 | value: function map(cb) { 154 | var mapped = []; 155 | var pushResult = function pushResult(node) { 156 | mapped.push(cb(node)); 157 | }; 158 | this.forEach(pushResult); 159 | return mapped; 160 | } 161 | }, { 162 | key: 'filter', 163 | value: function filter(predicate) { 164 | var filtered = []; 165 | this.forEach(function (node) { 166 | if (!!predicate(node)) { 167 | filtered.push(node); 168 | } 169 | }); 170 | return filtered; 171 | } 172 | }, { 173 | key: 'isEmpty', 174 | value: function isEmpty() { 175 | return this.size <= 0; 176 | } 177 | }, { 178 | key: 'tail', 179 | 180 | /** 181 | * Find the final node in the Linked List in O(n). 182 | * @return {[Node]} [last node in the linked list, null if list is empty] 183 | */ 184 | get: function () { 185 | var pointsAt = function pointsAt(point) { 186 | return function (element) { 187 | return element.next === point; 188 | }; 189 | }; 190 | var sentinel = this.circular ? this.head : null; 191 | return this.size < 1 ? null : this.filter(pointsAt(sentinel))[0]; 192 | } 193 | }], [{ 194 | key: 'makeNode', 195 | 196 | /** 197 | * Creates individual nodes, from anything that can be stored 198 | * in an immutable Seq. 199 | * Can be passed null to create tails. 200 | * @param {[Primitive, Object]} data [] 201 | * @param {[New Node, null]} next [] 202 | * @return {[object]} [] 203 | */ 204 | value: function makeNode(data, next) { 205 | var node = { 206 | data: data, 207 | next: next 208 | }; 209 | node[LList._LLNODE_SENTINEL_()] = true; 210 | return node; 211 | } 212 | }, { 213 | key: 'makeHead', 214 | value: function makeHead(seq) { 215 | if (seq === null || seq.size === 0) { 216 | return null; 217 | } else { 218 | var rest = seq.rest(); 219 | rest = rest.size === 0 ? null : rest; 220 | return LList.makeNode(seq.first(), LList.makeHead(rest)); 221 | } 222 | } 223 | }, { 224 | key: 'getData', 225 | 226 | //Extracts data from Nodes 227 | value: function getData(node) { 228 | return LList.isNode(node) ? node.data : new Error('getData only accepts nodes.'); 229 | } 230 | }, { 231 | key: 'isLList', 232 | value: function isLList(maybeLList) { 233 | return !!(maybeLList && maybeLList[LList._LL_SENTINEL_()]); 234 | } 235 | }, { 236 | key: 'isNode', 237 | value: function isNode(maybeNode) { 238 | return !!(maybeNode && maybeNode[LList._LLNODE_SENTINEL_()]); 239 | } 240 | }, { 241 | key: 'convertToSeq', 242 | value: function convertToSeq(itemOrList) { 243 | return Array.isArray(itemOrList) ? IM.Seq(itemOrList) : IM.Seq([].concat(itemOrList)); 244 | } 245 | }, { 246 | key: '_LL_SENTINEL_', 247 | value: function _LL_SENTINEL_() { 248 | return '@@__LINKED_LIST__@@'; 249 | } 250 | }, { 251 | key: '_LLNODE_SENTINEL_', 252 | value: function _LLNODE_SENTINEL_() { 253 | return '@@__LL_NODE__@@'; 254 | } 255 | }]); 256 | 257 | return LList; 258 | })(); 259 | 260 | exports['default'] = LList; 261 | 262 | LList.prototype[LList._LL_SENTINEL_()] = true; 263 | module.exports = exports['default']; 264 | -------------------------------------------------------------------------------- /src/heaps/Heap.es6: -------------------------------------------------------------------------------- 1 | import 'core-js/shim'; 2 | const IM = require('immutable'); 3 | 4 | export default class Heap { 5 | /** 6 | * Heap constructor 7 | * @param {[type]} value [IM.List, Array, Heap, or Primitive] 8 | * @param {Boolean} isMax [Defaults to a min heap] 9 | * @param {[function]} comparator [Must return {0, 1, -1} to sort nodes] 10 | * @return {[type]} [Heap] 11 | */ 12 | constructor( 13 | value = null , 14 | isMax = false, 15 | comparator = Heap.defaultComparator() 16 | ) { 17 | if (!!value && this.isHeap(value)) { 18 | this._heapStorage = value._heapStorage; 19 | this.maxHeap = isMax; 20 | this.comparatorFunction = value.comparator; 21 | return this; 22 | } 23 | //Construct from primitive, array, or IM.List 24 | this._heapStorage = IM.List.isList(value) ? value : new IM.List(value); 25 | this.maxHeap = (isMax && typeof isMax === 'boolean') ? true : false; 26 | if (!!comparator && typeof comparator === 'function') { 27 | this.comparatorFunction = comparator; 28 | } else { 29 | this.comparatorFunction = Heap.defaultComparator(); 30 | } 31 | this._heapStorage = this.buildHeap(this._heapStorage); 32 | Object.freeze(this); 33 | } 34 | 35 | //Returns a new Heap, with the new value inserted. 36 | push(value) { 37 | let childIndex = this.storage.size; 38 | let parentIndex = Heap.findParentWithChild(childIndex); 39 | let newStorage = this.storage.push(value); 40 | let finalStorageList = this.siftUp(parentIndex, childIndex, newStorage); 41 | 42 | return new Heap(finalStorageList, this.isMaxHeap, this.comparator); 43 | } 44 | 45 | /** 46 | * Returns a new Heap with the extracted value (max or min) 47 | * Use Peek() for the top of the Heap. 48 | * With inputs, will behave as if replace() was called on a regular Heap. 49 | * @param {[type]} value [Repesents a new node] 50 | * @return {[type]} [A new Heap] 51 | */ 52 | pop(value) { 53 | if (this.storage.size <= 0) { return this; } 54 | let siftingList; 55 | if (value === undefined) { 56 | siftingList = this.storage.withMutations((list) => { 57 | return list.set(0, this.storage.last()).pop(); 58 | }) 59 | } else { 60 | siftingList = this.storage.set(0, value); 61 | } 62 | let finalStorageList = this.siftDown(siftingList, 0); 63 | 64 | return new Heap(finalStorageList, this.isMaxHeap, this.comparator); 65 | } 66 | 67 | //Alias method for pop, but with a value. 68 | replace(value) { 69 | return this.pop(value); 70 | } 71 | 72 | /** 73 | * Builds the array repesenting Heap Storage. 74 | * Should only be called from constructor. 75 | * Does so by calling siftDown on all non-leaf nodes. 76 | * @param {[type]} array [Heap Storage, must be an Immutable List] 77 | * @return {[type]} [New Heap Storage] 78 | */ 79 | buildHeap(list) { 80 | if (!IM.List.isList(list)) { 81 | return new Error("buildHeap input is not an Immutable List!"); 82 | } else{ 83 | //Using size of Heap, find the number of .siftDown calls... 84 | let roundedPowerOfTwo = Math.floor(Math.log(list.size)/Math.log(2)); 85 | let numberOfSifts = Math.pow(2,roundedPowerOfTwo)-1; 86 | let heapifiedList = list.withMutations((list) => { 87 | let siftedList = list.reduceRight((previous, current, index, array) => { 88 | let inRange = (index+1 <=numberOfSifts); 89 | return inRange ? this.siftDown(previous, index) : previous; 90 | }.bind(this), list); 91 | return siftedList; 92 | }); 93 | 94 | return heapifiedList; 95 | } 96 | } 97 | 98 | /** 99 | * Merges heaps, returning a new Heap. 100 | * @param {[Heap]} hp [description] 101 | * @return {[type]} [description] 102 | */ 103 | merge(hp) { 104 | let newStorage = this.buildHeap(hp._heapStorage.concat(this._heapStorage)); 105 | return new Heap(newStorage, hp.isMaxHeap, hp.comparator); 106 | } 107 | 108 | //Returns a sorted Immuatble List of the Heap's elements. 109 | heapSort() { 110 | let sortedList = new IM.List([]); 111 | sortedList = sortedList.withMutations((list) => { 112 | let heap = this; 113 | for (let i = 0; i < heap.size; i++) { 114 | list.push(heap.peek()); 115 | heap = heap.pop(); 116 | } 117 | 118 | return list; 119 | }.bind(this)); 120 | 121 | return sortedList; 122 | } 123 | 124 | //Takes a list, and sifts down depending on the index. 125 | siftDown(list, indexToSift) { 126 | return list.withMutations((list) => { 127 | let finalList = list; 128 | let switchDown = (p, c, list) => {//Parent and Child 129 | finalList = Heap.switchNodes(p, c, list); 130 | parentIndex = c; 131 | children = Heap.findChildrenWithParent(parentIndex, list); 132 | }.bind(this); 133 | let parentIndex = indexToSift; 134 | let children = Heap.findChildrenWithParent(parentIndex, list); 135 | while (!this.integrityCheck(parentIndex,children.left,finalList) 136 | || !this.integrityCheck(parentIndex,children.right,finalList)) { 137 | if (children.left && children.right) { 138 | //must select correct child to switch: 139 | if(this.integrityCheck(children.left, children.right ,finalList)) { 140 | switchDown(parentIndex, children.left, finalList); 141 | } else { 142 | switchDown(parentIndex, children.right, finalList); 143 | } 144 | } else if (children.left && !children.right) { 145 | //one Child broke integrity check: 146 | switchDown(parentIndex, children.left, finalList); 147 | } else if (!children.left && children.right) { 148 | //other Child broke integrity check: 149 | switchDown(parentIndex, children.right, finalList); 150 | } 151 | } 152 | return finalList; 153 | }.bind(this)); 154 | } 155 | 156 | //Child checks parent, switches if they violate the Heap property. 157 | siftUp(parentIndex, childIndex, list) { 158 | return list.withMutations((siftingList) => { 159 | while (!this.integrityCheck(parentIndex, childIndex, siftingList)) { 160 | siftingList = Heap.switchNodes(parentIndex, childIndex, siftingList); 161 | //Update child and parent to continue checking: 162 | childIndex = parentIndex; 163 | parentIndex = Heap.findParentWithChild(childIndex); 164 | } 165 | return siftingList; 166 | }.bind(this)) 167 | } 168 | 169 | peek() { 170 | return this.storage.first(); 171 | } 172 | 173 | get comparator() { 174 | return this.comparatorFunction; 175 | } 176 | 177 | get isMaxHeap() { 178 | return this.maxHeap; 179 | } 180 | 181 | get storage() { 182 | return this._heapStorage; 183 | } 184 | 185 | get size() { 186 | return this._heapStorage.size; 187 | } 188 | 189 | isHeap(object) { 190 | return (this.__proto__ === object.__proto__) ? true : false; 191 | } 192 | 193 | //Standard comparator function. returns 1, -1, or 0 (for a match). 194 | static defaultComparator() { 195 | return function(a, b) { 196 | if (a > b) { 197 | return 1; 198 | } 199 | if (a < b) { 200 | return -1; 201 | } 202 | return 0; 203 | } 204 | } 205 | 206 | /** 207 | * Returns a boolean for whether Heap Integrity is maintained. 208 | * @param {[type]} parentIndex [description] 209 | * @param {[type]} childIndex [description] 210 | * @param {[type]} list [description] 211 | * @return {[type]} [description] 212 | */ 213 | integrityCheck(parentIndex, childIndex, list) { 214 | if (parentIndex === null || childIndex === null) {return true;} 215 | let parentNode = list.get(parentIndex); 216 | let childNode = list.get(childIndex); 217 | let comparison = this.comparatorFunction(parentNode, childNode); 218 | if (this.isMaxHeap) { 219 | //maxHeap, parent should be larger 220 | return (comparison === 1 || comparison === 0) ? true : false; 221 | } else{ 222 | //minHeap 223 | return (comparison === -1 || comparison === 0) ? true: false; 224 | } 225 | } 226 | 227 | /** 228 | * Switched the locations of two nodes. 229 | * @param {[type]} parentIndex [description] 230 | * @param {[type]} childIndex [description] 231 | * @param {[type]} list [description] 232 | * @return {[type]} [description] 233 | */ 234 | static switchNodes(parentIndex, childIndex, list) { 235 | return list.withMutations((list) => { 236 | let temp = list.get(parentIndex); 237 | return list.set(parentIndex, list.get(childIndex)).set(childIndex, temp) 238 | }); 239 | } 240 | 241 | //assigns child indexes for a given parent index 242 | static findChildrenWithParent(parentIndex, list) { 243 | let leftIdx = (parentIndex * 2) + 1; 244 | let rightIdx = (parentIndex + 1) * 2; 245 | return { 246 | left: (leftIdx >= list.size) ? null : leftIdx, 247 | right: (rightIdx >= list.size) ? null : rightIdx, 248 | } 249 | } 250 | 251 | //Find the parent of a child, with the Child's index 252 | static findParentWithChild(childIndex) { 253 | return (childIndex === 0) ? null : 254 | (childIndex % 2 === 0 ? childIndex / 2 - 1 : Math.floor(childIndex / 2)); 255 | } 256 | 257 | } 258 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2015 Clark Feusier & Sze-Hung Daniel Tsui 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /dist/Heap.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | 7 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 8 | 9 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 10 | 11 | require('core-js/shim'); 12 | 13 | var IM = require('immutable'); 14 | 15 | var Heap = (function () { 16 | /** 17 | * Heap constructor 18 | * @param {[type]} value [IM.List, Array, Heap, or Primitive] 19 | * @param {Boolean} isMax [Defaults to a min heap] 20 | * @param {[function]} comparator [Must return {0, 1, -1} to sort nodes] 21 | * @return {[type]} [Heap] 22 | */ 23 | 24 | function Heap() { 25 | var value = arguments[0] === undefined ? null : arguments[0]; 26 | var isMax = arguments[1] === undefined ? false : arguments[1]; 27 | var comparator = arguments[2] === undefined ? Heap.defaultComparator() : arguments[2]; 28 | 29 | _classCallCheck(this, Heap); 30 | 31 | if (!!value && this.isHeap(value)) { 32 | this._heapStorage = value._heapStorage; 33 | this.maxHeap = isMax; 34 | this.comparatorFunction = value.comparator; 35 | return this; 36 | } 37 | //Construct from primitive, array, or IM.List 38 | this._heapStorage = IM.List.isList(value) ? value : new IM.List(value); 39 | this.maxHeap = isMax && typeof isMax === 'boolean' ? true : false; 40 | if (!!comparator && typeof comparator === 'function') { 41 | this.comparatorFunction = comparator; 42 | } else { 43 | this.comparatorFunction = Heap.defaultComparator(); 44 | } 45 | this._heapStorage = this.buildHeap(this._heapStorage); 46 | Object.freeze(this); 47 | } 48 | 49 | _createClass(Heap, [{ 50 | key: 'push', 51 | 52 | //Returns a new Heap, with the new value inserted. 53 | value: function push(value) { 54 | var childIndex = this.storage.size; 55 | var parentIndex = Heap.findParentWithChild(childIndex); 56 | var newStorage = this.storage.push(value); 57 | var finalStorageList = this.siftUp(parentIndex, childIndex, newStorage); 58 | 59 | return new Heap(finalStorageList, this.isMaxHeap, this.comparator); 60 | } 61 | }, { 62 | key: 'pop', 63 | 64 | /** 65 | * Returns a new Heap with the extracted value (max or min) 66 | * Use Peek() for the top of the Heap. 67 | * With inputs, will behave as if replace() was called on a regular Heap. 68 | * @param {[type]} value [Repesents a new node] 69 | * @return {[type]} [A new Heap] 70 | */ 71 | value: function pop(value) { 72 | var _this = this; 73 | 74 | if (this.storage.size <= 0) { 75 | return this; 76 | } 77 | var siftingList = undefined; 78 | if (value === undefined) { 79 | siftingList = this.storage.withMutations(function (list) { 80 | return list.set(0, _this.storage.last()).pop(); 81 | }); 82 | } else { 83 | siftingList = this.storage.set(0, value); 84 | } 85 | var finalStorageList = this.siftDown(siftingList, 0); 86 | 87 | return new Heap(finalStorageList, this.isMaxHeap, this.comparator); 88 | } 89 | }, { 90 | key: 'replace', 91 | 92 | //Alias method for pop, but with a value. 93 | value: function replace(value) { 94 | return this.pop(value); 95 | } 96 | }, { 97 | key: 'buildHeap', 98 | 99 | /** 100 | * Builds the array repesenting Heap Storage. 101 | * Should only be called from constructor. 102 | * Does so by calling siftDown on all non-leaf nodes. 103 | * @param {[type]} array [Heap Storage, must be an Immutable List] 104 | * @return {[type]} [New Heap Storage] 105 | */ 106 | value: function buildHeap(list) { 107 | var _this2 = this; 108 | 109 | if (!IM.List.isList(list)) { 110 | return new Error('buildHeap input is not an Immutable List!'); 111 | } else { 112 | var _ret = (function () { 113 | //Using size of Heap, find the number of .siftDown calls... 114 | var roundedPowerOfTwo = Math.floor(Math.log(list.size) / Math.log(2)); 115 | var numberOfSifts = Math.pow(2, roundedPowerOfTwo) - 1; 116 | var heapifiedList = list.withMutations(function (list) { 117 | var siftedList = list.reduceRight((function (previous, current, index, array) { 118 | var inRange = index + 1 <= numberOfSifts; 119 | return inRange ? _this2.siftDown(previous, index) : previous; 120 | }).bind(_this2), list); 121 | return siftedList; 122 | }); 123 | 124 | return { 125 | v: heapifiedList 126 | }; 127 | })(); 128 | 129 | if (typeof _ret === 'object') return _ret.v; 130 | } 131 | } 132 | }, { 133 | key: 'merge', 134 | 135 | /** 136 | * Merges heaps, returning a new Heap. 137 | * @param {[Heap]} hp [description] 138 | * @return {[type]} [description] 139 | */ 140 | value: function merge(hp) { 141 | var newStorage = this.buildHeap(hp._heapStorage.concat(this._heapStorage)); 142 | return new Heap(newStorage, hp.isMaxHeap, hp.comparator); 143 | } 144 | }, { 145 | key: 'heapSort', 146 | 147 | //Returns a sorted Immuatble List of the Heap's elements. 148 | value: function heapSort() { 149 | var _this3 = this; 150 | 151 | var sortedList = new IM.List([]); 152 | sortedList = sortedList.withMutations((function (list) { 153 | var heap = _this3; 154 | for (var i = 0; i < heap.size; i++) { 155 | list.push(heap.peek()); 156 | heap = heap.pop(); 157 | } 158 | 159 | return list; 160 | }).bind(this)); 161 | 162 | return sortedList; 163 | } 164 | }, { 165 | key: 'siftDown', 166 | 167 | //Takes a list, and sifts down depending on the index. 168 | value: function siftDown(list, indexToSift) { 169 | var _this4 = this; 170 | 171 | return list.withMutations((function (list) { 172 | var finalList = list; 173 | var switchDown = (function (p, c, list) { 174 | //Parent and Child 175 | finalList = Heap.switchNodes(p, c, list); 176 | parentIndex = c; 177 | children = Heap.findChildrenWithParent(parentIndex, list); 178 | }).bind(_this4); 179 | var parentIndex = indexToSift; 180 | var children = Heap.findChildrenWithParent(parentIndex, list); 181 | while (!_this4.integrityCheck(parentIndex, children.left, finalList) || !_this4.integrityCheck(parentIndex, children.right, finalList)) { 182 | if (children.left && children.right) { 183 | //must select correct child to switch: 184 | if (_this4.integrityCheck(children.left, children.right, finalList)) { 185 | switchDown(parentIndex, children.left, finalList); 186 | } else { 187 | switchDown(parentIndex, children.right, finalList); 188 | } 189 | } else if (children.left && !children.right) { 190 | //one Child broke integrity check: 191 | switchDown(parentIndex, children.left, finalList); 192 | } else if (!children.left && children.right) { 193 | //other Child broke integrity check: 194 | switchDown(parentIndex, children.right, finalList); 195 | } 196 | } 197 | return finalList; 198 | }).bind(this)); 199 | } 200 | }, { 201 | key: 'siftUp', 202 | 203 | //Child checks parent, switches if they violate the Heap property. 204 | value: function siftUp(parentIndex, childIndex, list) { 205 | var _this5 = this; 206 | 207 | return list.withMutations((function (siftingList) { 208 | while (!_this5.integrityCheck(parentIndex, childIndex, siftingList)) { 209 | siftingList = Heap.switchNodes(parentIndex, childIndex, siftingList); 210 | //Update child and parent to continue checking: 211 | childIndex = parentIndex; 212 | parentIndex = Heap.findParentWithChild(childIndex); 213 | } 214 | return siftingList; 215 | }).bind(this)); 216 | } 217 | }, { 218 | key: 'peek', 219 | value: function peek() { 220 | return this.storage.first(); 221 | } 222 | }, { 223 | key: 'isHeap', 224 | value: function isHeap(object) { 225 | return this.__proto__ === object.__proto__ ? true : false; 226 | } 227 | }, { 228 | key: 'integrityCheck', 229 | 230 | /** 231 | * Returns a boolean for whether Heap Integrity is maintained. 232 | * @param {[type]} parentIndex [description] 233 | * @param {[type]} childIndex [description] 234 | * @param {[type]} list [description] 235 | * @return {[type]} [description] 236 | */ 237 | value: function integrityCheck(parentIndex, childIndex, list) { 238 | if (parentIndex === null || childIndex === null) { 239 | return true; 240 | } 241 | var parentNode = list.get(parentIndex); 242 | var childNode = list.get(childIndex); 243 | var comparison = this.comparatorFunction(parentNode, childNode); 244 | if (this.isMaxHeap) { 245 | //maxHeap, parent should be larger 246 | return comparison === 1 || comparison === 0 ? true : false; 247 | } else { 248 | //minHeap 249 | return comparison === -1 || comparison === 0 ? true : false; 250 | } 251 | } 252 | }, { 253 | key: 'comparator', 254 | get: function () { 255 | return this.comparatorFunction; 256 | } 257 | }, { 258 | key: 'isMaxHeap', 259 | get: function () { 260 | return this.maxHeap; 261 | } 262 | }, { 263 | key: 'storage', 264 | get: function () { 265 | return this._heapStorage; 266 | } 267 | }, { 268 | key: 'size', 269 | get: function () { 270 | return this._heapStorage.size; 271 | } 272 | }], [{ 273 | key: 'defaultComparator', 274 | 275 | //Standard comparator function. returns 1, -1, or 0 (for a match). 276 | value: function defaultComparator() { 277 | return function (a, b) { 278 | if (a > b) { 279 | return 1; 280 | } 281 | if (a < b) { 282 | return -1; 283 | } 284 | return 0; 285 | }; 286 | } 287 | }, { 288 | key: 'switchNodes', 289 | 290 | /** 291 | * Switched the locations of two nodes. 292 | * @param {[type]} parentIndex [description] 293 | * @param {[type]} childIndex [description] 294 | * @param {[type]} list [description] 295 | * @return {[type]} [description] 296 | */ 297 | value: function switchNodes(parentIndex, childIndex, list) { 298 | return list.withMutations(function (list) { 299 | var temp = list.get(parentIndex); 300 | return list.set(parentIndex, list.get(childIndex)).set(childIndex, temp); 301 | }); 302 | } 303 | }, { 304 | key: 'findChildrenWithParent', 305 | 306 | //assigns child indexes for a given parent index 307 | value: function findChildrenWithParent(parentIndex, list) { 308 | var leftIdx = parentIndex * 2 + 1; 309 | var rightIdx = (parentIndex + 1) * 2; 310 | return { 311 | left: leftIdx >= list.size ? null : leftIdx, 312 | right: rightIdx >= list.size ? null : rightIdx 313 | }; 314 | } 315 | }, { 316 | key: 'findParentWithChild', 317 | 318 | //Find the parent of a child, with the Child's index 319 | value: function findParentWithChild(childIndex) { 320 | return childIndex === 0 ? null : childIndex % 2 === 0 ? childIndex / 2 - 1 : Math.floor(childIndex / 2); 321 | } 322 | }]); 323 | 324 | return Heap; 325 | })(); 326 | 327 | exports['default'] = Heap; 328 | module.exports = exports['default']; 329 | -------------------------------------------------------------------------------- /src/binary_trees/BSTree.es6: -------------------------------------------------------------------------------- 1 | import 'core-js/shim'; 2 | const IM = require('immutable'); 3 | const BSTNode = require('./BSTNode'); 4 | 5 | export default class BSTree { 6 | /** 7 | * Accepts optional custom comparator function for sorting keys, 8 | * and optional BSTNode to use as root of new tree. 9 | * If no comparator, default comparator with #sort interface will be used. 10 | * @param {[Function]} comparator [must return 0, 1, or -1 to sort subtrees] 11 | * @param {[BSTNode]} _root [optional root from which to construct tree] 12 | * @constructor 13 | */ 14 | constructor(comparator, _root) { 15 | this._comparator = BSTree.setComparator(comparator); 16 | this._root = null; 17 | this._count = 0; 18 | if (BSTree.isBSTNode(_root)) { 19 | this._root = BSTree.cloneNode(_root); 20 | this._count = BSTree.recount(_root); 21 | } 22 | Object.freeze(this); 23 | } 24 | 25 | /** 26 | * Get the number of nodes in the tree 27 | * @return {[Number]} [count of all nodes] 28 | */ 29 | get size() { 30 | return this._count; 31 | } 32 | 33 | /** 34 | * Get the key comparator function of the tree 35 | * @return {[Function]} [custom comparator, default if no custom comparator] 36 | */ 37 | get comparator() { 38 | return this._comparator; 39 | } 40 | 41 | /** 42 | * Get the first node in tree 43 | * @return {[BSTNode|null]} [root node, null if tree is empty] 44 | */ 45 | get root() { 46 | return this._root; 47 | } 48 | 49 | /** 50 | * Get the node with the smallest key in tree in O(log n). 51 | * @return {[BSTNode|null]} [min node, null if tree is empty] 52 | */ 53 | get min() { 54 | return BSTree.traverseSide('left', this); 55 | } 56 | 57 | /** 58 | * Get the node with the largest key in tree in O(log n). 59 | * @return {[BSTNode|null]} [max node, null if tree is empty] 60 | */ 61 | get max() { 62 | return BSTree.traverseSide('right', this); 63 | } 64 | 65 | /** 66 | * Get all of the keys in tree in an ordered array in O(n). 67 | * @return {[Array]} [all the keys in the tree, ordered based on comparator] 68 | */ 69 | get keys() { 70 | let keys = []; 71 | this.forEach(node => keys.push(node.key)); 72 | return keys; 73 | } 74 | 75 | /** 76 | * Get all of the values in tree in a key-ordered array in O(n). 77 | * @return {[Array]} [all the values in the tree, ordered based on key comparison] 78 | */ 79 | get values() { 80 | let values = []; 81 | this.forEach(node => values.push(node.value)); 82 | return values; 83 | } 84 | 85 | /** 86 | * Returns a new tree with the key and value inserted. 87 | * @param {[*]} key [the key with which to store the value parameter] 88 | * @param {[*]} value [the value to store with key parameter] 89 | * @return {[BSTree]} [new BST with the key-value pair inserted] 90 | */ 91 | insert(key, value) { 92 | if (key === undefined) { 93 | return this.clone(); 94 | } else if (!this.size) { 95 | return new BSTree(this.comparator, new BSTNode(key, value, null, null, 1), 1); 96 | } else { 97 | let [node, ancestors] = BSTree.recursiveSearch(this.comparator, this.root, key); 98 | node = node ? new BSTNode(node._store.set('_value', value)) : 99 | new BSTNode(key, value, null, null, this.size + 1); 100 | return new BSTree(this.comparator, BSTree.constructFromLeaf(node, ancestors)); 101 | } 102 | } 103 | 104 | /** 105 | * Returns a new tree with the given node removed. If the key is not found, 106 | * returns a clone of current tree. 107 | * @param {[*]} key [the key of the node to remove] 108 | * @return {[BSTree]} [new BST with the given node removed] 109 | */ 110 | remove(key) { 111 | let [node, ancestors] = BSTree.recursiveSearch(this.comparator, this.root, key); 112 | if (!this.size || key === undefined || !node) { 113 | return this.clone(); 114 | } else if (node) { 115 | return BSTree.removeFound(this.comparator, node, ancestors); 116 | } 117 | } 118 | 119 | /** 120 | * Get the node with the matching key in tree in O(log n). 121 | * @param {[*]} key [the key of the node to find] 122 | * @return {[BSTNode|null]} [found node, null if key not found] 123 | */ 124 | find(key) { 125 | return BSTree.recursiveSearch(this.comparator, this.root, key)[0]; 126 | } 127 | 128 | /** 129 | * Get the value of the node with the matching key in tree in O(log n). 130 | * @param {[*]} key [the key of the value to get] 131 | * @return {[*]} [value of found node, null if key not found] 132 | */ 133 | get(key) { 134 | let [search,] = BSTree.recursiveSearch(this.comparator, this.root, key); 135 | return !search ? null : search.value; 136 | } 137 | 138 | /** 139 | * Check if there is a node with the matching value in tree in O(n). 140 | * @param {[*]} value [the value of the node for which to search] 141 | * @return {[Boolean]} [true if found, false if not found] 142 | */ 143 | contains(value) { 144 | return this.values.indexOf(value) > -1; 145 | } 146 | 147 | /** 148 | * Apply the callback to each node in the tree, in-order. 149 | * @param {[Function]} callback [recieves a BSTNode as input] 150 | * @return {[undefined]} [side-effect function] 151 | */ 152 | forEach(callback) { 153 | BSTree.traverseInOrder(this.root, callback); 154 | } 155 | 156 | /** 157 | * Returns a new tree with the list's key-value pairs inserted. 158 | * @param {[Array]} listToInsert [an array of key-value tuples to insert] 159 | * @return {[BSTree]} [new BST with the all the key-value pairs inserted] 160 | */ 161 | insertAll(listToInsert = []) { 162 | let resultTree = this; 163 | listToInsert.forEach(pair => { 164 | resultTree = resultTree.insert(pair[0], pair[1]); 165 | }); 166 | return resultTree; 167 | } 168 | 169 | /** 170 | * Clone the current tree. 171 | * @return {[BSTree]} [new BST clone of current tree] 172 | */ 173 | clone() { 174 | return new BSTree(this.comparator, this.root); 175 | } 176 | 177 | /** 178 | * Returns the given comparator if acceptable, or the default comparator function. 179 | * @param {[Function]} comparator [must return {1, 0, -1} when comparing two inputs] 180 | * @return {[Function]} [custom comparator if given, default comparator otherwise] 181 | */ 182 | static setComparator(comparator) { 183 | let isComparator = !!comparator && typeof comparator === 'function'; 184 | return isComparator ? comparator : BSTree.defaultComp; 185 | } 186 | 187 | /** 188 | * Returns 1, 0, or -1 based on default comparison criteria. 189 | * @param {[*]} keyA [the first key for comparison] 190 | * @param {[*]} keyB [the second key for comparison] 191 | * @return {[Number]} [-1 if keyA is smaller, 1 if keyA is bigger, 0 if the same] 192 | */ 193 | static defaultComp(keyA, keyB) { 194 | if (keyA < keyB) return -1; 195 | else if (keyA > keyB) return 1; 196 | else return 0; 197 | } 198 | 199 | /** 200 | * Checks if a given input is a BSTNode. 201 | * @param {[*]} maybe [entity to check for BSTNode-ness] 202 | * @return {[Boolean]} [true if maybe is a BSTNode, false otherwise] 203 | */ 204 | static isBSTNode(maybe) { 205 | return !!maybe && maybe.constructor === BSTNode; 206 | } 207 | 208 | /** 209 | * Clone the input BSTNode. 210 | * @param {[BSTNode]} node [node to clone] 211 | * @return {[BSTNode]} [new BSTNode clone of input node] 212 | */ 213 | static cloneNode(node) { 214 | return new BSTNode(node.key, node.value, node.left, node.right, node.id); 215 | } 216 | 217 | /** 218 | * Returns the count of nodes present in _root input. 219 | * @param {[BSTNode]} _root [the root to recount] 220 | * @return {[Number]} [count of nodes in _root] 221 | */ 222 | static recount(_root) { 223 | let count = 0; 224 | BSTree.traverseInOrder(_root, () => count++); 225 | return count; 226 | } 227 | 228 | /** 229 | * Returns the ancestor nodes and in-order predecessor of the input node. 230 | * @param {[BSTNode]} leftChild [node from which to start the search for IOP] 231 | * @return {[Array]} [tuple containing a stack of ancestors and the IOP] 232 | */ 233 | static findInOrderPredecessor(leftChild) { 234 | let currentIop = leftChild, 235 | ancestors = []; 236 | while (currentIop.right) { 237 | ancestors.push(['_right', currentIop]); 238 | currentIop = currentIop.right; 239 | } 240 | return [ancestors, currentIop]; 241 | } 242 | 243 | /** 244 | * Apply the callback to each node, in-order. 245 | * Recursive traversal, static version of #forEach 246 | * @param {[BSTNode]} node [the root node from which to start traversal] 247 | * @param {[Function]} callback [recieves a BSTNode as input] 248 | * @return {[undefined]} [side-effect function] 249 | */ 250 | static traverseInOrder(node, cb) { 251 | if (!node) return; 252 | let left = node.left, right = node.right; 253 | if (left) BSTree.traverseInOrder(left, cb); 254 | cb(node); 255 | if (right) BSTree.traverseInOrder(right, cb); 256 | } 257 | 258 | /** 259 | * Returns the leaf BSTNode furthest down a given side of tree in O(log n). 260 | * @return {[BSTNode|null]} [max or min node, null if tree is empty] 261 | */ 262 | static traverseSide(side, tree) { 263 | let currentRoot = tree.root; 264 | if (!currentRoot) return null; 265 | let nextNode = currentRoot[side]; 266 | while (nextNode) { 267 | currentRoot = nextNode; 268 | nextNode = nextNode[side]; 269 | } 270 | return currentRoot; 271 | } 272 | 273 | /** 274 | * Returns tuple of the found node and a stack of ancestor nodes. 275 | * Generic O(log n) recursive search of BSTree. 276 | * @param {[Function]} comparator [must return {1, 0, -1} when comparing two inputs] 277 | * @param {[BSTNode]} node [node from which to start the search] 278 | * @param {[*]} key [the key used for search] 279 | * @param {[Array]} ancestorStack [stack of tuples containing ancestor side and ancestor node] 280 | * @return {[Array]} [tuple containing null or the found node, and a stack of ancestors] 281 | */ 282 | static recursiveSearch(comparator, node, key, ancestorStack = []) { 283 | if (!node) return [null, ancestorStack]; 284 | let comparisons = comparator(node.key, key); 285 | if (comparisons === -1) { 286 | ancestorStack.push(['_right', node]) 287 | return BSTree.recursiveSearch(comparator, node.right, key, ancestorStack); 288 | } else if (comparisons === 1) { 289 | ancestorStack.push(['_left', node]) 290 | return BSTree.recursiveSearch(comparator, node.left, key, ancestorStack); 291 | } else { 292 | return [node, ancestorStack]; 293 | } 294 | } 295 | 296 | /** 297 | * Returns new root node with input node removed. 298 | * Input node must have no children. 299 | * @param {[BSTNode]} node [node from which to start the removal] 300 | * @param {[Array]} ancestorStack [stack of tuples containing ancestor side and ancestor node] 301 | * @return {[BSTNode]} [new root node constructed from tree with input node removed] 302 | */ 303 | static removeNoChildren(node, ancestors) { 304 | if (ancestors.length) { 305 | let [childSide, parentNode] = ancestors.pop(); 306 | node = new BSTNode(parentNode._store.set(childSide, null)); 307 | } 308 | return BSTree.constructFromLeaf(node, ancestors) 309 | } 310 | 311 | /** 312 | * Returns new root node with input node removed. 313 | * Input node must have exactly one child. 314 | * @param {[BSTNode]} node [node from which to start the removal] 315 | * @param {[Array]} ancestorStack [stack of tuples containing ancestor side and ancestor node] 316 | * @return {[BSTNode]} [new root node with input node removed and children repositioned] 317 | */ 318 | static removeOneChild(node, ancestors) { 319 | let childNode = node.children[0][1]; 320 | if (!ancestors.length) { 321 | return childNode; 322 | } else { 323 | let [childSide, parentNode] = ancestors.pop(), 324 | leaf = new BSTNode(parentNode._store.set(childSide, childNode)); 325 | return BSTree.constructFromLeaf(leaf, ancestors); 326 | } 327 | } 328 | 329 | /** 330 | * Returns new root node with input node removed. 331 | * Input node must have exactly two children. 332 | * @param {[Function]} comparator [must return {1, 0, -1} when comparing two inputs] 333 | * @param {[BSTNode]} node [node from which to start the removal] 334 | * @param {[Array]} ancestorStack [stack of tuples containing ancestor side and ancestor node] 335 | * @return {[BSTNode]} [new root node with input node removed and children repositioned] 336 | */ 337 | static removeTwoChildren(comparator, node, ancestors) { 338 | let [rightAncestors, iop] = BSTree.findInOrderPredecessor(node.left); 339 | let iopReplacementStore = iop.store.withMutations(_store => { 340 | _store.set('_key', node.key).set('_value', node.value).set('_id', node.id); 341 | }); 342 | let targetReplacementStore = node.store.withMutations(_store => { 343 | _store.set('_key', iop.key) 344 | .set('_value', iop.value) 345 | .set('_id', iop.id) 346 | .set('_left', new BSTNode(iopReplacementStore)); 347 | }); 348 | let newIopNode = new BSTNode(targetReplacementStore); 349 | ancestors = ancestors.concat([['_left', newIopNode]], rightAncestors); 350 | return BSTree.removeFound(comparator, newIopNode.left, ancestors); 351 | } 352 | 353 | /** 354 | * Returns new root node with input node removed. 355 | * Input node can have any number of children. Dispatches to correct removal method. 356 | * @param {[Function]} comparator [must return {1, 0, -1} when comparing two inputs] 357 | * @param {[BSTNode]} node [node from which to start the removal] 358 | * @param {[Array]} ancestorStack [stack of tuples containing ancestor side and ancestor node] 359 | * @return {[BSTNode]} [new root node with input node removed and children repositioned] 360 | */ 361 | static removeFound(comparator, node, ancestors) { 362 | switch (node.children.length) { 363 | case 1: 364 | return new BSTree(comparator, BSTree.removeOneChild(node, ancestors)); 365 | break; 366 | case 2: 367 | return BSTree.removeTwoChildren(comparator, node, ancestors); 368 | break; 369 | default: 370 | return new BSTree(comparator, BSTree.removeNoChildren(node, ancestors)); 371 | break; 372 | } 373 | } 374 | 375 | /** 376 | * Returns new root node reconstructed from a leaf node and ancestors. 377 | * @param {[BSTNode]} node [leaf node from which to start the construction] 378 | * @param {[Array]} ancestorStack [stack of tuples containing ancestor side and ancestor node] 379 | * @return {[BSTNode]} [new root node reconstructed from leaf and ancestors stack] 380 | */ 381 | static constructFromLeaf(node, ancestors) { 382 | while (ancestors.length) { 383 | let [childSide, parentNode] = ancestors.pop(); 384 | node = new BSTNode(parentNode._store.set(childSide, node)); 385 | } 386 | return node; 387 | } 388 | 389 | } 390 | -------------------------------------------------------------------------------- /dist/BSTree.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { 4 | value: true 5 | }); 6 | 7 | var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); 8 | 9 | function _slicedToArray(arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } } 10 | 11 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } } 12 | 13 | require('core-js/shim'); 14 | 15 | var IM = require('immutable'); 16 | var BSTNode = require('./BSTNode'); 17 | 18 | var BSTree = (function () { 19 | /** 20 | * Accepts optional custom comparator function for sorting keys, 21 | * and optional BSTNode to use as root of new tree. 22 | * If no comparator, default comparator with #sort interface will be used. 23 | * @param {[Function]} comparator [must return 0, 1, or -1 to sort subtrees] 24 | * @param {[BSTNode]} _root [optional root from which to construct tree] 25 | * @constructor 26 | */ 27 | 28 | function BSTree(comparator, _root) { 29 | _classCallCheck(this, BSTree); 30 | 31 | this._comparator = BSTree.setComparator(comparator); 32 | this._root = null; 33 | this._count = 0; 34 | if (BSTree.isBSTNode(_root)) { 35 | this._root = BSTree.cloneNode(_root); 36 | this._count = BSTree.recount(_root); 37 | } 38 | Object.freeze(this); 39 | } 40 | 41 | _createClass(BSTree, [{ 42 | key: 'insert', 43 | 44 | /** 45 | * Returns a new tree with the key and value inserted. 46 | * @param {[*]} key [the key with which to store the value parameter] 47 | * @param {[*]} value [the value to store with key parameter] 48 | * @return {[BSTree]} [new BST with the key-value pair inserted] 49 | */ 50 | value: function insert(key, value) { 51 | if (key === undefined) { 52 | return this.clone(); 53 | } else if (!this.size) { 54 | return new BSTree(this.comparator, new BSTNode(key, value, null, null, 1), 1); 55 | } else { 56 | var _BSTree$recursiveSearch = BSTree.recursiveSearch(this.comparator, this.root, key); 57 | 58 | var _BSTree$recursiveSearch2 = _slicedToArray(_BSTree$recursiveSearch, 2); 59 | 60 | var node = _BSTree$recursiveSearch2[0]; 61 | var ancestors = _BSTree$recursiveSearch2[1]; 62 | 63 | node = node ? new BSTNode(node._store.set('_value', value)) : new BSTNode(key, value, null, null, this.size + 1); 64 | return new BSTree(this.comparator, BSTree.constructFromLeaf(node, ancestors)); 65 | } 66 | } 67 | }, { 68 | key: 'remove', 69 | 70 | /** 71 | * Returns a new tree with the given node removed. If the key is not found, 72 | * returns a clone of current tree. 73 | * @param {[*]} key [the key of the node to remove] 74 | * @return {[BSTree]} [new BST with the given node removed] 75 | */ 76 | value: function remove(key) { 77 | var _BSTree$recursiveSearch3 = BSTree.recursiveSearch(this.comparator, this.root, key); 78 | 79 | var _BSTree$recursiveSearch32 = _slicedToArray(_BSTree$recursiveSearch3, 2); 80 | 81 | var node = _BSTree$recursiveSearch32[0]; 82 | var ancestors = _BSTree$recursiveSearch32[1]; 83 | 84 | if (!this.size || key === undefined || !node) { 85 | return this.clone(); 86 | } else if (node) { 87 | return BSTree.removeFound(this.comparator, node, ancestors); 88 | } 89 | } 90 | }, { 91 | key: 'find', 92 | 93 | /** 94 | * Get the node with the matching key in tree in O(log n). 95 | * @param {[*]} key [the key of the node to find] 96 | * @return {[BSTNode|null]} [found node, null if key not found] 97 | */ 98 | value: function find(key) { 99 | return BSTree.recursiveSearch(this.comparator, this.root, key)[0]; 100 | } 101 | }, { 102 | key: 'get', 103 | 104 | /** 105 | * Get the value of the node with the matching key in tree in O(log n). 106 | * @param {[*]} key [the key of the value to get] 107 | * @return {[*]} [value of found node, null if key not found] 108 | */ 109 | value: function get(key) { 110 | var _BSTree$recursiveSearch4 = BSTree.recursiveSearch(this.comparator, this.root, key); 111 | 112 | var _BSTree$recursiveSearch42 = _slicedToArray(_BSTree$recursiveSearch4, 1); 113 | 114 | var search = _BSTree$recursiveSearch42[0]; 115 | 116 | return !search ? null : search.value; 117 | } 118 | }, { 119 | key: 'contains', 120 | 121 | /** 122 | * Check if there is a node with the matching value in tree in O(n). 123 | * @param {[*]} value [the value of the node for which to search] 124 | * @return {[Boolean]} [true if found, false if not found] 125 | */ 126 | value: function contains(value) { 127 | return this.values.indexOf(value) > -1; 128 | } 129 | }, { 130 | key: 'forEach', 131 | 132 | /** 133 | * Apply the callback to each node in the tree, in-order. 134 | * @param {[Function]} callback [recieves a BSTNode as input] 135 | * @return {[undefined]} [side-effect function] 136 | */ 137 | value: function forEach(callback) { 138 | BSTree.traverseInOrder(this.root, callback); 139 | } 140 | }, { 141 | key: 'insertAll', 142 | 143 | /** 144 | * Returns a new tree with the list's key-value pairs inserted. 145 | * @param {[Array]} listToInsert [an array of key-value tuples to insert] 146 | * @return {[BSTree]} [new BST with the all the key-value pairs inserted] 147 | */ 148 | value: function insertAll() { 149 | var listToInsert = arguments[0] === undefined ? [] : arguments[0]; 150 | 151 | var resultTree = this; 152 | listToInsert.forEach(function (pair) { 153 | resultTree = resultTree.insert(pair[0], pair[1]); 154 | }); 155 | return resultTree; 156 | } 157 | }, { 158 | key: 'clone', 159 | 160 | /** 161 | * Clone the current tree. 162 | * @return {[BSTree]} [new BST clone of current tree] 163 | */ 164 | value: function clone() { 165 | return new BSTree(this.comparator, this.root); 166 | } 167 | }, { 168 | key: 'size', 169 | 170 | /** 171 | * Get the number of nodes in the tree 172 | * @return {[Number]} [count of all nodes] 173 | */ 174 | get: function () { 175 | return this._count; 176 | } 177 | }, { 178 | key: 'comparator', 179 | 180 | /** 181 | * Get the key comparator function of the tree 182 | * @return {[Function]} [custom comparator, default if no custom comparator] 183 | */ 184 | get: function () { 185 | return this._comparator; 186 | } 187 | }, { 188 | key: 'root', 189 | 190 | /** 191 | * Get the first node in tree 192 | * @return {[BSTNode|null]} [root node, null if tree is empty] 193 | */ 194 | get: function () { 195 | return this._root; 196 | } 197 | }, { 198 | key: 'min', 199 | 200 | /** 201 | * Get the node with the smallest key in tree in O(log n). 202 | * @return {[BSTNode|null]} [min node, null if tree is empty] 203 | */ 204 | get: function () { 205 | return BSTree.traverseSide('left', this); 206 | } 207 | }, { 208 | key: 'max', 209 | 210 | /** 211 | * Get the node with the largest key in tree in O(log n). 212 | * @return {[BSTNode|null]} [max node, null if tree is empty] 213 | */ 214 | get: function () { 215 | return BSTree.traverseSide('right', this); 216 | } 217 | }, { 218 | key: 'keys', 219 | 220 | /** 221 | * Get all of the keys in tree in an ordered array in O(n). 222 | * @return {[Array]} [all the keys in the tree, ordered based on comparator] 223 | */ 224 | get: function () { 225 | var keys = []; 226 | this.forEach(function (node) { 227 | return keys.push(node.key); 228 | }); 229 | return keys; 230 | } 231 | }, { 232 | key: 'values', 233 | 234 | /** 235 | * Get all of the values in tree in a key-ordered array in O(n). 236 | * @return {[Array]} [all the values in the tree, ordered based on key comparison] 237 | */ 238 | get: function () { 239 | var values = []; 240 | this.forEach(function (node) { 241 | return values.push(node.value); 242 | }); 243 | return values; 244 | } 245 | }], [{ 246 | key: 'setComparator', 247 | 248 | /** 249 | * Returns the given comparator if acceptable, or the default comparator function. 250 | * @param {[Function]} comparator [must return {1, 0, -1} when comparing two inputs] 251 | * @return {[Function]} [custom comparator if given, default comparator otherwise] 252 | */ 253 | value: function setComparator(comparator) { 254 | var isComparator = !!comparator && typeof comparator === 'function'; 255 | return isComparator ? comparator : BSTree.defaultComp; 256 | } 257 | }, { 258 | key: 'defaultComp', 259 | 260 | /** 261 | * Returns 1, 0, or -1 based on default comparison criteria. 262 | * @param {[*]} keyA [the first key for comparison] 263 | * @param {[*]} keyB [the second key for comparison] 264 | * @return {[Number]} [-1 if keyA is smaller, 1 if keyA is bigger, 0 if the same] 265 | */ 266 | value: function defaultComp(keyA, keyB) { 267 | if (keyA < keyB) return -1;else if (keyA > keyB) return 1;else return 0; 268 | } 269 | }, { 270 | key: 'isBSTNode', 271 | 272 | /** 273 | * Checks if a given input is a BSTNode. 274 | * @param {[*]} maybe [entity to check for BSTNode-ness] 275 | * @return {[Boolean]} [true if maybe is a BSTNode, false otherwise] 276 | */ 277 | value: function isBSTNode(maybe) { 278 | return !!maybe && maybe.constructor === BSTNode; 279 | } 280 | }, { 281 | key: 'cloneNode', 282 | 283 | /** 284 | * Clone the input BSTNode. 285 | * @param {[BSTNode]} node [node to clone] 286 | * @return {[BSTNode]} [new BSTNode clone of input node] 287 | */ 288 | value: function cloneNode(node) { 289 | return new BSTNode(node.key, node.value, node.left, node.right, node.id); 290 | } 291 | }, { 292 | key: 'recount', 293 | 294 | /** 295 | * Returns the count of nodes present in _root input. 296 | * @param {[BSTNode]} _root [the root to recount] 297 | * @return {[Number]} [count of nodes in _root] 298 | */ 299 | value: function recount(_root) { 300 | var count = 0; 301 | BSTree.traverseInOrder(_root, function () { 302 | return count++; 303 | }); 304 | return count; 305 | } 306 | }, { 307 | key: 'findInOrderPredecessor', 308 | 309 | /** 310 | * Returns the ancestor nodes and in-order predecessor of the input node. 311 | * @param {[BSTNode]} leftChild [node from which to start the search for IOP] 312 | * @return {[Array]} [tuple containing a stack of ancestors and the IOP] 313 | */ 314 | value: function findInOrderPredecessor(leftChild) { 315 | var currentIop = leftChild, 316 | ancestors = []; 317 | while (currentIop.right) { 318 | ancestors.push(['_right', currentIop]); 319 | currentIop = currentIop.right; 320 | } 321 | return [ancestors, currentIop]; 322 | } 323 | }, { 324 | key: 'traverseInOrder', 325 | 326 | /** 327 | * Apply the callback to each node, in-order. 328 | * Recursive traversal, static version of #forEach 329 | * @param {[BSTNode]} node [the root node from which to start traversal] 330 | * @param {[Function]} callback [recieves a BSTNode as input] 331 | * @return {[undefined]} [side-effect function] 332 | */ 333 | value: function traverseInOrder(node, cb) { 334 | if (!node) return; 335 | var left = node.left, 336 | right = node.right; 337 | if (left) BSTree.traverseInOrder(left, cb); 338 | cb(node); 339 | if (right) BSTree.traverseInOrder(right, cb); 340 | } 341 | }, { 342 | key: 'traverseSide', 343 | 344 | /** 345 | * Returns the leaf BSTNode furthest down a given side of tree in O(log n). 346 | * @return {[BSTNode|null]} [max or min node, null if tree is empty] 347 | */ 348 | value: function traverseSide(side, tree) { 349 | var currentRoot = tree.root; 350 | if (!currentRoot) return null; 351 | var nextNode = currentRoot[side]; 352 | while (nextNode) { 353 | currentRoot = nextNode; 354 | nextNode = nextNode[side]; 355 | } 356 | return currentRoot; 357 | } 358 | }, { 359 | key: 'recursiveSearch', 360 | 361 | /** 362 | * Returns tuple of the found node and a stack of ancestor nodes. 363 | * Generic O(log n) recursive search of BSTree. 364 | * @param {[Function]} comparator [must return {1, 0, -1} when comparing two inputs] 365 | * @param {[BSTNode]} node [node from which to start the search] 366 | * @param {[*]} key [the key used for search] 367 | * @param {[Array]} ancestorStack [stack of tuples containing ancestor side and ancestor node] 368 | * @return {[Array]} [tuple containing null or the found node, and a stack of ancestors] 369 | */ 370 | value: function recursiveSearch(comparator, node, key) { 371 | var ancestorStack = arguments[3] === undefined ? [] : arguments[3]; 372 | 373 | if (!node) return [null, ancestorStack]; 374 | var comparisons = comparator(node.key, key); 375 | if (comparisons === -1) { 376 | ancestorStack.push(['_right', node]); 377 | return BSTree.recursiveSearch(comparator, node.right, key, ancestorStack); 378 | } else if (comparisons === 1) { 379 | ancestorStack.push(['_left', node]); 380 | return BSTree.recursiveSearch(comparator, node.left, key, ancestorStack); 381 | } else { 382 | return [node, ancestorStack]; 383 | } 384 | } 385 | }, { 386 | key: 'removeNoChildren', 387 | 388 | /** 389 | * Returns new root node with input node removed. 390 | * Input node must have no children. 391 | * @param {[BSTNode]} node [node from which to start the removal] 392 | * @param {[Array]} ancestorStack [stack of tuples containing ancestor side and ancestor node] 393 | * @return {[BSTNode]} [new root node constructed from tree with input node removed] 394 | */ 395 | value: function removeNoChildren(node, ancestors) { 396 | if (ancestors.length) { 397 | var _ancestors$pop = ancestors.pop(); 398 | 399 | var _ancestors$pop2 = _slicedToArray(_ancestors$pop, 2); 400 | 401 | var childSide = _ancestors$pop2[0]; 402 | var parentNode = _ancestors$pop2[1]; 403 | 404 | node = new BSTNode(parentNode._store.set(childSide, null)); 405 | } 406 | return BSTree.constructFromLeaf(node, ancestors); 407 | } 408 | }, { 409 | key: 'removeOneChild', 410 | 411 | /** 412 | * Returns new root node with input node removed. 413 | * Input node must have exactly one child. 414 | * @param {[BSTNode]} node [node from which to start the removal] 415 | * @param {[Array]} ancestorStack [stack of tuples containing ancestor side and ancestor node] 416 | * @return {[BSTNode]} [new root node with input node removed and children repositioned] 417 | */ 418 | value: function removeOneChild(node, ancestors) { 419 | var childNode = node.children[0][1]; 420 | if (!ancestors.length) { 421 | return childNode; 422 | } else { 423 | var _ancestors$pop3 = ancestors.pop(); 424 | 425 | var _ancestors$pop32 = _slicedToArray(_ancestors$pop3, 2); 426 | 427 | var childSide = _ancestors$pop32[0]; 428 | var parentNode = _ancestors$pop32[1]; 429 | var leaf = new BSTNode(parentNode._store.set(childSide, childNode)); 430 | return BSTree.constructFromLeaf(leaf, ancestors); 431 | } 432 | } 433 | }, { 434 | key: 'removeTwoChildren', 435 | 436 | /** 437 | * Returns new root node with input node removed. 438 | * Input node must have exactly two children. 439 | * @param {[Function]} comparator [must return {1, 0, -1} when comparing two inputs] 440 | * @param {[BSTNode]} node [node from which to start the removal] 441 | * @param {[Array]} ancestorStack [stack of tuples containing ancestor side and ancestor node] 442 | * @return {[BSTNode]} [new root node with input node removed and children repositioned] 443 | */ 444 | value: function removeTwoChildren(comparator, node, ancestors) { 445 | var _BSTree$findInOrderPredecessor = BSTree.findInOrderPredecessor(node.left); 446 | 447 | var _BSTree$findInOrderPredecessor2 = _slicedToArray(_BSTree$findInOrderPredecessor, 2); 448 | 449 | var rightAncestors = _BSTree$findInOrderPredecessor2[0]; 450 | var iop = _BSTree$findInOrderPredecessor2[1]; 451 | 452 | var iopReplacementStore = iop.store.withMutations(function (_store) { 453 | _store.set('_key', node.key).set('_value', node.value).set('_id', node.id); 454 | }); 455 | var targetReplacementStore = node.store.withMutations(function (_store) { 456 | _store.set('_key', iop.key).set('_value', iop.value).set('_id', iop.id).set('_left', new BSTNode(iopReplacementStore)); 457 | }); 458 | var newIopNode = new BSTNode(targetReplacementStore); 459 | ancestors = ancestors.concat([['_left', newIopNode]], rightAncestors); 460 | return BSTree.removeFound(comparator, newIopNode.left, ancestors); 461 | } 462 | }, { 463 | key: 'removeFound', 464 | 465 | /** 466 | * Returns new root node with input node removed. 467 | * Input node can have any number of children. Dispatches to correct removal method. 468 | * @param {[Function]} comparator [must return {1, 0, -1} when comparing two inputs] 469 | * @param {[BSTNode]} node [node from which to start the removal] 470 | * @param {[Array]} ancestorStack [stack of tuples containing ancestor side and ancestor node] 471 | * @return {[BSTNode]} [new root node with input node removed and children repositioned] 472 | */ 473 | value: function removeFound(comparator, node, ancestors) { 474 | switch (node.children.length) { 475 | case 1: 476 | return new BSTree(comparator, BSTree.removeOneChild(node, ancestors)); 477 | break; 478 | case 2: 479 | return BSTree.removeTwoChildren(comparator, node, ancestors); 480 | break; 481 | default: 482 | return new BSTree(comparator, BSTree.removeNoChildren(node, ancestors)); 483 | break; 484 | } 485 | } 486 | }, { 487 | key: 'constructFromLeaf', 488 | 489 | /** 490 | * Returns new root node reconstructed from a leaf node and ancestors. 491 | * @param {[BSTNode]} node [leaf node from which to start the construction] 492 | * @param {[Array]} ancestorStack [stack of tuples containing ancestor side and ancestor node] 493 | * @return {[BSTNode]} [new root node reconstructed from leaf and ancestors stack] 494 | */ 495 | value: function constructFromLeaf(node, ancestors) { 496 | while (ancestors.length) { 497 | var _ancestors$pop4 = ancestors.pop(); 498 | 499 | var _ancestors$pop42 = _slicedToArray(_ancestors$pop4, 2); 500 | 501 | var childSide = _ancestors$pop42[0]; 502 | var parentNode = _ancestors$pop42[1]; 503 | 504 | node = new BSTNode(parentNode._store.set(childSide, node)); 505 | } 506 | return node; 507 | } 508 | }]); 509 | 510 | return BSTree; 511 | })(); 512 | 513 | exports['default'] = BSTree; 514 | module.exports = exports['default']; 515 | -------------------------------------------------------------------------------- /__tests__/integration/binary_trees/BSTree.es6: -------------------------------------------------------------------------------- 1 | jest.autoMockOff(); 2 | const IM = require('immutable'); 3 | const BSTree = require('../../../src/binary_trees/BSTree'); 4 | const BSTNode = require('../../../src/binary_trees/BSTNode'); 5 | 6 | describe('BSTree', () => { 7 | describe('Instantiation', () => { 8 | let bst = new BSTree(), 9 | _node = new BSTNode(0, 'first node value', null, null, 1), 10 | bstWithNode = new BSTree(null, _node); 11 | 12 | it('instantiates empty, checks size', () => { 13 | expect(bst.size).toBe(0); 14 | }); 15 | 16 | it('instantiates with a root node, checks size', () => { 17 | expect(bstWithNode.size).toBe(1); 18 | }); 19 | 20 | it('instantiates with a default comparator', () => { 21 | expect(bst.comparator).toBeDefined(); 22 | expect(typeof bst.comparator).toBe('function'); 23 | }); 24 | 25 | it('instantiates with a custom comparator', () => { 26 | let comp = () => 0, compBST = new BSTree(comp); 27 | expect(compBST.comparator).toBe(comp); 28 | expect(typeof compBST.comparator).toBe('function'); 29 | }); 30 | 31 | }); 32 | 33 | describe('Instance Methods', () => { 34 | describe('#insert', () => { 35 | describe('empty trees', () => { 36 | let bst = new BSTree(); 37 | 38 | it('returns clone of current tree when no args', () => { 39 | expect(bst.insert()).toEqual(bst); 40 | }); 41 | 42 | it('returns new tree for simple insert into empty tree', () => { 43 | expect(bst.insert(2, 'val')).toEqual(jasmine.any(BSTree)); 44 | }); 45 | 46 | it('returns a tree with the correct root node', () => { 47 | let _node = bst.insert(2, 'val').root; 48 | expect(_node.key).toBe(2); 49 | expect(_node.value).toBe('val'); 50 | }); 51 | 52 | }) 53 | 54 | describe('nonempty trees', () => { 55 | let rootNode = new BSTNode(1, 'hi', null, null, 1), 56 | bst = new BSTree(null, rootNode); 57 | 58 | it('returns a new tree', () => { 59 | expect(bst.insert(2, 'a')).toEqual(jasmine.any(BSTree)); 60 | }); 61 | 62 | describe('root node', () => { 63 | let newTree1 = bst.insert(8, 'string'); 64 | it('has correct id', () => { 65 | expect(newTree1.root.id).toBe(1); 66 | }); 67 | 68 | it('has correct key', () => { 69 | expect(newTree1.root.key).toBe(1); 70 | }); 71 | 72 | it('has correct value', () => { 73 | expect(newTree1.root.value).toBe('hi'); 74 | }); 75 | 76 | it('has correct right child', () => { 77 | expect(newTree1.root.right).toEqual(new BSTNode(8, 'string', null, null, 2)); 78 | }); 79 | 80 | it('has correct left child', () => { 81 | expect(newTree1.root.left).toBeNull(); 82 | }); 83 | 84 | }); 85 | 86 | }); 87 | 88 | describe('chained insertion', () => { 89 | let rootNode = new BSTNode(50, 'hi', null, null, 1), 90 | bst = new BSTree(undefined, rootNode), 91 | chainedResult = bst.insert(25, 'a').insert(75, 'b').insert(95, 'c'); 92 | 93 | describe('resulting tree', () => { 94 | it('returns a new tree', () => { 95 | expect(chainedResult).toEqual(jasmine.any(BSTree)); 96 | }); 97 | 98 | it('has correct max node', () => { 99 | expect(chainedResult.max.key).toBe(95); 100 | }); 101 | 102 | it('has correct min node', () => { 103 | expect(chainedResult.min.key).toBe(25); 104 | }); 105 | 106 | }); 107 | 108 | describe('root node of chained tree', () => { 109 | it('has the correct id', () => { 110 | expect(chainedResult.root.id).toBe(1); 111 | }); 112 | 113 | it('has the correct key', () => { 114 | expect(chainedResult.root.key).toBe(50); 115 | }); 116 | 117 | it('has the correct value', () => { 118 | expect(chainedResult.root.value).toBe('hi'); 119 | }); 120 | 121 | it('has the correct left and right nodes', () => { 122 | expect(chainedResult.root.left.key).toBe(25); 123 | expect(chainedResult.root.right.key).toBe(75); 124 | }); 125 | 126 | }); 127 | 128 | }); 129 | 130 | describe('insertion operation immutability', () => { 131 | it('does not mutate tree', () => { 132 | let bst = new BSTree(); 133 | bst.insert(1, 'a'); 134 | expect(bst.size).toBe(0); 135 | }); 136 | 137 | it('does not mutate nodes', () => { 138 | let node = new BSTNode(1, 'a', null, null, 1), 139 | bst = new BSTree(null, node); 140 | bst.insert(2, 'b'); 141 | expect(node.right).toBeNull(); 142 | }); 143 | 144 | }); 145 | 146 | }); 147 | 148 | describe('#remove', () => { 149 | describe('empty trees', () => { 150 | let bst = new BSTree(); 151 | 152 | it('returns current tree when no args', () => { 153 | expect(bst.remove()).toEqual(bst); 154 | }); 155 | 156 | it('returns current tree when args', () => { 157 | expect(bst.remove(2)).toEqual(bst); 158 | }); 159 | 160 | }) 161 | 162 | describe('nonempty trees', () => { 163 | it('returns a new tree', () => { 164 | let rootNode = new BSTNode(1, 'hi', null, null, 1), 165 | bst = new BSTree(null, rootNode); 166 | expect(bst.remove(1)).toEqual(jasmine.any(BSTree)); 167 | }); 168 | 169 | describe('root node', () => { 170 | let minNode = new BSTNode(0, 'min', null, null, 4), 171 | rootRight = new BSTNode(75, 'max', null, null, 2), 172 | rootLeft = new BSTNode(25, 'c', minNode, null, 3), 173 | rootNode = new BSTNode(50, 'a', rootLeft, rootRight, 1), 174 | bst = new BSTree(null, rootNode), 175 | newTreeRoot = bst.remove(25).root; 176 | 177 | it('has correct left child', () => { 178 | expect(newTreeRoot.left).toBe(minNode); 179 | }); 180 | 181 | 182 | it('has correct right child', () => { 183 | expect(newTreeRoot.right).toBe(rootRight); 184 | }); 185 | 186 | }); 187 | 188 | }); 189 | 190 | describe('target node with no children', () => { 191 | let target = new BSTNode(1, 'remove me', null, null, 1), 192 | parent = new BSTNode(99, 'reset my children', target, null, 2), 193 | bst = new BSTree(null, parent); 194 | 195 | it('sets parent node side to null', () => { 196 | expect(bst.remove(1).root.left).toBeNull(); 197 | }); 198 | 199 | }); 200 | 201 | describe('target node with one child', () => { 202 | describe('no ancestor', () => { 203 | let childL = new BSTNode(0, 'reset my position to root', null, null, 1), 204 | childR = new BSTNode(99, 'reset my position to root', null, null, 1), 205 | targetLeft = new BSTNode(1, 'remove me', childL, null, 2), 206 | targetRight = new BSTNode(1, 'remove me', null, childR, 2), 207 | bstLeft = new BSTree(null, targetLeft), 208 | bstRight = new BSTree(null, targetRight); 209 | 210 | it('left child', () => { 211 | expect(bstLeft.remove(1).root).toEqual(childL); 212 | }); 213 | 214 | it('right child', () => { 215 | expect(bstRight.remove(1).root).toEqual(childR); 216 | }); 217 | 218 | }); 219 | 220 | describe('ancestor', () => { 221 | let child = new BSTNode(-99, 'b', null, null, 3), 222 | target = new BSTNode(0, 'remove me', child, null, 2), 223 | _root = new BSTNode(1, 'a', target, null, 1), 224 | bst = new BSTree(null, _root); 225 | 226 | it('promotes child and updates child of ancestor', () => { 227 | expect(bst.remove(0).root.left).toEqual(child); 228 | }); 229 | 230 | }); 231 | 232 | }); 233 | 234 | describe('target node with two children', () => { 235 | 236 | describe('left child of target has 0 right children', () => { 237 | let childLeft = new BSTNode(1, 'reset my position', null, null, 2), 238 | childRight = new BSTNode(9, 'reset my position', null, null, 3), 239 | target = new BSTNode(5, 'remove me', childLeft, childRight, 1), 240 | bst = new BSTree(null, target); 241 | 242 | it('repositions left child', () => { 243 | // assuming rearrangement based on in-order predecessor swap 244 | let _root = bst.remove(5).root; 245 | expect(_root.key).toBe(1); 246 | expect(_root.left).toBeNull(); 247 | }); 248 | 249 | it('repositions right child', () => { 250 | // assuming rearrangement based on in-order predecessor swap 251 | expect(bst.remove(5).root.right.key).toBe(9); 252 | }); 253 | 254 | }); 255 | 256 | describe('left child of target has > 0 right children', () => { 257 | let inOrderPredChild = new BSTNode(3, 'swap me with target', null, null, 4), 258 | childLeft = new BSTNode(1, 'reset my position', null, inOrderPredChild, 2), 259 | childRight = new BSTNode(9, 'reset my position', null, null, 3), 260 | target = new BSTNode(5, 'remove me', childLeft, childRight, 1), 261 | bst = new BSTree(null, target), 262 | removalResult = bst.remove(5); 263 | 264 | it('sets root to in-order predecessor of target', () => { 265 | expect(removalResult.root.key).toBe(3); 266 | }); 267 | 268 | it('resets left child of new root', () => { 269 | let left = removalResult.root.left; 270 | expect(left.key).toBe(1); 271 | expect(left.right).toBeNull(); 272 | }); 273 | 274 | it('resets right child of new root', () => { 275 | expect(removalResult.root.right.key).toBe(9); 276 | }); 277 | 278 | }); 279 | 280 | }); 281 | 282 | describe('chained removal', () => { 283 | let childRightRight = new BSTNode(100, 'd', null, null, 4), 284 | childRightLeft = new BSTNode(60, 'e', null, null, 5), 285 | childRight = new BSTNode(75, 'c', childRightLeft, childRightRight, 3), 286 | childLeftRight = new BSTNode(35, 'f', null, null, 6), 287 | childLeftLeft = new BSTNode(0, 'g', null, null, 7), 288 | childLeft = new BSTNode(25, 'b', childLeftLeft, childLeftRight, 2), 289 | rootNode = new BSTNode(50, 'root', childLeft, childRight, 1), 290 | bst = new BSTree(null, rootNode), 291 | chainedResult = bst.remove(50).remove(75).remove(35); 292 | 293 | describe('resulting tree', () => { 294 | it('returns a new tree', () => { 295 | expect(chainedResult).toEqual(jasmine.any(BSTree)); 296 | }); 297 | 298 | it('has correct max node', () => { 299 | expect(chainedResult.max.key).toBe(100); 300 | }); 301 | 302 | it('has correct min node', () => { 303 | expect(chainedResult.min.key).toBe(0); 304 | }); 305 | 306 | }); 307 | 308 | describe('root node of chained tree', () => { 309 | it('has the correct id', () => { 310 | expect(chainedResult.root.id).toBe(2); 311 | }); 312 | 313 | it('has the correct key', () => { 314 | expect(chainedResult.root.key).toBe(25); 315 | }); 316 | 317 | it('has the correct value', () => { 318 | expect(chainedResult.root.value).toBe('b'); 319 | }); 320 | 321 | it('has the correct left and right nodes', () => { 322 | expect(chainedResult.root.left).toBe(childLeftLeft); 323 | expect(chainedResult.root.right.key).toBe(60); 324 | expect(chainedResult.root.right.left).toBeNull(); 325 | expect(chainedResult.root.right.right.key).toBe(100); 326 | }); 327 | 328 | }); 329 | 330 | }); 331 | 332 | describe('removal operation immutability', () => { 333 | it('does not mutate tree', () => { 334 | let rootNode = new BSTNode(50, 'root', null, null, 1), 335 | bst = new BSTree(null, rootNode); 336 | bst.remove(50); 337 | expect(bst.size).toBe(1); 338 | }); 339 | 340 | it('does not mutate nodes', () => { 341 | let target = new BSTNode(2, 'b', null, null, 2) 342 | node = new BSTNode(1, 'a', null, target, 1), 343 | bst = new BSTree(null, node); 344 | bst.remove(2); 345 | expect(node.right).toBe(target); 346 | }); 347 | 348 | }); 349 | 350 | }); 351 | 352 | describe('#find', () => { 353 | describe('key not present in tree', () => { 354 | it('returns null for empty tree', () => { 355 | expect((new BSTree()).find(1)).toBeNull(); 356 | }); 357 | 358 | it('returns null for nonempty tree', () => { 359 | let bst = new BSTree(null, new BSTNode(1, 'a', null, null, 1)); 360 | expect(bst.find(20)).toBeNull(); 361 | }); 362 | 363 | }); 364 | 365 | describe('key present in tree', () => { 366 | it('returns associated node, shallow tree', () => { 367 | let node = new BSTNode(1, 'a', null, null, 1), 368 | bst = new BSTree(null, node); 369 | expect(bst.find(1)).toEqual(node); 370 | }); 371 | 372 | it('returns associated node, deep tree', () => { 373 | let maxNode = new BSTNode(100, 'max', null, null, 4), 374 | rootRight = new BSTNode(75, 'b', null, maxNode, 2), 375 | rootLeft = new BSTNode(25, 'c', null, null, 3), 376 | rootNode = new BSTNode(50, 'a', rootLeft, rootRight, 1), 377 | bst = new BSTree(null, rootNode); 378 | expect(bst.find(100)).toEqual(maxNode); 379 | }); 380 | 381 | }); 382 | 383 | }); 384 | 385 | describe('#get', () => { 386 | describe('key not present in tree', () => { 387 | it('returns null for empty tree', () => { 388 | expect((new BSTree()).get(1)).toBeNull(); 389 | }); 390 | 391 | it('returns null for nonempty tree', () => { 392 | let bst = new BSTree(null, new BSTNode(1, 'a', null, null, 1)); 393 | expect(bst.get(20)).toBeNull(); 394 | }); 395 | 396 | }); 397 | 398 | describe('key present in tree', () => { 399 | it('returns associated node value, shallow tree', () => { 400 | let node = new BSTNode(1, 'a', null, null, 1), 401 | bst = new BSTree(null, node); 402 | expect(bst.get(1)).toBe('a'); 403 | }); 404 | 405 | it('returns associated node value, deep tree', () => { 406 | let maxNode = new BSTNode(100, 'max', null, null, 4), 407 | rootRight = new BSTNode(75, 'b', null, maxNode, 2), 408 | rootLeft = new BSTNode(25, 'c', null, null, 3), 409 | rootNode = new BSTNode(50, 'a', rootLeft, rootRight, 1), 410 | bst = new BSTree(null, rootNode); 411 | expect(bst.get(100)).toBe('max'); 412 | }); 413 | }); 414 | }); 415 | 416 | describe('#contains', () => { 417 | describe('value not present in tree', () => { 418 | it('returns false for empty tree', () => { 419 | expect((new BSTree()).contains(1)).toBe(false); 420 | }); 421 | 422 | it('returns false for nonempty tree', () => { 423 | let bst = new BSTree(null, new BSTNode(1, 'a', null, null, 1)); 424 | expect(bst.contains('b')).toBe(false); 425 | }); 426 | 427 | }); 428 | 429 | describe('value present in tree', () => { 430 | it('returns true, shallow tree', () => { 431 | let node = new BSTNode(1, 'a', null, null, 1), 432 | bst = new BSTree(null, node); 433 | expect(bst.contains('a')).toBe(true); 434 | }); 435 | 436 | it('returns true, deep tree', () => { 437 | let maxNode = new BSTNode(100, 'max', null, null, 4), 438 | rootRight = new BSTNode(75, 'b', null, maxNode, 2), 439 | rootLeft = new BSTNode(25, 'c', null, null, 3), 440 | rootNode = new BSTNode(50, 'a', rootLeft, rootRight, 1), 441 | bst = new BSTree(null, rootNode); 442 | expect(bst.contains('max')).toBe(true); 443 | }); 444 | 445 | }); 446 | 447 | }); 448 | 449 | describe('#forEach', () => { 450 | it('does not execute callback for empty tree', () => { 451 | let bstEmpty = new BSTree(), 452 | result = []; 453 | bstEmpty.forEach(node => result.push(node.key)); 454 | expect(result.length).toBe(0); 455 | }); 456 | 457 | it('executes callback on each node in order for nonempty tree', () => { 458 | let maxNode = new BSTNode(100, 'max', null, null, 4), 459 | rootRight = new BSTNode(75, 'b', null, maxNode, 2), 460 | rootLeft = new BSTNode(25, 'c', null, null, 3), 461 | rootNode = new BSTNode(50, 'a', rootLeft, rootRight, 1), 462 | bst = new BSTree(null, rootNode), 463 | result = []; 464 | bst.forEach(node => result.push(node.key)); 465 | expect(result.length).toBe(4); 466 | expect(result).toEqual([25, 50, 75, 100]); 467 | }); 468 | 469 | }); 470 | 471 | describe('#insertAll', () => { 472 | let pairs = [[1, 'a'], [2, 'b'], [5, 'root'], [100, 'max']], 473 | bst = new BSTree(), 474 | bstFull = bst.insertAll(pairs); 475 | 476 | it('adds nodes to tree based on input order', () => { 477 | expect(bstFull.root.key).toBe(1); 478 | expect(bstFull.max.key).toBe(100); 479 | expect(bstFull.min.key).toBe(1); 480 | }); 481 | 482 | }); 483 | 484 | }); 485 | 486 | describe('Basic Getters', () => { 487 | describe('get size', () => { 488 | it('returns current number of nodes in tree', () => { 489 | let bst1 = new BSTree(), 490 | bst2 = bst1.insert(5, 'first value'), 491 | bst3 = bst2.insert(0, 'second value'), 492 | bst4 = bst3.insert(10, 'third value'); 493 | expect(bst1.size).toBe(0); 494 | expect(bst2.size).toBe(1); 495 | expect(bst3.size).toBe(2); 496 | expect(bst4.size).toBe(3); 497 | }); 498 | 499 | }); 500 | 501 | describe('get comparator', () => { 502 | it('returns default comparator, duck-check', () => { 503 | let bstDefault = (new BSTree()).comparator; 504 | expect(bstDefault(2, 1)).toBe(1); 505 | expect(bstDefault(1, 2)).toBe(-1); 506 | expect(bstDefault(1, 1)).toBe(0); 507 | }); 508 | 509 | it('returns custom comparator, duck-check', () => { 510 | let bstCustom = (new BSTree(() => 0)).comparator; 511 | expect(bstCustom(2, 1)).toBe(0); 512 | expect(bstCustom(1, 2)).toBe(0); 513 | expect(bstCustom('a', 'hi')).toBe(0); 514 | }); 515 | 516 | }); 517 | 518 | describe('get root', () => { 519 | it('returns null for empty tree', () => { 520 | expect((new BSTree()).root).toBeNull(); 521 | }); 522 | 523 | it('returns root node for nonempty tree', () => { 524 | let rootNode = new BSTNode(1, 'a', null, null, 1); 525 | expect((new BSTree(null, rootNode)).root).toEqual(rootNode); 526 | }); 527 | 528 | }); 529 | 530 | describe('get min', () => { 531 | it('returns null for empty tree', () => { 532 | expect((new BSTree()).min).toBeNull(); 533 | }); 534 | 535 | it('returns node with min key for nonempty tree', () => { 536 | let minNode = new BSTNode(0, 'min', null, null, 4), 537 | rootRight = new BSTNode(75, 'b', null, null, 2), 538 | rootLeft = new BSTNode(25, 'c', minNode, null, 3), 539 | rootNode = new BSTNode(50, 'a', rootLeft, rootRight, 1), 540 | bst = new BSTree(null, rootNode); 541 | expect(bst.min).toEqual(minNode); 542 | }); 543 | 544 | }); 545 | 546 | describe('get max', () => { 547 | it('returns null for empty tree', () => { 548 | expect((new BSTree()).max).toBeNull(); 549 | }); 550 | 551 | it('returns node with max key for nonempty tree', () => { 552 | let maxNode = new BSTNode(100, 'max', null, null, 4), 553 | rootRight = new BSTNode(75, 'b', null, maxNode, 2), 554 | rootLeft = new BSTNode(25, 'c', null, null, 3), 555 | rootNode = new BSTNode(50, 'a', rootLeft, rootRight, 1), 556 | bst = new BSTree(null, rootNode); 557 | expect(bst.max).toEqual(maxNode); 558 | }); 559 | }); 560 | 561 | describe('get keys', () => { 562 | it('returns empty array for empty tree', () => { 563 | expect((new BSTree()).keys).toEqual([]); 564 | }); 565 | 566 | it('returns in-order array of all node keys for nonempty tree', () => { 567 | let maxNode = new BSTNode(100, 'max', null, null, 4), 568 | rootRight = new BSTNode(75, 'b', null, maxNode, 2), 569 | rootLeft = new BSTNode(25, 'c', null, null, 3), 570 | rootNode = new BSTNode(50, 'a', rootLeft, rootRight, 1), 571 | bst = new BSTree(null, rootNode); 572 | expect(bst.keys).toEqual([25, 50, 75, 100]); 573 | }); 574 | }); 575 | 576 | describe('get values', () => { 577 | it('returns empty array for empty tree', () => { 578 | expect((new BSTree()).values).toEqual([]); 579 | }); 580 | 581 | it('returns in-order array of all node values for nonempty tree', () => { 582 | let maxNode = new BSTNode(100, 'max', null, null, 4), 583 | rootRight = new BSTNode(75, 'b', null, maxNode, 2), 584 | rootLeft = new BSTNode(25, 'c', null, null, 3), 585 | rootNode = new BSTNode(50, 'a', rootLeft, rootRight, 1), 586 | bst = new BSTree(null, rootNode); 587 | expect(bst.values).toEqual(['c', 'a', 'b', 'max']); 588 | }); 589 | }); 590 | }); 591 | }); 592 | -------------------------------------------------------------------------------- /STYLE_GUIDE.md: -------------------------------------------------------------------------------- 1 | # JavaScript Style Guide 2 | 3 | *From [Airbnb's Style Guide](https://github.com/airbnb/javascript)* 4 | 5 | ## Table of Contents 6 | 7 | 1. [Types](#types) 8 | 1. [Objects](#objects) 9 | 1. [Arrays](#arrays) 10 | 1. [Strings](#strings) 11 | 1. [Functions](#functions) 12 | 1. [Properties](#properties) 13 | 1. [Variables](#variables) 14 | 1. [Hoisting](#hoisting) 15 | 1. [Conditional Expressions & Equality](#conditional-expressions--equality) 16 | 1. [Blocks](#blocks) 17 | 1. [Comments](#comments) 18 | 1. [Whitespace](#whitespace) 19 | 1. [Commas](#commas) 20 | 1. [Semicolons](#semicolons) 21 | 1. [Type Casting & Coercion](#type-casting--coercion) 22 | 1. [Naming Conventions](#naming-conventions) 23 | 1. [Accessors](#accessors) 24 | 1. [Constructors](#constructors) 25 | 1. [Events](#events) 26 | 1. [Modules](#modules) 27 | 1. [jQuery](#jquery) 28 | 1. [ECMAScript 5 Compatibility](#ecmascript-5-compatibility) 29 | 1. [Testing](#testing) 30 | 31 | ## Types 32 | 33 | - **Primitives**: When you access a primitive type you work directly on its value 34 | 35 | + `string` 36 | + `number` 37 | + `boolean` 38 | + `null` 39 | + `undefined` 40 | 41 | ```javascript 42 | var foo = 1; 43 | var bar = foo; 44 | 45 | bar = 9; 46 | 47 | console.log(foo, bar); // => 1, 9 48 | ``` 49 | - **Complex**: When you access a complex type you work on a reference to its value 50 | 51 | + `object` 52 | + `array` 53 | + `function` 54 | 55 | ```javascript 56 | var foo = [1, 2]; 57 | var bar = foo; 58 | 59 | bar[0] = 9; 60 | 61 | console.log(foo[0], bar[0]); // => 9, 9 62 | ``` 63 | 64 | **[⬆ back to top](#table-of-contents)** 65 | 66 | ## Objects 67 | 68 | - Use the literal syntax for object creation. 69 | 70 | ```javascript 71 | // bad 72 | var item = new Object(); 73 | 74 | // good 75 | var item = {}; 76 | ``` 77 | 78 | - Don't use [reserved words](http://es5.github.io/#x7.6.1) as keys. It won't work in IE8. [More info](https://github.com/airbnb/javascript/issues/61) 79 | 80 | ```javascript 81 | // bad 82 | var superman = { 83 | default: { clark: 'kent' }, 84 | private: true 85 | }; 86 | 87 | // good 88 | var superman = { 89 | defaults: { clark: 'kent' }, 90 | hidden: true 91 | }; 92 | ``` 93 | 94 | - Use readable synonyms in place of reserved words. 95 | 96 | ```javascript 97 | // bad 98 | var superman = { 99 | class: 'alien' 100 | }; 101 | 102 | // bad 103 | var superman = { 104 | klass: 'alien' 105 | }; 106 | 107 | // good 108 | var superman = { 109 | type: 'alien' 110 | }; 111 | ``` 112 | 113 | **[⬆ back to top](#table-of-contents)** 114 | 115 | ## Arrays 116 | 117 | - Use the literal syntax for array creation 118 | 119 | ```javascript 120 | // bad 121 | var items = new Array(); 122 | 123 | // good 124 | var items = []; 125 | ``` 126 | 127 | - If you don't know array length use Array#push. 128 | 129 | ```javascript 130 | var someStack = []; 131 | 132 | 133 | // bad 134 | someStack[someStack.length] = 'abracadabra'; 135 | 136 | // good 137 | someStack.push('abracadabra'); 138 | ``` 139 | 140 | - When you need to copy an array use Array#slice. [jsPerf](http://jsperf.com/converting-arguments-to-an-array/7) 141 | 142 | ```javascript 143 | var len = items.length; 144 | var itemsCopy = []; 145 | var i; 146 | 147 | // bad 148 | for (i = 0; i < len; i++) { 149 | itemsCopy[i] = items[i]; 150 | } 151 | 152 | // good 153 | itemsCopy = items.slice(); 154 | ``` 155 | 156 | - To convert an array-like object to an array, use Array#slice. 157 | 158 | ```javascript 159 | function trigger() { 160 | var args = Array.prototype.slice.call(arguments); 161 | ... 162 | } 163 | ``` 164 | 165 | **[⬆ back to top](#table-of-contents)** 166 | 167 | 168 | ## Strings 169 | 170 | - Use single quotes `''` for strings 171 | 172 | ```javascript 173 | // bad 174 | var name = "Bob Parr"; 175 | 176 | // good 177 | var name = 'Bob Parr'; 178 | 179 | // bad 180 | var fullName = "Bob " + this.lastName; 181 | 182 | // good 183 | var fullName = 'Bob ' + this.lastName; 184 | ``` 185 | 186 | - Strings longer than 80 characters should be written across multiple lines using string concatenation. 187 | - Note: If overused, long strings with concatenation could impact performance. [jsPerf](http://jsperf.com/ya-string-concat) & [Discussion](https://github.com/airbnb/javascript/issues/40) 188 | 189 | ```javascript 190 | // bad 191 | var errorMessage = 'This is a super long error that was thrown because of Batman. When you stop to think about how Batman had anything to do with this, you would get nowhere fast.'; 192 | 193 | // bad 194 | var errorMessage = 'This is a super long error that was thrown because \ 195 | of Batman. When you stop to think about how Batman had anything to do \ 196 | with this, you would get nowhere \ 197 | fast.'; 198 | 199 | // good 200 | var errorMessage = 'This is a super long error that was thrown because ' + 201 | 'of Batman. When you stop to think about how Batman had anything to do ' + 202 | 'with this, you would get nowhere fast.'; 203 | ``` 204 | 205 | - When programmatically building up a string, use Array#join instead of string concatenation. Mostly for IE: [jsPerf](http://jsperf.com/string-vs-array-concat/2). 206 | 207 | ```javascript 208 | var items; 209 | var messages; 210 | var length; 211 | var i; 212 | 213 | messages = [{ 214 | state: 'success', 215 | message: 'This one worked.' 216 | }, { 217 | state: 'success', 218 | message: 'This one worked as well.' 219 | }, { 220 | state: 'error', 221 | message: 'This one did not work.' 222 | }]; 223 | 224 | length = messages.length; 225 | 226 | // bad 227 | function inbox(messages) { 228 | items = '