├── .gitignore ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── STYLE_GUIDE.md ├── __tests__ └── integration │ ├── binary_trees │ └── BSTree.es6 │ ├── heaps │ └── Heap.es6 │ └── lists │ ├── CLList.es6 │ └── LList.es6 ├── dist ├── BSTNode.js ├── BSTree.js ├── CLList.js ├── Heap.js ├── LList.js └── Persist.js ├── package.json └── src ├── binary_trees ├── BSTNode.es6 └── BSTree.es6 ├── heaps └── Heap.es6 └── lists ├── CLList.es6 └── LList.es6 /.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* -------------------------------------------------------------------------------- /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!** -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | [ ![Current Stable Release Version](https://img.shields.io/badge/version-0.0.1-blue.svg)](https://github.com/persistence-js/persist/releases) 6 | [ ![Codeship Status for PersistenceJS](https://img.shields.io/badge/build-passing-brightgreen.svg)](https://codeship.com/projects/86120/) 7 | [ ![Current Stable npm Release](https://img.shields.io/badge/npm-install%20persistence--js-lightgrey.svg)](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 | -------------------------------------------------------------------------------- /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 = ''; 235 | } 236 | 237 | // good 238 | function inbox(messages) { 239 | items = []; 240 | 241 | for (i = 0; i < length; i++) { 242 | items[i] = messages[i].message; 243 | } 244 | 245 | return ''; 246 | } 247 | ``` 248 | 249 | **[⬆ back to top](#table-of-contents)** 250 | 251 | 252 | ## Functions 253 | 254 | - Function expressions: 255 | 256 | ```javascript 257 | // anonymous function expression 258 | var anonymous = function() { 259 | return true; 260 | }; 261 | 262 | // named function expression 263 | var named = function named() { 264 | return true; 265 | }; 266 | 267 | // immediately-invoked function expression (IIFE) 268 | (function() { 269 | console.log('Welcome to the Internet. Please follow me.'); 270 | })(); 271 | ``` 272 | 273 | - Never declare a function in a non-function block (if, while, etc). Assign the function to a variable instead. Browsers will allow you to do it, but they all interpret it differently, which is bad news bears. 274 | - **Note:** ECMA-262 defines a `block` as a list of statements. A function declaration is not a statement. [Read ECMA-262's note on this issue](http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf#page=97). 275 | 276 | ```javascript 277 | // bad 278 | if (currentUser) { 279 | function test() { 280 | console.log('Nope.'); 281 | } 282 | } 283 | 284 | // good 285 | var test; 286 | if (currentUser) { 287 | test = function test() { 288 | console.log('Yup.'); 289 | }; 290 | } 291 | ``` 292 | 293 | - Never name a parameter `arguments`, this will take precedence over the `arguments` object that is given to every function scope. 294 | 295 | ```javascript 296 | // bad 297 | function nope(name, options, arguments) { 298 | // ...stuff... 299 | } 300 | 301 | // good 302 | function yup(name, options, args) { 303 | // ...stuff... 304 | } 305 | ``` 306 | 307 | **[⬆ back to top](#table-of-contents)** 308 | 309 | 310 | 311 | ## Properties 312 | 313 | - Use dot notation when accessing properties. 314 | 315 | ```javascript 316 | var luke = { 317 | jedi: true, 318 | age: 28 319 | }; 320 | 321 | // bad 322 | var isJedi = luke['jedi']; 323 | 324 | // good 325 | var isJedi = luke.jedi; 326 | ``` 327 | 328 | - Use subscript notation `[]` when accessing properties with a variable. 329 | 330 | ```javascript 331 | var luke = { 332 | jedi: true, 333 | age: 28 334 | }; 335 | 336 | function getProp(prop) { 337 | return luke[prop]; 338 | } 339 | 340 | var isJedi = getProp('jedi'); 341 | ``` 342 | 343 | **[⬆ back to top](#table-of-contents)** 344 | 345 | 346 | ## Variables 347 | 348 | - Always use `var` to declare variables. Not doing so will result in global variables. We want to avoid polluting the global namespace. Captain Planet warned us of that. 349 | 350 | ```javascript 351 | // bad 352 | superPower = new SuperPower(); 353 | 354 | // good 355 | var superPower = new SuperPower(); 356 | ``` 357 | 358 | - Use one `var` declaration per variable. 359 | It's easier to add new variable declarations this way, and you never have 360 | to worry about swapping out a `;` for a `,` or introducing punctuation-only 361 | diffs. 362 | 363 | ```javascript 364 | // bad 365 | var items = getItems(), 366 | goSportsTeam = true, 367 | dragonball = 'z'; 368 | 369 | // bad 370 | // (compare to above, and try to spot the mistake) 371 | var items = getItems(), 372 | goSportsTeam = true; 373 | dragonball = 'z'; 374 | 375 | // good 376 | var items = getItems(); 377 | var goSportsTeam = true; 378 | var dragonball = 'z'; 379 | ``` 380 | 381 | - Declare unassigned variables last. This is helpful when later on you might need to assign a variable depending on one of the previous assigned variables. 382 | 383 | ```javascript 384 | // bad 385 | var i, len, dragonball, 386 | items = getItems(), 387 | goSportsTeam = true; 388 | 389 | // bad 390 | var i; 391 | var items = getItems(); 392 | var dragonball; 393 | var goSportsTeam = true; 394 | var len; 395 | 396 | // good 397 | var items = getItems(); 398 | var goSportsTeam = true; 399 | var dragonball; 400 | var length; 401 | var i; 402 | ``` 403 | 404 | - Assign variables at the top of their scope. This helps avoid issues with variable declaration and assignment hoisting related issues. 405 | 406 | ```javascript 407 | // bad 408 | function() { 409 | test(); 410 | console.log('doing stuff..'); 411 | 412 | //..other stuff.. 413 | 414 | var name = getName(); 415 | 416 | if (name === 'test') { 417 | return false; 418 | } 419 | 420 | return name; 421 | } 422 | 423 | // good 424 | function() { 425 | var name = getName(); 426 | 427 | test(); 428 | console.log('doing stuff..'); 429 | 430 | //..other stuff.. 431 | 432 | if (name === 'test') { 433 | return false; 434 | } 435 | 436 | return name; 437 | } 438 | 439 | // bad 440 | function() { 441 | var name = getName(); 442 | 443 | if (!arguments.length) { 444 | return false; 445 | } 446 | 447 | return true; 448 | } 449 | 450 | // good 451 | function() { 452 | if (!arguments.length) { 453 | return false; 454 | } 455 | 456 | var name = getName(); 457 | 458 | return true; 459 | } 460 | ``` 461 | 462 | **[⬆ back to top](#table-of-contents)** 463 | 464 | 465 | ## Hoisting 466 | 467 | - Variable declarations get hoisted to the top of their scope, their assignment does not. 468 | 469 | ```javascript 470 | // we know this wouldn't work (assuming there 471 | // is no notDefined global variable) 472 | function example() { 473 | console.log(notDefined); // => throws a ReferenceError 474 | } 475 | 476 | // creating a variable declaration after you 477 | // reference the variable will work due to 478 | // variable hoisting. Note: the assignment 479 | // value of `true` is not hoisted. 480 | function example() { 481 | console.log(declaredButNotAssigned); // => undefined 482 | var declaredButNotAssigned = true; 483 | } 484 | 485 | // The interpreter is hoisting the variable 486 | // declaration to the top of the scope. 487 | // Which means our example could be rewritten as: 488 | function example() { 489 | var declaredButNotAssigned; 490 | console.log(declaredButNotAssigned); // => undefined 491 | declaredButNotAssigned = true; 492 | } 493 | ``` 494 | 495 | - Anonymous function expressions hoist their variable name, but not the function assignment. 496 | 497 | ```javascript 498 | function example() { 499 | console.log(anonymous); // => undefined 500 | 501 | anonymous(); // => TypeError anonymous is not a function 502 | 503 | var anonymous = function() { 504 | console.log('anonymous function expression'); 505 | }; 506 | } 507 | ``` 508 | 509 | - Named function expressions hoist the variable name, not the function name or the function body. 510 | 511 | ```javascript 512 | function example() { 513 | console.log(named); // => undefined 514 | 515 | named(); // => TypeError named is not a function 516 | 517 | superPower(); // => ReferenceError superPower is not defined 518 | 519 | var named = function superPower() { 520 | console.log('Flying'); 521 | }; 522 | } 523 | 524 | // the same is true when the function name 525 | // is the same as the variable name. 526 | function example() { 527 | console.log(named); // => undefined 528 | 529 | named(); // => TypeError named is not a function 530 | 531 | var named = function named() { 532 | console.log('named'); 533 | } 534 | } 535 | ``` 536 | 537 | - Function declarations hoist their name and the function body. 538 | 539 | ```javascript 540 | function example() { 541 | superPower(); // => Flying 542 | 543 | function superPower() { 544 | console.log('Flying'); 545 | } 546 | } 547 | ``` 548 | 549 | - For more information refer to [JavaScript Scoping & Hoisting](http://www.adequatelygood.com/2010/2/JavaScript-Scoping-and-Hoisting) by [Ben Cherry](http://www.adequatelygood.com/) 550 | 551 | **[⬆ back to top](#table-of-contents)** 552 | 553 | 554 | 555 | ## Conditional Expressions & Equality 556 | 557 | - Use `===` and `!==` over `==` and `!=`. 558 | - Conditional expressions are evaluated using coercion with the `ToBoolean` method and always follow these simple rules: 559 | 560 | + **Objects** evaluate to **true** 561 | + **Undefined** evaluates to **false** 562 | + **Null** evaluates to **false** 563 | + **Booleans** evaluate to **the value of the boolean** 564 | + **Numbers** evaluate to **false** if **+0, -0, or NaN**, otherwise **true** 565 | + **Strings** evaluate to **false** if an empty string `''`, otherwise **true** 566 | 567 | ```javascript 568 | if ([0]) { 569 | // true 570 | // An array is an object, objects evaluate to true 571 | } 572 | ``` 573 | 574 | - Use shortcuts. 575 | 576 | ```javascript 577 | // bad 578 | if (name !== '') { 579 | // ...stuff... 580 | } 581 | 582 | // good 583 | if (name) { 584 | // ...stuff... 585 | } 586 | 587 | // bad 588 | if (collection.length > 0) { 589 | // ...stuff... 590 | } 591 | 592 | // good 593 | if (collection.length) { 594 | // ...stuff... 595 | } 596 | ``` 597 | 598 | - For more information see [Truth Equality and JavaScript](http://javascriptweblog.wordpress.com/2011/02/07/truth-equality-and-javascript/#more-2108) by Angus Croll 599 | 600 | **[⬆ back to top](#table-of-contents)** 601 | 602 | 603 | ## Blocks 604 | 605 | - Use braces with all multi-line blocks. 606 | 607 | ```javascript 608 | // bad 609 | if (test) 610 | return false; 611 | 612 | // good 613 | if (test) return false; 614 | 615 | // good 616 | if (test) { 617 | return false; 618 | } 619 | 620 | // bad 621 | function() { return false; } 622 | 623 | // good 624 | function() { 625 | return false; 626 | } 627 | ``` 628 | 629 | **[⬆ back to top](#table-of-contents)** 630 | 631 | 632 | ## Comments 633 | 634 | - Use `/** ... */` for multiline comments. Include a description, specify types and values for all parameters and return values. 635 | 636 | ```javascript 637 | // bad 638 | // make() returns a new element 639 | // based on the passed in tag name 640 | // 641 | // @param {String} tag 642 | // @return {Element} element 643 | function make(tag) { 644 | 645 | // ...stuff... 646 | 647 | return element; 648 | } 649 | 650 | // good 651 | /** 652 | * make() returns a new element 653 | * based on the passed in tag name 654 | * 655 | * @param {String} tag 656 | * @return {Element} element 657 | */ 658 | function make(tag) { 659 | 660 | // ...stuff... 661 | 662 | return element; 663 | } 664 | ``` 665 | 666 | - Use `//` for single line comments. Place single line comments on a newline above the subject of the comment. Put an empty line before the comment. 667 | 668 | ```javascript 669 | // bad 670 | var active = true; // is current tab 671 | 672 | // good 673 | // is current tab 674 | var active = true; 675 | 676 | // bad 677 | function getType() { 678 | console.log('fetching type...'); 679 | // set the default type to 'no type' 680 | var type = this._type || 'no type'; 681 | 682 | return type; 683 | } 684 | 685 | // good 686 | function getType() { 687 | console.log('fetching type...'); 688 | 689 | // set the default type to 'no type' 690 | var type = this._type || 'no type'; 691 | 692 | return type; 693 | } 694 | ``` 695 | 696 | - Prefixing your comments with `FIXME` or `TODO` helps other developers quickly understand if you're pointing out a problem that needs to be revisited, or if you're suggesting a solution to the problem that needs to be implemented. These are different than regular comments because they are actionable. The actions are `FIXME -- need to figure this out` or `TODO -- need to implement`. 697 | 698 | - Use `// FIXME:` to annotate problems 699 | 700 | ```javascript 701 | function Calculator() { 702 | 703 | // FIXME: shouldn't use a global here 704 | total = 0; 705 | 706 | return this; 707 | } 708 | ``` 709 | 710 | - Use `// TODO:` to annotate solutions to problems 711 | 712 | ```javascript 713 | function Calculator() { 714 | 715 | // TODO: total should be configurable by an options param 716 | this.total = 0; 717 | 718 | return this; 719 | } 720 | ``` 721 | 722 | **[⬆ back to top](#table-of-contents)** 723 | 724 | 725 | ## Whitespace 726 | 727 | - Use soft tabs set to 2 spaces 728 | 729 | ```javascript 730 | // bad 731 | function() { 732 | ∙∙∙∙var name; 733 | } 734 | 735 | // bad 736 | function() { 737 | ∙var name; 738 | } 739 | 740 | // good 741 | function() { 742 | ∙∙var name; 743 | } 744 | ``` 745 | 746 | - Place 1 space before the leading brace. 747 | 748 | ```javascript 749 | // bad 750 | function test(){ 751 | console.log('test'); 752 | } 753 | 754 | // good 755 | function test() { 756 | console.log('test'); 757 | } 758 | 759 | // bad 760 | dog.set('attr',{ 761 | age: '1 year', 762 | breed: 'Bernese Mountain Dog' 763 | }); 764 | 765 | // good 766 | dog.set('attr', { 767 | age: '1 year', 768 | breed: 'Bernese Mountain Dog' 769 | }); 770 | ``` 771 | 772 | - Set off operators with spaces. 773 | 774 | ```javascript 775 | // bad 776 | var x=y+5; 777 | 778 | // good 779 | var x = y + 5; 780 | ``` 781 | 782 | - End files with a single newline character. 783 | 784 | ```javascript 785 | // bad 786 | (function(global) { 787 | // ...stuff... 788 | })(this); 789 | ``` 790 | 791 | ```javascript 792 | // bad 793 | (function(global) { 794 | // ...stuff... 795 | })(this);↵ 796 | ↵ 797 | ``` 798 | 799 | ```javascript 800 | // good 801 | (function(global) { 802 | // ...stuff... 803 | })(this);↵ 804 | ``` 805 | 806 | - Use indentation when making long method chains. 807 | 808 | ```javascript 809 | // bad 810 | $('#items').find('.selected').highlight().end().find('.open').updateCount(); 811 | 812 | // good 813 | $('#items') 814 | .find('.selected') 815 | .highlight() 816 | .end() 817 | .find('.open') 818 | .updateCount(); 819 | 820 | // bad 821 | var leds = stage.selectAll('.led').data(data).enter().append('svg:svg').class('led', true) 822 | .attr('width', (radius + margin) * 2).append('svg:g') 823 | .attr('transform', 'translate(' + (radius + margin) + ',' + (radius + margin) + ')') 824 | .call(tron.led); 825 | 826 | // good 827 | var leds = stage.selectAll('.led') 828 | .data(data) 829 | .enter().append('svg:svg') 830 | .class('led', true) 831 | .attr('width', (radius + margin) * 2) 832 | .append('svg:g') 833 | .attr('transform', 'translate(' + (radius + margin) + ',' + (radius + margin) + ')') 834 | .call(tron.led); 835 | ``` 836 | 837 | **[⬆ back to top](#table-of-contents)** 838 | 839 | ## Commas 840 | 841 | - Leading commas: **Nope.** 842 | 843 | ```javascript 844 | // bad 845 | var story = [ 846 | once 847 | , upon 848 | , aTime 849 | ]; 850 | 851 | // good 852 | var story = [ 853 | once, 854 | upon, 855 | aTime 856 | ]; 857 | 858 | // bad 859 | var hero = { 860 | firstName: 'Bob' 861 | , lastName: 'Parr' 862 | , heroName: 'Mr. Incredible' 863 | , superPower: 'strength' 864 | }; 865 | 866 | // good 867 | var hero = { 868 | firstName: 'Bob', 869 | lastName: 'Parr', 870 | heroName: 'Mr. Incredible', 871 | superPower: 'strength' 872 | }; 873 | ``` 874 | 875 | - Additional trailing comma: **Nope.** This can cause problems with IE6/7 and IE9 if it's in quirksmode. Also, in some implementations of ES3 would add length to an array if it had an additional trailing comma. This was clarified in ES5 ([source](http://es5.github.io/#D)): 876 | 877 | > Edition 5 clarifies the fact that a trailing comma at the end of an ArrayInitialiser does not add to the length of the array. This is not a semantic change from Edition 3 but some implementations may have previously misinterpreted this. 878 | 879 | ```javascript 880 | // bad 881 | var hero = { 882 | firstName: 'Kevin', 883 | lastName: 'Flynn', 884 | }; 885 | 886 | var heroes = [ 887 | 'Batman', 888 | 'Superman', 889 | ]; 890 | 891 | // good 892 | var hero = { 893 | firstName: 'Kevin', 894 | lastName: 'Flynn' 895 | }; 896 | 897 | var heroes = [ 898 | 'Batman', 899 | 'Superman' 900 | ]; 901 | ``` 902 | 903 | **[⬆ back to top](#table-of-contents)** 904 | 905 | 906 | ## Semicolons 907 | 908 | - **Yup.** 909 | 910 | ```javascript 911 | // bad 912 | (function() { 913 | var name = 'Skywalker' 914 | return name 915 | })() 916 | 917 | // good 918 | (function() { 919 | var name = 'Skywalker'; 920 | return name; 921 | })(); 922 | 923 | // good (guards against the function becoming an argument when two files with IIFEs are concatenated) 924 | ;(function() { 925 | var name = 'Skywalker'; 926 | return name; 927 | })(); 928 | ``` 929 | 930 | [Read more](http://stackoverflow.com/a/7365214/1712802). 931 | 932 | **[⬆ back to top](#table-of-contents)** 933 | 934 | 935 | ## Type Casting & Coercion 936 | 937 | - Perform type coercion at the beginning of the statement. 938 | - Strings: 939 | 940 | ```javascript 941 | // => this.reviewScore = 9; 942 | 943 | // bad 944 | var totalScore = this.reviewScore + ''; 945 | 946 | // good 947 | var totalScore = '' + this.reviewScore; 948 | 949 | // bad 950 | var totalScore = '' + this.reviewScore + ' total score'; 951 | 952 | // good 953 | var totalScore = this.reviewScore + ' total score'; 954 | ``` 955 | 956 | - Use `parseInt` for Numbers and always with a radix for type casting. 957 | 958 | ```javascript 959 | var inputValue = '4'; 960 | 961 | // bad 962 | var val = new Number(inputValue); 963 | 964 | // bad 965 | var val = +inputValue; 966 | 967 | // bad 968 | var val = inputValue >> 0; 969 | 970 | // bad 971 | var val = parseInt(inputValue); 972 | 973 | // good 974 | var val = Number(inputValue); 975 | 976 | // good 977 | var val = parseInt(inputValue, 10); 978 | ``` 979 | 980 | - If for whatever reason you are doing something wild and `parseInt` is your bottleneck and need to use Bitshift for [performance reasons](http://jsperf.com/coercion-vs-casting/3), leave a comment explaining why and what you're doing. 981 | 982 | ```javascript 983 | // good 984 | /** 985 | * parseInt was the reason my code was slow. 986 | * Bitshifting the String to coerce it to a 987 | * Number made it a lot faster. 988 | */ 989 | var val = inputValue >> 0; 990 | ``` 991 | 992 | - **Note:** Be careful when using bitshift operations. Numbers are represented as [64-bit values](http://es5.github.io/#x4.3.19), but Bitshift operations always return a 32-bit integer ([source](http://es5.github.io/#x11.7)). Bitshift can lead to unexpected behavior for integer values larger than 32 bits. [Discussion](https://github.com/airbnb/javascript/issues/109). Largest signed 32-bit Int is 2,147,483,647: 993 | 994 | ```javascript 995 | 2147483647 >> 0 //=> 2147483647 996 | 2147483648 >> 0 //=> -2147483648 997 | 2147483649 >> 0 //=> -2147483647 998 | ``` 999 | 1000 | - Booleans: 1001 | 1002 | ```javascript 1003 | var age = 0; 1004 | 1005 | // bad 1006 | var hasAge = new Boolean(age); 1007 | 1008 | // good 1009 | var hasAge = Boolean(age); 1010 | 1011 | // good 1012 | var hasAge = !!age; 1013 | ``` 1014 | 1015 | **[⬆ back to top](#table-of-contents)** 1016 | 1017 | 1018 | ## Naming Conventions 1019 | 1020 | - Avoid single letter names. Be descriptive with your naming. 1021 | 1022 | ```javascript 1023 | // bad 1024 | function q() { 1025 | // ...stuff... 1026 | } 1027 | 1028 | // good 1029 | function query() { 1030 | // ..stuff.. 1031 | } 1032 | ``` 1033 | 1034 | - Use camelCase when naming objects, functions, and instances 1035 | 1036 | ```javascript 1037 | // bad 1038 | var OBJEcttsssss = {}; 1039 | var this_is_my_object = {}; 1040 | function c() {} 1041 | var u = new user({ 1042 | name: 'Bob Parr' 1043 | }); 1044 | 1045 | // good 1046 | var thisIsMyObject = {}; 1047 | function thisIsMyFunction() {} 1048 | var user = new User({ 1049 | name: 'Bob Parr' 1050 | }); 1051 | ``` 1052 | 1053 | - Use PascalCase when naming constructors or classes 1054 | 1055 | ```javascript 1056 | // bad 1057 | function user(options) { 1058 | this.name = options.name; 1059 | } 1060 | 1061 | var bad = new user({ 1062 | name: 'nope' 1063 | }); 1064 | 1065 | // good 1066 | function User(options) { 1067 | this.name = options.name; 1068 | } 1069 | 1070 | var good = new User({ 1071 | name: 'yup' 1072 | }); 1073 | ``` 1074 | 1075 | - Use a leading underscore `_` when naming private properties 1076 | 1077 | ```javascript 1078 | // bad 1079 | this.__firstName__ = 'Panda'; 1080 | this.firstName_ = 'Panda'; 1081 | 1082 | // good 1083 | this._firstName = 'Panda'; 1084 | ``` 1085 | 1086 | - When saving a reference to `this` use `_this`. 1087 | 1088 | ```javascript 1089 | // bad 1090 | function() { 1091 | var self = this; 1092 | return function() { 1093 | console.log(self); 1094 | }; 1095 | } 1096 | 1097 | // bad 1098 | function() { 1099 | var that = this; 1100 | return function() { 1101 | console.log(that); 1102 | }; 1103 | } 1104 | 1105 | // good 1106 | function() { 1107 | var _this = this; 1108 | return function() { 1109 | console.log(_this); 1110 | }; 1111 | } 1112 | ``` 1113 | 1114 | - Name your functions. This is helpful for stack traces. 1115 | 1116 | ```javascript 1117 | // bad 1118 | var log = function(msg) { 1119 | console.log(msg); 1120 | }; 1121 | 1122 | // good 1123 | var log = function log(msg) { 1124 | console.log(msg); 1125 | }; 1126 | ``` 1127 | 1128 | - **Note:** IE8 and below exhibit some quirks with named function expressions. See [http://kangax.github.io/nfe/](http://kangax.github.io/nfe/) for more info. 1129 | 1130 | **[⬆ back to top](#table-of-contents)** 1131 | 1132 | 1133 | ## Accessors 1134 | 1135 | - Accessor functions for properties are not required 1136 | - If you do make accessor functions use getVal() and setVal('hello') 1137 | 1138 | ```javascript 1139 | // bad 1140 | dragon.age(); 1141 | 1142 | // good 1143 | dragon.getAge(); 1144 | 1145 | // bad 1146 | dragon.age(25); 1147 | 1148 | // good 1149 | dragon.setAge(25); 1150 | ``` 1151 | 1152 | - If the property is a boolean, use isVal() or hasVal() 1153 | 1154 | ```javascript 1155 | // bad 1156 | if (!dragon.age()) { 1157 | return false; 1158 | } 1159 | 1160 | // good 1161 | if (!dragon.hasAge()) { 1162 | return false; 1163 | } 1164 | ``` 1165 | 1166 | - It's okay to create get() and set() functions, but be consistent. 1167 | 1168 | ```javascript 1169 | function Jedi(options) { 1170 | options || (options = {}); 1171 | var lightsaber = options.lightsaber || 'blue'; 1172 | this.set('lightsaber', lightsaber); 1173 | } 1174 | 1175 | Jedi.prototype.set = function(key, val) { 1176 | this[key] = val; 1177 | }; 1178 | 1179 | Jedi.prototype.get = function(key) { 1180 | return this[key]; 1181 | }; 1182 | ``` 1183 | 1184 | **[⬆ back to top](#table-of-contents)** 1185 | 1186 | 1187 | ## Constructors 1188 | 1189 | - Assign methods to the prototype object, instead of overwriting the prototype with a new object. Overwriting the prototype makes inheritance impossible: by resetting the prototype you'll overwrite the base! 1190 | 1191 | ```javascript 1192 | function Jedi() { 1193 | console.log('new jedi'); 1194 | } 1195 | 1196 | // bad 1197 | Jedi.prototype = { 1198 | fight: function fight() { 1199 | console.log('fighting'); 1200 | }, 1201 | 1202 | block: function block() { 1203 | console.log('blocking'); 1204 | } 1205 | }; 1206 | 1207 | // good 1208 | Jedi.prototype.fight = function fight() { 1209 | console.log('fighting'); 1210 | }; 1211 | 1212 | Jedi.prototype.block = function block() { 1213 | console.log('blocking'); 1214 | }; 1215 | ``` 1216 | 1217 | - Methods can return `this` to help with method chaining. 1218 | 1219 | ```javascript 1220 | // bad 1221 | Jedi.prototype.jump = function() { 1222 | this.jumping = true; 1223 | return true; 1224 | }; 1225 | 1226 | Jedi.prototype.setHeight = function(height) { 1227 | this.height = height; 1228 | }; 1229 | 1230 | var luke = new Jedi(); 1231 | luke.jump(); // => true 1232 | luke.setHeight(20); // => undefined 1233 | 1234 | // good 1235 | Jedi.prototype.jump = function() { 1236 | this.jumping = true; 1237 | return this; 1238 | }; 1239 | 1240 | Jedi.prototype.setHeight = function(height) { 1241 | this.height = height; 1242 | return this; 1243 | }; 1244 | 1245 | var luke = new Jedi(); 1246 | 1247 | luke.jump() 1248 | .setHeight(20); 1249 | ``` 1250 | 1251 | 1252 | - It's okay to write a custom toString() method, just make sure it works successfully and causes no side effects. 1253 | 1254 | ```javascript 1255 | function Jedi(options) { 1256 | options || (options = {}); 1257 | this.name = options.name || 'no name'; 1258 | } 1259 | 1260 | Jedi.prototype.getName = function getName() { 1261 | return this.name; 1262 | }; 1263 | 1264 | Jedi.prototype.toString = function toString() { 1265 | return 'Jedi - ' + this.getName(); 1266 | }; 1267 | ``` 1268 | 1269 | **[⬆ back to top](#table-of-contents)** 1270 | 1271 | 1272 | ## Events 1273 | 1274 | - When attaching data payloads to events (whether DOM events or something more proprietary like Backbone events), pass a hash instead of a raw value. This allows a subsequent contributor to add more data to the event payload without finding and updating every handler for the event. For example, instead of: 1275 | 1276 | ```js 1277 | // bad 1278 | $(this).trigger('listingUpdated', listing.id); 1279 | 1280 | ... 1281 | 1282 | $(this).on('listingUpdated', function(e, listingId) { 1283 | // do something with listingId 1284 | }); 1285 | ``` 1286 | 1287 | prefer: 1288 | 1289 | ```js 1290 | // good 1291 | $(this).trigger('listingUpdated', { listingId : listing.id }); 1292 | 1293 | ... 1294 | 1295 | $(this).on('listingUpdated', function(e, data) { 1296 | // do something with data.listingId 1297 | }); 1298 | ``` 1299 | 1300 | **[⬆ back to top](#table-of-contents)** 1301 | 1302 | 1303 | ## Modules 1304 | 1305 | - The module should start with a `!`. This ensures that if a malformed module forgets to include a final semicolon there aren't errors in production when the scripts get concatenated. [Explanation](https://github.com/airbnb/javascript/issues/44#issuecomment-13063933) 1306 | - The file should be named with camelCase, live in a folder with the same name, and match the name of the single export. 1307 | - Add a method called `noConflict()` that sets the exported module to the previous version and returns this one. 1308 | - Always declare `'use strict';` at the top of the module. 1309 | 1310 | ```javascript 1311 | // fancyInput/fancyInput.js 1312 | 1313 | !function(global) { 1314 | 'use strict'; 1315 | 1316 | var previousFancyInput = global.FancyInput; 1317 | 1318 | function FancyInput(options) { 1319 | this.options = options || {}; 1320 | } 1321 | 1322 | FancyInput.noConflict = function noConflict() { 1323 | global.FancyInput = previousFancyInput; 1324 | return FancyInput; 1325 | }; 1326 | 1327 | global.FancyInput = FancyInput; 1328 | }(this); 1329 | ``` 1330 | 1331 | **[⬆ back to top](#table-of-contents)** 1332 | 1333 | 1334 | ## jQuery 1335 | 1336 | - Prefix jQuery object variables with a `$`. 1337 | 1338 | ```javascript 1339 | // bad 1340 | var sidebar = $('.sidebar'); 1341 | 1342 | // good 1343 | var $sidebar = $('.sidebar'); 1344 | ``` 1345 | 1346 | - Cache jQuery lookups. 1347 | 1348 | ```javascript 1349 | // bad 1350 | function setSidebar() { 1351 | $('.sidebar').hide(); 1352 | 1353 | // ...stuff... 1354 | 1355 | $('.sidebar').css({ 1356 | 'background-color': 'pink' 1357 | }); 1358 | } 1359 | 1360 | // good 1361 | function setSidebar() { 1362 | var $sidebar = $('.sidebar'); 1363 | $sidebar.hide(); 1364 | 1365 | // ...stuff... 1366 | 1367 | $sidebar.css({ 1368 | 'background-color': 'pink' 1369 | }); 1370 | } 1371 | ``` 1372 | 1373 | - For DOM queries use Cascading `$('.sidebar ul')` or parent > child `$('.sidebar > ul')`. [jsPerf](http://jsperf.com/jquery-find-vs-context-sel/16) 1374 | - Use `find` with scoped jQuery object queries. 1375 | 1376 | ```javascript 1377 | // bad 1378 | $('ul', '.sidebar').hide(); 1379 | 1380 | // bad 1381 | $('.sidebar').find('ul').hide(); 1382 | 1383 | // good 1384 | $('.sidebar ul').hide(); 1385 | 1386 | // good 1387 | $('.sidebar > ul').hide(); 1388 | 1389 | // good 1390 | $sidebar.find('ul').hide(); 1391 | ``` 1392 | 1393 | **[⬆ back to top](#table-of-contents)** 1394 | 1395 | 1396 | ## ECMAScript 5 Compatibility 1397 | 1398 | - Refer to [Kangax](https://twitter.com/kangax/)'s ES5 [compatibility table](http://kangax.github.com/es5-compat-table/) 1399 | 1400 | **[⬆ back to top](#table-of-contents)** 1401 | 1402 | 1403 | ## Testing 1404 | 1405 | - **Yup.** 1406 | 1407 | ```javascript 1408 | function() { 1409 | return true; 1410 | } 1411 | ``` 1412 | 1413 | **[⬆ back to top](#table-of-contents)** 1414 | -------------------------------------------------------------------------------- /__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 | -------------------------------------------------------------------------------- /__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 | -------------------------------------------------------------------------------- /__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/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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------