├── .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 | [ ](https://github.com/persistence-js/persist/releases)
6 | [ ](https://codeship.com/projects/86120/)
7 | [ ](https://www.npmjs.com/package/persistence-js)
8 |
9 | > Immutable data cannot be changed once created... Persistent data presents a mutative API which does not update the data in-place, but instead always yields new updated data ([source](https://facebook.github.io/immutable-js/)).
10 |
11 | ---
12 |
13 | To learn more about immutable data, persistent data structures, or any of the individual data structures implemented by PersistenceJS, please explore the [appendix](#appendix).
14 |
15 | ---
16 |
17 | > **Created by [Clark Feusier](http://clarkfeusier.com/pages/about) and [Daniel Tsui](http://sdtsui.com)**
18 |
19 |
20 | ---
21 |
22 | 1. [Dependencies](#dependencies)
23 | 1. [Installation](#installation)
24 | 1. [Documentation](#documentation)
25 | 1. [Roadmap](#roadmap)
26 | 1. [Contributing](#contributing-to-jkif-parser)
27 | 1. [Development Requirements](#development-requirements)
28 | 1. [Installing Dependencies](#installing-dependencies)
29 | 1. [Running Tests](#running-tests)
30 | 1. [License](#license)
31 | 1. [Appendix](#appendix)
32 |
33 | ---
34 |
35 | #### Dependencies
36 |
37 | - [immutable](https://facebook.github.io/immutable-js/) — basic immutable collections on which PersistenceJS is constructed
38 | - [core-js](https://www.npmjs.com/package/core-js/) — ES5/6/7 polyfills, shims, and other goodies
39 |
40 | ---
41 |
42 | ## Installation
43 |
44 | **PersistenceJS** is available as an npm package.
45 |
46 | ***Install module from command-line***
47 |
48 | ```sh
49 | npm install persistence-js
50 | ```
51 |
52 | ***Require module for use in desired file***
53 |
54 | ```js
55 | var Persist = require('persistence-js');
56 | ```
57 |
58 | ---
59 |
60 | ## Documentation
61 |
62 | ### *PersistenceJS*
63 |
64 | This object provides all of the data structures offered by PersistenceJS.
65 |
66 | ```js
67 | var Persist = require('persistence-js');
68 | ```
69 |
70 | ## Data Structures
71 |
72 | - [Linked List](src/lists/LList.es6)
73 |
74 | ```js
75 | var LList = Persist.LinkedList;
76 | var exampleLList = new LList();
77 | ```
78 |
79 | - [Circular Linked List](src/lists/CLList.es6)
80 |
81 | ```js
82 | var CLList = Persist.CircularLinkedList;
83 | var exampleCLList = new CLList();
84 | ```
85 |
86 | - [Heap](src/heaps/Heap.es6)
87 |
88 | ```js
89 | var Heap = Persist.Heap;
90 | var exampleHeap = new Heap();
91 | ```
92 |
93 | - [Binary Search Tree](src/binary_trees/BSTree.es6)
94 |
95 | ```js
96 | var BSTree = Persist.BSTree;
97 | var exampleBST = new BSTree();
98 | ```
99 |
100 | ---
101 |
102 | ## Roadmap
103 |
104 | The future of PersistenceJS is managed through this repository's **issues** — [view the roadmap here](https://github.com/persistence-js/persist/issues).
105 |
106 | ## Contributing to PersistenceJS
107 |
108 | We welcome contributions, but please read our [contribution guidelines](CONTRIBUTING.md) before submitting your work. The development requirements and instructions are below.
109 |
110 | ## Development Requirements
111 |
112 | - Node 0.10.x
113 | - npm 2.x.x
114 | - core-js
115 | - immutable
116 | - babel (global install)
117 | - babel-jest
118 | - jest-cli (global install)
119 | - grunt (global install)
120 | - grunt-cli (global install)
121 | - load-grunt-tasks
122 | - grunt-babel
123 |
124 | ### Installing Dependencies
125 |
126 | Install Node (bundled with npm) using [Homebrew](http://brew.sh/):
127 |
128 | ```sh
129 | brew install node
130 | ```
131 |
132 | Install project and development dependencies using npm:
133 |
134 | ```sh
135 | npm install
136 | ```
137 |
138 | ### Running Tests
139 |
140 | After installing the above dependencies, tests can be run using the following command:
141 |
142 | ```sh
143 | npm test
144 | ```
145 |
146 | ## [License](LICENSE.md)
147 |
148 | **PersistenceJS** - specialized persistent collections in javascript
149 |
150 | _Copyright 2015 Clark Feusier & Sze-Hung Daniel Tsui_
151 |
152 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
153 |
154 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
155 |
156 | [**COMPLETE LICENSE**](LICENSE)
157 |
158 | ---
159 |
160 | ## Appendix
161 |
162 | [Persistence](https://en.wikipedia.org/wiki/Persistent_data_structure)
163 |
164 | > A persistent data structure ... always preserves the previous version of itself when modified.
165 |
166 | [Immutability](https://en.wikipedia.org/wiki/Immutable_object)
167 |
168 | > An immutable object is an object whose state cannot be modified after it is created.
169 |
170 | Note, persistent data structures are generally immutable, since the API returns a new structure, despite appearing mutable.
171 |
172 | #### [Back to Top](#)
173 |
174 |
--------------------------------------------------------------------------------
/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 = '
';
229 |
230 | for (i = 0; i < length; i++) {
231 | items += '- ' + messages[i].message + '
';
232 | }
233 |
234 | return 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 |
--------------------------------------------------------------------------------