├── .gitignore
├── .travis.yml
├── .eslintrc.js
├── src
├── data-structures
│ ├── binary-heap
│ │ ├── package.json
│ │ ├── README.md
│ │ └── binary-heap.js
│ ├── linked-list
│ │ ├── package.json
│ │ ├── README.md
│ │ └── linked-list.js
│ ├── binary-search-tree
│ │ ├── package.json
│ │ ├── README.md
│ │ └── binary-search-tree.js
│ ├── doubly-linked-list
│ │ ├── package.json
│ │ └── README.md
│ ├── circular-linked-list
│ │ ├── package.json
│ │ └── README.md
│ ├── hash-map
│ │ ├── package.json
│ │ ├── README.md
│ │ └── hash-map.js
│ └── circular-doubly-linked-list
│ │ ├── package.json
│ │ └── README.md
└── algorithms
│ └── sorting
│ └── bubble-sort
│ ├── package.json
│ ├── README.md
│ └── bubble-sort.js
├── package.json
├── LICENSE
├── tests
├── algorithms
│ └── sorting
│ │ └── bubble-sort
│ │ └── bubble-sort.js
└── data-structures
│ ├── hash-map
│ └── hash-map.js
│ ├── binary-search-tree
│ └── binary-search-tree.js
│ ├── binary-heap
│ └── binary-heap.js
│ ├── linked-list
│ └── linked-list.js
│ ├── circular-linked-list
│ └── circular-linked-list.js
│ ├── circular-doubly-linked-list
│ └── circular-doubly-linked-list.js
│ └── doubly-linked-list
│ └── doubly-linked-list.js
├── algorithms
├── searching
│ └── binary-search
│ │ └── binary-search.js
├── checksums
│ └── luhn-algorithm
│ │ └── luhn-algorithm.js
└── sorting
│ ├── insertion-sort
│ └── insertion-sort.js
│ ├── merge-sort-recursive
│ ├── merge-sort-recursive.js
│ └── merge-sort-inplace.js
│ ├── selection-sort
│ └── selection-sort.js
│ ├── merge-sort-iterative
│ └── merge-sort-iterative.js
│ └── quicksort
│ └── quicksort.js
├── README.md
└── encodings
└── base64
├── base64.js
└── base64.htm
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | holding/
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "8"
4 | - "9"
5 | - "10"
6 | - "11"
7 | sudo: false
8 | branches:
9 | only:
10 | - master
11 |
12 | # Run npm test always
13 | script:
14 | - "npm test"
15 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "env": {
3 | "commonjs": true,
4 | "es6": true,
5 | "node": true
6 | },
7 | "extends": "eslint:recommended",
8 | "parserOptions": {
9 | "ecmaVersion": 2018
10 | },
11 | "rules": {
12 | "indent": [
13 | "error",
14 | 4
15 | ],
16 | "linebreak-style": [
17 | "error",
18 | "unix"
19 | ],
20 | "quotes": [
21 | "error",
22 | "double"
23 | ],
24 | "semi": [
25 | "error",
26 | "always"
27 | ]
28 | }
29 | };
--------------------------------------------------------------------------------
/src/data-structures/binary-heap/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@humanwhocodes/binary-heap",
3 | "version": "2.0.1",
4 | "description": "A binary heap implementation in JavaScript",
5 | "main": "binary-heap.js",
6 | "scripts": {
7 | "test": "npx mocha ../../../tests/data-structures/binary-heap/binary-heap.js"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/humanwhocodes/computer-science-in-javascript.git"
12 | },
13 | "keywords": [
14 | "heap",
15 | "binary heap",
16 | "data structure"
17 | ],
18 | "author": "Nicholas C. Zakas",
19 | "license": "MIT",
20 | "bugs": {
21 | "url": "https://github.com/humanwhocodes/computer-science-in-javascript/issues"
22 | },
23 | "homepage": "https://github.com/humanwhocodes/computer-science-in-javascript#readme",
24 | "engines": {
25 | "node": ">=8.0.0"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/data-structures/linked-list/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@humanwhocodes/linked-list",
3 | "version": "2.0.2",
4 | "description": "A LinkedList implementation in JavaScript",
5 | "main": "linked-list.js",
6 | "scripts": {
7 | "test": "npx mocha ../../../tests/data-structures/linked-list/linked-list.js"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/humanwhocodes/computer-science-in-javascript.git"
12 | },
13 | "keywords": [
14 | "linked list",
15 | "data structure",
16 | "iterable"
17 | ],
18 | "author": "Nicholas C. Zakas",
19 | "license": "MIT",
20 | "bugs": {
21 | "url": "https://github.com/humanwhocodes/computer-science-in-javascript/issues"
22 | },
23 | "homepage": "https://github.com/humanwhocodes/computer-science-in-javascript#readme",
24 | "engines": {
25 | "node": ">=8.0.0"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/algorithms/sorting/bubble-sort/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@humanwhocodes/bubble-sort",
3 | "version": "2.0.0",
4 | "description": "A bubble sort implementation in JavaScript",
5 | "main": "bubble-sort.js",
6 | "scripts": {
7 | "test": "npx mocha ../../../../tests/algorithms/sorting/bubble-sort/bubble-sort.js"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/humanwhocodes/computer-science-in-javascript.git"
12 | },
13 | "keywords": [
14 | "sorting",
15 | "algorithm",
16 | "bubble sort"
17 | ],
18 | "author": "Nicholas C. Zakas",
19 | "license": "MIT",
20 | "bugs": {
21 | "url": "https://github.com/humanwhocodes/computer-science-in-javascript/issues"
22 | },
23 | "homepage": "https://github.com/humanwhocodes/computer-science-in-javascript#readme",
24 | "engines": {
25 | "node": ">=8.0.0"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/data-structures/binary-search-tree/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@humanwhocodes/binary-search-tree",
3 | "version": "2.0.0",
4 | "description": "A binary search tree implementation in JavaScript",
5 | "main": "binary-search-tree.js",
6 | "scripts": {
7 | "test": "npx mocha ../../../tests/data-structures/binary-search-tree/binary-search-tree.js"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/humanwhocodes/computer-science-in-javascript.git"
12 | },
13 | "keywords": [
14 | "binary search tree",
15 | "linked list",
16 | "data structure",
17 | "iterable"
18 | ],
19 | "author": "Nicholas C. Zakas",
20 | "license": "MIT",
21 | "bugs": {
22 | "url": "https://github.com/humanwhocodes/computer-science-in-javascript/issues"
23 | },
24 | "homepage": "https://github.com/humanwhocodes/computer-science-in-javascript#readme",
25 | "engines": {
26 | "node": ">=8.0.0"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/data-structures/doubly-linked-list/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@humanwhocodes/doubly-linked-list",
3 | "version": "2.2.0",
4 | "description": "A doubly linked list implementation in JavaScript",
5 | "main": "doubly-linked-list.js",
6 | "scripts": {
7 | "test": "npx mocha ../../../tests/data-structures/doubly-linked-list/doubly-linked-list.js"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/humanwhocodes/computer-science-in-javascript.git"
12 | },
13 | "keywords": [
14 | "linked list",
15 | "doubly linked list",
16 | "data structure",
17 | "iterable"
18 | ],
19 | "author": "Nicholas C. Zakas",
20 | "license": "MIT",
21 | "bugs": {
22 | "url": "https://github.com/humanwhocodes/computer-science-in-javascript/issues"
23 | },
24 | "homepage": "https://github.com/humanwhocodes/computer-science-in-javascript#readme",
25 | "engines": {
26 | "node": ">=8.0.0"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/data-structures/circular-linked-list/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@humanwhocodes/circular-linked-list",
3 | "version": "2.0.0",
4 | "description": "A circular linked list implementation in JavaScript",
5 | "main": "circular-linked-list.js",
6 | "scripts": {
7 | "test": "npx mocha ../../../tests/data-structures/circular-linked-list/circular-linked-list.js"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/humanwhocodes/computer-science-in-javascript.git"
12 | },
13 | "keywords": [
14 | "circular linked list",
15 | "linked list",
16 | "data structure",
17 | "iterable"
18 | ],
19 | "author": "Nicholas C. Zakas",
20 | "license": "MIT",
21 | "bugs": {
22 | "url": "https://github.com/humanwhocodes/computer-science-in-javascript/issues"
23 | },
24 | "homepage": "https://github.com/humanwhocodes/computer-science-in-javascript#readme",
25 | "engines": {
26 | "node": ">=8.0.0"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/data-structures/hash-map/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@humanwhocodes/hash-map",
3 | "version": "2.0.0",
4 | "description": "A hash map implementation in JavaScript",
5 | "main": "hash-map.js",
6 | "scripts": {
7 | "test": "npx mocha ../../../tests/data-structures/hash-map/hash-map.js"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/humanwhocodes/computer-science-in-javascript.git"
12 | },
13 | "keywords": [
14 | "hash",
15 | "hash table",
16 | "hash map",
17 | "data structure",
18 | "iterable"
19 | ],
20 | "author": "Nicholas C. Zakas",
21 | "license": "MIT",
22 | "bugs": {
23 | "url": "https://github.com/humanwhocodes/computer-science-in-javascript/issues"
24 | },
25 | "homepage": "https://github.com/humanwhocodes/computer-science-in-javascript#readme",
26 | "engines": {
27 | "node": ">=8.0.0"
28 | },
29 | "dependencies": {
30 | "@humanwhocodes/doubly-linked-list": "^2.2.0"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/data-structures/circular-doubly-linked-list/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@humanwhocodes/circular-doubly-linked-list",
3 | "version": "2.0.2",
4 | "description": "A circular doubly linked list implementation in JavaScript",
5 | "main": "circular-doubly-linked-list.js",
6 | "scripts": {
7 | "test": "npx mocha ../../../tests/data-structures/circular-doubly-linked-list/circular-doubly-linked-list.js"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/humanwhocodes/computer-science-in-javascript.git"
12 | },
13 | "keywords": [
14 | "circular linked list",
15 | "cicular doubly linked list",
16 | "linked list",
17 | "doubly linked list",
18 | "data structure",
19 | "iterable"
20 | ],
21 | "author": "Nicholas C. Zakas",
22 | "license": "MIT",
23 | "bugs": {
24 | "url": "https://github.com/humanwhocodes/computer-science-in-javascript/issues"
25 | },
26 | "homepage": "https://github.com/humanwhocodes/computer-science-in-javascript#readme",
27 | "engines": {
28 | "node": ">=8.0.0"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@humanwhocodes/computer-science-in-javascript",
3 | "version": "2.0.0",
4 | "private": true,
5 | "description": "Collection of classic computer science paradigms, algorithms, and approaches written in JavaScript.",
6 | "main": "README.md",
7 | "scripts": {
8 | "lint": "eslint src/ tests/",
9 | "test": "npm run lint && mocha tests/**/*.js"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/nzakas/computer-science-in-javascript.git"
14 | },
15 | "keywords": [
16 | "cs",
17 | "algorithms",
18 | "data",
19 | "structures"
20 | ],
21 | "author": "nzakas",
22 | "license": "MIT",
23 | "bugs": {
24 | "url": "https://github.com/nzakas/computer-science-in-javascript/issues"
25 | },
26 | "homepage": "https://github.com/nzakas/computer-science-in-javascript#readme",
27 | "directories": {
28 | "test": "tests"
29 | },
30 | "devDependencies": {
31 | "chai": "^4.2.0",
32 | "eslint": "^5.10.0",
33 | "mocha": "^5.2.0"
34 | },
35 | "engines": {
36 | "node": ">=8.0.0"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2009 Nicholas C. Zakas. All rights reserved.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/tests/algorithms/sorting/bubble-sort/bubble-sort.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileoverview Bubble Sort tests
3 | */
4 | /* global it, describe */
5 |
6 | "use strict";
7 |
8 | //-----------------------------------------------------------------------------
9 | // Requirements
10 | //-----------------------------------------------------------------------------
11 |
12 | const assert = require("chai").assert;
13 | const { bubbleSort } = require("../../../../src/algorithms/sorting/bubble-sort");
14 |
15 | //-----------------------------------------------------------------------------
16 | // Tests
17 | //-----------------------------------------------------------------------------
18 |
19 | describe("bubbleSort()", () => {
20 |
21 | [
22 | [],
23 | [1],
24 | [2, 1],
25 | [2, 1, 3],
26 | [32,4,5,7,9,4,1],
27 | [3,1,1,5,9,4,2, 5, 12, 45]
28 | ].forEach(items => {
29 |
30 | it("should sort an array when the array has " + items.length + " item(s)", () => {
31 | const result = [...items].sort((a, b) => a - b);
32 |
33 | bubbleSort(items);
34 | assert.deepStrictEqual(items, result);
35 | });
36 |
37 | });
38 |
39 | });
--------------------------------------------------------------------------------
/src/algorithms/sorting/bubble-sort/README.md:
--------------------------------------------------------------------------------
1 | # Bubble Sort in JavaScript
2 |
3 | by [Nicholas C. Zakas](https://humanwhocodes.com)
4 |
5 | If you find this useful, please consider supporting my work with a [donation](https://humanwhocodes.com/donate).
6 |
7 | ## Overview
8 |
9 | This is an implementation of the bubble sort algorithm in JavaScript. The function sorts an array in place.
10 |
11 | **Note:** You should always use the builtin `sort()` method on arrays in your code because it is already optimized for production use. This implementation should be used for learning purposes only.
12 |
13 | ## Usage
14 |
15 | Use CommonJS to get access to the `bubbleSort()` function:
16 |
17 | ```js
18 | const { bubbleSort } = require("@humanwhocodes/bubble-sort");
19 |
20 | const items = [1, 5, 2];
21 | const result = bubbleSort(items);
22 |
23 | console.log(result); // [1, 2, 5]
24 | ```
25 |
26 | ## Note on Code Style
27 |
28 | You may find the code style of this module to be overly verbose with a lot of comments. That is intentional, as the primary use of this module is intended to be for educational purposes. There are frequently more concise ways of implementing the details of this class, but the more concise ways are difficult for newcomers who are unfamiliar with linked lists as a concept or JavaScript as a whole.
29 |
30 | ## Issues and Pull Requests
31 |
32 | As this is part of series of tutorials I'm writing, only bug fixes will be accepted. No new functionality will be added to this module.
33 |
34 | ## License
35 |
36 | MIT
--------------------------------------------------------------------------------
/src/algorithms/sorting/bubble-sort/bubble-sort.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileoverview Bubble sort implementation in JavaScript
3 | */
4 |
5 | /**
6 | * Swaps two values in an array.
7 | * @param {Array} items The array containing the items.
8 | * @param {int} firstIndex Index of first item to swap.
9 | * @param {int} secondIndex Index of second item to swap.
10 | * @returns {void}
11 | */
12 | function swap(items, firstIndex, secondIndex){
13 | var temp = items[firstIndex];
14 | items[firstIndex] = items[secondIndex];
15 | items[secondIndex] = temp;
16 | }
17 |
18 | /**
19 | * A bubble sort implementation in JavaScript. The array
20 | * is sorted in-place.
21 | * @param {Array} items An array of items to sort.
22 | * @return {Array} The sorted array.
23 | */
24 | exports.bubbleSort = (items) => {
25 |
26 | /*
27 | * The outer loop moves from the first item in the array to the last item
28 | * in the array.
29 | */
30 | for (let i = 0; i < items.length; i++){
31 |
32 | /*
33 | * The inner loop also moves from the first item in the array towards
34 | * a stopping point. The `stop` value is the length of the array
35 | * minus the position of the outer loop minus one. The stop position
36 | * is used because items start by being sorted at the back of the
37 | * array and increasing towards the front of the array. The minus one
38 | * is necessary because we are comparing each item to the next
39 | * item, and the last item doesn't have a next item to compare to.
40 | */
41 | for (let j = 0, stop = items.length - i - 1; j < stop; j++){
42 |
43 | /*
44 | * If the item at index `j` is greater than the item at index
45 | * `j + 1`, then swap the values.
46 | */
47 | if (items[j] > items[j + 1]){
48 | swap(items, j, j + 1);
49 | }
50 | }
51 | }
52 |
53 | return items;
54 | };
--------------------------------------------------------------------------------
/algorithms/searching/binary-search/binary-search.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Binary search implementation in JavaScript
3 | * Copyright (c) 2009 Nicholas C. Zakas
4 | *
5 | * Permission is hereby granted, free of charge, to any person obtaining a copy
6 | * of this software and associated documentation files (the "Software"), to deal
7 | * in the Software without restriction, including without limitation the rights
8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | * copies of the Software, and to permit persons to whom the Software is
10 | * furnished to do so, subject to the following conditions:
11 | *
12 | * The above copyright notice and this permission notice shall be included in
13 | * all copies or substantial portions of the Software.
14 | *
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | * THE SOFTWARE.
22 | */
23 |
24 |
25 | /**
26 | * Uses a binary search algorithm to locate a value in the specified array.
27 | * @param {Array} items The array containing the item.
28 | * @param {variant} value The value to search for.
29 | * @return {int} The zero-based index of the value in the array or -1 if not found.
30 | */
31 | function binarySearch(items, value){
32 |
33 | var startIndex = 0,
34 | stopIndex = items.length - 1,
35 | middle = Math.floor((stopIndex + startIndex)/2);
36 |
37 | while(items[middle] != value && startIndex < stopIndex){
38 |
39 | //adjust search area
40 | if (value < items[middle]){
41 | stopIndex = middle - 1;
42 | } else if (value > items[middle]){
43 | startIndex = middle + 1;
44 | }
45 |
46 | //recalculate middle
47 | middle = Math.floor((stopIndex + startIndex)/2);
48 | }
49 |
50 | //make sure it's the right value
51 | return (items[middle] != value) ? -1 : middle;
52 | }
53 |
--------------------------------------------------------------------------------
/src/data-structures/hash-map/README.md:
--------------------------------------------------------------------------------
1 | # JavaScript Hash Map Class
2 |
3 | by [Nicholas C. Zakas](https://humanwhocodes.com)
4 |
5 | If you find this useful, please consider supporting my work with a [donation](https://humanwhocodes.com/donate).
6 |
7 | ## Overview
8 |
9 | A JavaScript implementation of a hash map where all keys must be strings. This class uses the conventions of built-in JavaScript collection objects, such as:
10 |
11 | 1. There is a `[Symbol.iterator]` method so each instance is iterable.
12 | 1. The `size` getter property instead of a `length` data property to indicate that the size of the list is dynamically counted rather than stored.
13 | 1. Defining `entries()`, `keys()`, and `values()` generator methods.
14 |
15 | ## Usage
16 |
17 | Use CommonJS to get access to the `HashMap` constructor:
18 |
19 | ```js
20 | const { HashMap } = require("@humanwhocodes/hash-map");
21 | ```
22 |
23 | Each instance of `HashMap` has the following properties and methods:
24 |
25 | ```js
26 | const map = new HashMap();
27 |
28 | // add an item
29 | map.set("foo", 1);
30 |
31 | // get the value of an item
32 | let value = map.get("foo");
33 |
34 | // get the number of items
35 | let count = map.size;
36 |
37 | // does the key exist in the map?
38 | let found = map.has("foo");
39 |
40 | // remove a key
41 | map.delete("foo");
42 |
43 | // get all key-value pairs
44 | let entries1 = [...map.entries()];
45 | let entries2 = [...map];
46 |
47 | // get all keys
48 | let keys = [...map.keys()];
49 |
50 | // get all values
51 | let values = [...map.values()];
52 |
53 | // remove all items
54 | map.clear();
55 | ```
56 |
57 | ## Note on Code Style
58 |
59 | You may find the code style of this module to be overly verbose with a lot of comments. That is intentional, as the primary use of this module is intended to be for educational purposes. There are frequently more concise ways of implementing the details of this class, but the more concise ways are difficult for newcomers who are unfamiliar with linked lists as a concept or JavaScript as a whole.
60 |
61 | ## Note on Usage
62 |
63 | This module is intended for educational purposes. For production purposes, you should use the native JavaScript `Map` class.
64 |
65 | ## Issues and Pull Requests
66 |
67 | As this is part of series of tutorials I'm writing, only bug fixes will be accepted. No new functionality will be added to this module.
68 |
69 | ## License
70 |
71 | MIT
--------------------------------------------------------------------------------
/src/data-structures/binary-search-tree/README.md:
--------------------------------------------------------------------------------
1 | # JavaScript Binary Search Tree Class
2 |
3 | by [Nicholas C. Zakas](https://humanwhocodes.com)
4 |
5 | If you find this useful, please consider supporting my work with a [donation](https://humanwhocodes.com/donate).
6 |
7 | ## Overview
8 |
9 | A JavaScript implementation of a binary search tree. This class uses the conventions of built-in JavaScript collection objects, such as:
10 |
11 | 1. There is a `[Symbol.iterator]` method so each instance is iterable.
12 | 1. The `size` getter property instead of a `length` data property to indicate that the size of the tree is dynamically counted rather than stored.
13 | 1. Defining a `values()` generator method.
14 |
15 | Additionally, this implementation follows the JavaScript `Set` interface for adding, detecting, and removing values:
16 |
17 | * `add(value)` to add a value into the tree
18 | * `has(value)` to detect if a value is in the tree
19 | * `delete(value)` to remove a value from the tree
20 |
21 | ## Usage
22 |
23 | Use CommonJS to get access to the `BinarySearchTree` constructor:
24 |
25 | ```js
26 | const { BinarySearchTree } = require("@humanwhocodes/binary-search-tree");
27 | ```
28 |
29 | Each instance of `BinarySearchTree` has the following properties and methods:
30 |
31 | ```js
32 | const tree = new BinarySearchTree();
33 |
34 | // add an item to the tree
35 | tree.add(2);
36 |
37 | // determine if a value is in the tree
38 | let found = tree.has(2);
39 |
40 | // get the number of nodes in the tree
41 | let count = tree.size;
42 |
43 | // convert to an array using iterators
44 | let array1 = [...tree.values()];
45 | let array2 = [...tree];
46 |
47 | // remove a node with the given value
48 | let value = tree.delete(2);
49 |
50 | // remove all nodes
51 | tree.clear();
52 | ```
53 |
54 | ## Note on Code Style
55 |
56 | You may find the code style of this module to be overly verbose with a lot of comments. That is intentional, as the primary use of this module is intended to be for educational purposes. There are frequently more concise ways of implementing the details of this class, but the more concise ways are difficult for newcomers who are unfamiliar with linked trees as a concept or JavaScript as a whole.
57 |
58 | ## Issues and Pull Requests
59 |
60 | As this is part of series of tutorials I'm writing, only bug fixes will be accepted. No new functionality will be added to this module.
61 |
62 | ## License
63 |
64 | MIT
--------------------------------------------------------------------------------
/algorithms/checksums/luhn-algorithm/luhn-algorithm.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Luhn algorithm implementation in JavaScript
3 | * Copyright (c) 2009 Nicholas C. Zakas
4 | *
5 | * Permission is hereby granted, free of charge, to any person obtaining a copy
6 | * of this software and associated documentation files (the "Software"), to deal
7 | * in the Software without restriction, including without limitation the rights
8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | * copies of the Software, and to permit persons to whom the Software is
10 | * furnished to do so, subject to the following conditions:
11 | *
12 | * The above copyright notice and this permission notice shall be included in
13 | * all copies or substantial portions of the Software.
14 | *
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | * THE SOFTWARE.
22 | */
23 |
24 |
25 | /**
26 | * Uses Luhn algorithm to validate a numeric identifier.
27 | * @param {String} identifier The identifier to validate.
28 | * @return {Boolean} True if the identifier is valid, false if not.
29 | */
30 | function isValidIdentifier(identifier) {
31 |
32 | var sum = 0,
33 | alt = false,
34 | i = identifier.length-1,
35 | num;
36 |
37 | while (i >= 0){
38 |
39 | //get the next digit
40 | num = parseInt(identifier.charAt(i), 10);
41 |
42 | //if it's not a valid number, abort
43 | if (isNaN(num)){
44 | return false;
45 | }
46 |
47 | //if it's an alternate number...
48 | if (alt) {
49 | num *= 2;
50 | if (num > 9){
51 | num = (num % 10) + 1;
52 | }
53 | }
54 |
55 | //flip the alternate bit
56 | alt = !alt;
57 |
58 | //add to the rest of the sum
59 | sum += num;
60 |
61 | //go to next digit
62 | i--;
63 | }
64 |
65 | //determine if it's valid
66 | return (sum % 10 == 0);
67 | }
--------------------------------------------------------------------------------
/algorithms/sorting/insertion-sort/insertion-sort.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Insertion sort implementation in JavaScript
3 | * Copyright (c) 2012 Nicholas C. Zakas
4 | *
5 | * Permission is hereby granted, free of charge, to any person obtaining a copy
6 | * of items software and associated documentation files (the "Software"), to deal
7 | * in the Software without restriction, including without limitation the rights
8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | * copies of the Software, and to permit persons to whom the Software is
10 | * furnished to do so, subject to the following conditions:
11 | *
12 | * The above copyright notice and items permission notice shall be included in
13 | * all copies or substantial portions of the Software.
14 | *
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | * THE SOFTWARE.
22 | */
23 |
24 | /**
25 | * An insertion sort implementation in JavaScript. The array
26 | * is sorted in-place.
27 | * @param {Array} items An array of items to sort.
28 | * @return {Array} The sorted array.
29 | */
30 | function insertionSort(items) {
31 |
32 | var len = items.length, // number of items in the array
33 | value, // the value currently being compared
34 | i, // index into unsorted section
35 | j; // index into sorted section
36 |
37 | for (i=0; i < len; i++) {
38 |
39 | // store the current value because it may shift later
40 | value = items[i];
41 |
42 | /*
43 | * Whenever the value in the sorted section is greater than the value
44 | * in the unsorted section, shift all items in the sorted section over
45 | * by one. This creates space in which to insert the value.
46 | */
47 | for (j=i-1; j > -1 && items[j] > value; j--) {
48 | items[j+1] = items[j];
49 | }
50 |
51 | items[j+1] = value;
52 | }
53 |
54 | return items;
55 | }
--------------------------------------------------------------------------------
/algorithms/sorting/merge-sort-recursive/merge-sort-recursive.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Recursive merge sort implementation in JavaScript
3 | * Copyright (c) 2009 Nicholas C. Zakas
4 | *
5 | * Permission is hereby granted, free of charge, to any person obtaining a copy
6 | * of this software and associated documentation files (the "Software"), to deal
7 | * in the Software without restriction, including without limitation the rights
8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | * copies of the Software, and to permit persons to whom the Software is
10 | * furnished to do so, subject to the following conditions:
11 | *
12 | * The above copyright notice and this permission notice shall be included in
13 | * all copies or substantial portions of the Software.
14 | *
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | * THE SOFTWARE.
22 | */
23 |
24 | /**
25 | * Merges two arrays in order based on their natural
26 | * relationship.
27 | * @param {Array} left The first array to merge.
28 | * @param {Array} right The second array to merge.
29 | * @return {Array} The merged array.
30 | */
31 | function merge(left, right){
32 | var result = [],
33 | il = 0,
34 | ir = 0;
35 |
36 | while (il < left.length && ir < right.length){
37 | if (left[il] < right[ir]){
38 | result.push(left[il++]);
39 | } else {
40 | result.push(right[ir++]);
41 | }
42 | }
43 |
44 | return result.concat(left.slice(il)).concat(right.slice(ir));
45 | }
46 |
47 | /**
48 | * Sorts an array in ascending natural order using
49 | * merge sort.
50 | * @param {Array} items The array to sort.
51 | * @return {Array} The sorted array.
52 | */
53 | function mergeSort(items){
54 |
55 | if (items.length < 2) {
56 | return items;
57 | }
58 |
59 | var middle = Math.floor(items.length / 2),
60 | left = items.slice(0, middle),
61 | right = items.slice(middle);
62 |
63 | return merge(mergeSort(left), mergeSort(right));
64 | }
65 |
--------------------------------------------------------------------------------
/src/data-structures/linked-list/README.md:
--------------------------------------------------------------------------------
1 | # JavaScript Linked List Class
2 |
3 | by [Nicholas C. Zakas](https://humanwhocodes.com)
4 |
5 | If you find this useful, please consider supporting my work with a [donation](https://humanwhocodes.com/donate).
6 |
7 | ## Overview
8 |
9 | A JavaScript implementation of a linked list. This class uses the conventions of built-in JavaScript collection objects, such as:
10 |
11 | 1. There is a `[Symbol.iterator]` method so each instance is iterable.
12 | 1. The `size` getter property instead of a `length` data property to indicate that the size of the list is dynamically counted rather than stored.
13 | 1. Defining a `values()` generator method.
14 | 1. Returning `undefined` from `get()` when no such index exists.
15 |
16 | Read the [blog post](https://humanwhocodes.com/blog/2019/01/computer-science-in-javascript-linked-list/) about the design of this class.
17 |
18 | ## Usage
19 |
20 | Use CommonJS to get access to the `LinkedList` constructor:
21 |
22 | ```js
23 | const { LinkedList } = require("@humanwhocodes/linked-list");
24 | ```
25 |
26 | Each instance of `LinkedList` has the following properties and methods:
27 |
28 | ```js
29 | const list = new LinkedList();
30 |
31 | // add an item to the end
32 | list.add("foo");
33 |
34 | // insert an item
35 | list.insertBefore("bar", 0);
36 | list.insertAfter("baz", 1);
37 |
38 | // get the value at an index
39 | let value = list.get(0);
40 |
41 | // get the number of items
42 | let count = list.size;
43 |
44 | // get the index of a value
45 | let index = list.indexOf("foo");
46 |
47 | // convert to an array using iterators
48 | let array1 = [...list.values()];
49 | let array2 = [...list];
50 |
51 | // remove an item at the given index and return the data that was removed
52 | let data = list.remove(0);
53 |
54 | // remove all items
55 | list.clear();
56 | ```
57 |
58 | ## Note on Code Style
59 |
60 | You may find the code style of this module to be overly verbose with a lot of comments. That is intentional, as the primary use of this module is intended to be for educational purposes. There are frequently more concise ways of implementing the details of this class, but the more concise ways are difficult for newcomers who are unfamiliar with linked lists as a concept or JavaScript as a whole.
61 |
62 | ## Issues and Pull Requests
63 |
64 | As this is part of series of tutorials I'm writing, only bug fixes will be accepted. No new functionality will be added to this module.
65 |
66 | ## License
67 |
68 | MIT
--------------------------------------------------------------------------------
/src/data-structures/circular-linked-list/README.md:
--------------------------------------------------------------------------------
1 | # JavaScript Circular Linked List Class
2 |
3 | by [Nicholas C. Zakas](https://humanwhocodes.com)
4 |
5 | If you find this useful, please consider supporting my work with a [donation](https://humanwhocodes.com/donate).
6 |
7 | ## Overview
8 |
9 | A JavaScript implementation of a linked list. This class uses the conventions of built-in JavaScript collection objects, such as:
10 |
11 | 1. There is a `[Symbol.iterator]` method so each instance is iterable.
12 | 1. The `size` getter property instead of a `length` data property to indicate that the size of the list is dynamically counted rather than stored.
13 | 1. Defining a `values()` generator method.
14 | 1. Returning `undefined` from `get()` when no such index exists.
15 |
16 | ## Usage
17 |
18 | Use CommonJS to get access to the `CircularLinkedList` constructor:
19 |
20 | ```js
21 | const { CircularLinkedList } = require("@humanwhocodes/circular-linked-list");
22 | ```
23 |
24 | Each instance of `CircularLinkedList` has the following properties and methods:
25 |
26 | ```js
27 | const list = new CircularLinkedList();
28 |
29 | // add an item to the end
30 | list.add("foo");
31 |
32 | // insert an item
33 | list.insertBefore("bar", 0);
34 | list.insertAfter("baz", 1);
35 |
36 | // get the value at an index
37 | let value = list.get(0);
38 |
39 | // get the number of items
40 | let count = list.size;
41 |
42 | // get the index of a value
43 | let index = list.indexOf("foo");
44 |
45 | // convert to an array using iterators
46 | let array1 = [...list.values()];
47 | let array2 = [...list];
48 |
49 | // create a circular iterator to keep iterating over values
50 | const iterator = list.circularValues();
51 |
52 | // remove an item at the given index and return the data that was removed
53 | let data = list.remove(0);
54 |
55 | // remove all items
56 | list.clear();
57 | ```
58 |
59 | ## Note on Code Style
60 |
61 | You may find the code style of this module to be overly verbose with a lot of comments. That is intentional, as the primary use of this module is intended to be for educational purposes. There are frequently more concise ways of implementing the details of this class, but the more concise ways are difficult for newcomers who are unfamiliar with linked lists as a concept or JavaScript as a whole.
62 |
63 | ## Issues and Pull Requests
64 |
65 | As this is part of series of tutorials I'm writing, only bug fixes will be accepted. No new functionality will be added to this module.
66 |
67 | ## License
68 |
69 | MIT
--------------------------------------------------------------------------------
/algorithms/sorting/selection-sort/selection-sort.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Selection sort implementation in JavaScript
3 | * Copyright (c) 2009 Nicholas C. Zakas
4 | *
5 | * Permission is hereby granted, free of charge, to any person obtaining a copy
6 | * of this software and associated documentation files (the "Software"), to deal
7 | * in the Software without restriction, including without limitation the rights
8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | * copies of the Software, and to permit persons to whom the Software is
10 | * furnished to do so, subject to the following conditions:
11 | *
12 | * The above copyright notice and this permission notice shall be included in
13 | * all copies or substantial portions of the Software.
14 | *
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | * THE SOFTWARE.
22 | */
23 |
24 |
25 | /**
26 | * Swaps two values in an array.
27 | * @param {Array} items The array containing the items.
28 | * @param {int} firstIndex Index of first item to swap.
29 | * @param {int} secondIndex Index of second item to swap.
30 | * @return {void}
31 | */
32 | function swap(items, firstIndex, secondIndex){
33 | var temp = items[firstIndex];
34 | items[firstIndex] = items[secondIndex];
35 | items[secondIndex] = temp;
36 | }
37 |
38 | /**
39 | * A selection sort implementation in JavaScript. The array
40 | * is sorted in-place.
41 | * @param {Array} items An array of items to sort.
42 | * @return {Array} The sorted array.
43 | */
44 | function selectionSort(items){
45 |
46 | var len = items.length,
47 | min, i, j;
48 |
49 | for (i=0; i < len; i++){
50 |
51 | // set minimum to this position
52 | min = i;
53 |
54 | // check the rest of the array to see if anything is smaller
55 | for (j=i+1; j < len; j++){
56 | if (items[j] < items[min]){
57 | min = j;
58 | }
59 | }
60 |
61 | // if the minimum isn't in the position, swap it
62 | if (i != min){
63 | swap(items, i, min);
64 | }
65 | }
66 |
67 | return items;
68 | }
--------------------------------------------------------------------------------
/algorithms/sorting/merge-sort-recursive/merge-sort-inplace.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Recursive merge sort implementation in JavaScript
3 | * Copyright (c) 2012 Nicholas C. Zakas
4 | *
5 | * Permission is hereby granted, free of charge, to any person obtaining a copy
6 | * of this software and associated documentation files (the "Software"), to deal
7 | * in the Software without restriction, including without limitation the rights
8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | * copies of the Software, and to permit persons to whom the Software is
10 | * furnished to do so, subject to the following conditions:
11 | *
12 | * The above copyright notice and this permission notice shall be included in
13 | * all copies or substantial portions of the Software.
14 | *
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | * THE SOFTWARE.
22 | */
23 |
24 | /**
25 | * Merges to arrays in order based on their natural
26 | * relationship.
27 | * @param {Array} left The first array to merge.
28 | * @param {Array} right The second array to merge.
29 | * @return {Array} The merged array.
30 | */
31 | function merge(left, right){
32 | var result = [],
33 | il = 0,
34 | ir = 0;
35 |
36 | while (il < left.length && ir < right.length){
37 | if (left[il] < right[ir]){
38 | result.push(left[il++]);
39 | } else {
40 | result.push(right[ir++]);
41 | }
42 | }
43 |
44 | return result.concat(left.slice(il)).concat(right.slice(ir));
45 | }
46 |
47 | /**
48 | * Sorts an array in ascending natural order using
49 | * merge sort.
50 | * @param {Array} items The array to sort.
51 | * @return {Array} The sorted array.
52 | */
53 | function mergeSort(items){
54 |
55 | if (items.length < 2) {
56 | return items;
57 | }
58 |
59 | var middle = Math.floor(items.length / 2),
60 | left = items.slice(0, middle),
61 | right = items.slice(middle),
62 | params = merge(mergeSort(left), mergeSort(right));
63 |
64 | // Add the arguments to replace everything between 0 and last item in the array
65 | params.unshift(0, items.length);
66 | items.splice.apply(items, params);
67 | return items;
68 | }
--------------------------------------------------------------------------------
/src/data-structures/circular-doubly-linked-list/README.md:
--------------------------------------------------------------------------------
1 | # JavaScript Circular Doubly Linked List Class
2 |
3 | by [Nicholas C. Zakas](https://humanwhocodes.com)
4 |
5 | If you find this useful, please consider supporting my work with a [donation](https://humanwhocodes.com/donate).
6 |
7 | ## Overview
8 |
9 | A JavaScript implementation of a circular doubly linked list. This class uses the conventions of built-in JavaScript collection objects, such as:
10 |
11 | 1. There is a `[Symbol.iterator]` method so each instance is iterable.
12 | 1. The `size` getter property instead of a `length` data property to indicate that the size of the list is dynamically counted rather than stored.
13 | 1. Defining a `values()` generator method.
14 | 1. Returning `undefined` from `get()` when no such index exists.
15 |
16 | Read the [blog post](https://humanwhocodes.com/blog/2019/03/computer-science-in-javascript-circular-doubly-linked-lists/) about the design of this class.
17 |
18 | ## Usage
19 |
20 | Use CommonJS to get access to the `CircularDoublyLinkedList` constructor:
21 |
22 | ```js
23 | const { CircularDoublyLinkedList } = require("@humanwhocodes/circular-doubly-linked-list");
24 | ```
25 |
26 | Each instance of `CircularDoublyLinkedList` has the following properties and methods:
27 |
28 | ```js
29 | const list = new CircularDoublyLinkedList();
30 |
31 | // add an item to the end
32 | list.add("foo");
33 |
34 | // insert an item
35 | list.insertBefore("bar", 0);
36 | list.insertAfter("baz", 1);
37 |
38 | // get the value at an index
39 | let value = list.get(0);
40 |
41 | // get the number of items
42 | let count = list.size;
43 |
44 | // get the index of a value
45 | let index = list.indexOf("foo");
46 |
47 | // convert to an array using iterators
48 | let array1 = [...list.values()];
49 | let array2 = [...list];
50 |
51 | // create a circular iterator to keep iterating over values
52 | const iterator = list.circularValues();
53 |
54 | // convert to an array in reverse order using an iterator
55 | let array3 = [...list.reverse()];
56 |
57 | // remove an item at the given index and return the data that was removed
58 | let data = list.remove(0);
59 |
60 | // remove all items
61 | list.clear();
62 | ```
63 |
64 | ## Note on Code Style
65 |
66 | You may find the code style of this module to be overly verbose with a lot of comments. That is intentional, as the primary use of this module is intended to be for educational purposes. There are frequently more concise ways of implementing the details of this class, but the more concise ways are difficult for newcomers who are unfamiliar with linked lists as a concept or JavaScript as a whole.
67 |
68 | ## Issues and Pull Requests
69 |
70 | As this is part of series of tutorials I'm writing, only bug fixes will be accepted. No new functionality will be added to this module.
71 |
72 | ## License
73 |
74 | MIT
--------------------------------------------------------------------------------
/src/data-structures/binary-heap/README.md:
--------------------------------------------------------------------------------
1 | # JavaScript Binary Heap Class
2 |
3 | by [Nicholas C. Zakas](https://humanwhocodes.com)
4 |
5 | If you find this useful, please consider supporting my work with a [donation](https://humanwhocodes.com/donate).
6 |
7 | ## Overview
8 |
9 | A JavaScript implementation of a binary heap. This class uses the conventions of built-in JavaScript collection objects, such as:
10 |
11 | 1. There is a `[Symbol.iterator]` method so each instance is iterable.
12 | 1. The `size` getter property instead of a `length` data property to indicate that the size of the list is dynamically counted rather than stored.
13 | 1. Defining a `values()` generator method.
14 | 1. Using `includes()` instead of `contains()`.
15 |
16 | ## Usage
17 |
18 | Use CommonJS to get access to the `BinaryHeap` constructor:
19 |
20 | ```js
21 | const { BinaryHeap } = require("@humanwhocodes/binary-heap");
22 | ```
23 |
24 | Each instance of `BinaryHeap` has the following properties and methods:
25 |
26 | ```js
27 | const heap = new BinaryHeap();
28 |
29 | // add an item to the end
30 | heap.add("foo");
31 |
32 | // get the minimum value without removing
33 | let value = heap.peek();
34 |
35 | // get the minimum value and remove
36 | let value = heap.poll();
37 |
38 | // get the number of items
39 | let count = heap.size;
40 |
41 | // does the value exist in the heap?
42 | let found = heap.includes(5);
43 |
44 | // convert to an array using iterators
45 | let array1 = [...heap.values()];
46 | let array2 = [...heap];
47 |
48 | // remove all items
49 | heap.clear();
50 | ```
51 |
52 | By default, the `BinaryHeap` class is a min heap designed to work with numbers. You can change the comparator used to determine ordering by passing a function into the constructor, such as:
53 |
54 | ```js
55 | // create a max numeric heap
56 | let heap = new BinaryHeap((a, b) => b - a);
57 | ```
58 |
59 | The comparator function uses the same format as comparator functions for JavaScript arrays, two values are passed in and you must return:
60 |
61 | * A negative number if the first value should come before the second
62 | * Zero if the ordering of the two values should remain unchanged
63 | * A positive number if the first value should come after the second
64 |
65 | ## Note on Code Style
66 |
67 | You may find the code style of this module to be overly verbose with a lot of comments. That is intentional, as the primary use of this module is intended to be for educational purposes. There are frequently more concise ways of implementing the details of this class, but the more concise ways are difficult for newcomers who are unfamiliar with linked lists as a concept or JavaScript as a whole.
68 |
69 | ## Issues and Pull Requests
70 |
71 | As this is part of series of tutorials I'm writing, only bug fixes will be accepted. No new functionality will be added to this module.
72 |
73 | ## License
74 |
75 | MIT
--------------------------------------------------------------------------------
/src/data-structures/doubly-linked-list/README.md:
--------------------------------------------------------------------------------
1 | # JavaScript Doubly Linked List Class
2 |
3 | by [Nicholas C. Zakas](https://humanwhocodes.com)
4 |
5 | If you find this useful, please consider supporting my work with a [donation](https://humanwhocodes.com/donate).
6 |
7 | ## Overview
8 |
9 | A JavaScript implementation of a doubly linked list. This class uses the conventions of built-in JavaScript collection objects, such as:
10 |
11 | 1. There is a `[Symbol.iterator]` method so each instance is iterable.
12 | 1. The `size` getter property instead of a `length` data property to indicate that the size of the list is dynamically counted rather than stored.
13 | 1. Defining a `values()` generator method.
14 | 1. Defining a `find()` method for searching the list to return data.
15 | 1. Defining a `findIndex()` method for searching the list to return an index.
16 | 1. Returning `undefined` from `get()` when no such index exists.
17 |
18 | Read the [blog post](https://humanwhocodes.com/blog/2019/02/computer-science-in-javascript-doubly-linked-lists/) about the design of this class.
19 |
20 | ## Usage
21 |
22 | Use CommonJS to get access to the `DoublyLinkedList` constructor:
23 |
24 | ```js
25 | const { DoublyLinkedList } = require("@humanwhocodes/doubly-linked-list");
26 | ```
27 |
28 | Each instance of `DoublyLinkedList` has the following properties and methods:
29 |
30 | ```js
31 | const list = new DoublyLinkedList();
32 |
33 | // add an item to the end
34 | list.add("foo");
35 |
36 | // insert an item
37 | list.insertBefore("bar", 0);
38 | list.insertAfter("baz", 1);
39 |
40 | // get the value at an index
41 | let value = list.get(0);
42 |
43 | // get the number of items
44 | let count = list.size;
45 |
46 | // get the index of a value
47 | let index = list.indexOf("foo");
48 |
49 | // search for a value
50 | let result = list.find(value => value.length > 3);
51 | let foundIndex = list.findIndex(value => value.length > 3);
52 |
53 | // convert to an array using iterators
54 | let array1 = [...list.values()];
55 | let array2 = [...list];
56 |
57 | // convert to an array in reverse order using an iterator
58 | let array3 = [...list.reverse()];
59 |
60 | // remove an item at the given index and return the data that was removed
61 | let data = list.remove(0);
62 |
63 | // remove all items
64 | list.clear();
65 | ```
66 |
67 | ## Note on Code Style
68 |
69 | You may find the code style of this module to be overly verbose with a lot of comments. That is intentional, as the primary use of this module is intended to be for educational purposes. There are frequently more concise ways of implementing the details of this class, but the more concise ways are difficult for newcomers who are unfamiliar with linked lists as a concept or JavaScript as a whole.
70 |
71 | ## Issues and Pull Requests
72 |
73 | As this is part of series of tutorials I'm writing, only bug fixes will be accepted. No new functionality will be added to this module.
74 |
75 | ## License
76 |
77 | MIT
--------------------------------------------------------------------------------
/algorithms/sorting/merge-sort-iterative/merge-sort-iterative.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Iterative merge sort implementation in JavaScript
3 | * Copyright (c) 2009-2011 Nicholas C. Zakas
4 | *
5 | * Permission is hereby granted, free of charge, to any person obtaining a copy
6 | * of this software and associated documentation files (the "Software"), to deal
7 | * in the Software without restriction, including without limitation the rights
8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | * copies of the Software, and to permit persons to whom the Software is
10 | * furnished to do so, subject to the following conditions:
11 | *
12 | * The above copyright notice and this permission notice shall be included in
13 | * all copies or substantial portions of the Software.
14 | *
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | * THE SOFTWARE.
22 | */
23 |
24 | /**
25 | * Merges to arrays in order based on their natural
26 | * relationship.
27 | * @param {Array} left The first array to merge.
28 | * @param {Array} right The second array to merge.
29 | * @return {Array} The merged array.
30 | */
31 | function merge(left, right){
32 | var result = [];
33 |
34 | while (left.length > 0 && right.length > 0){
35 | if (left[0] < right[0]){
36 | result.push(left.shift());
37 | } else {
38 | result.push(right.shift());
39 | }
40 | }
41 |
42 | result = result.concat(left).concat(right);
43 |
44 | //make sure remaining arrays are empty
45 | left.splice(0, left.length);
46 | right.splice(0, right.length);
47 |
48 | return result;
49 | }
50 |
51 | /**
52 | * Sorts an array in ascending natural order using
53 | * merge sort.
54 | * @param {Array} items The array to sort.
55 | * @return {Array} The sorted array.
56 | */
57 | function mergeSort(items){
58 |
59 | // Terminal condition - don't need to do anything for arrays with 0 or 1 items
60 | if (items.length < 2) {
61 | return items;
62 | }
63 |
64 | var work = [],
65 | i,
66 | len;
67 |
68 |
69 | for (i=0, len=items.length; i < len; i++){
70 | work.push([items[i]]);
71 | }
72 | work.push([]); //in case of odd number of items
73 |
74 | for (var lim=len; lim > 1; lim = Math.floor((lim+1)/2)){
75 | for (var j=0,k=0; k < lim; j++, k+=2){
76 | work[j] = merge(work[k], work[k+1]);
77 | }
78 | work[j] = []; //in case of odd number of items
79 | }
80 |
81 | return work[0];
82 | }
--------------------------------------------------------------------------------
/algorithms/sorting/quicksort/quicksort.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Quick sort implementation in JavaScript
3 | * Copyright (c) 2012 Nicholas C. Zakas
4 | *
5 | * Permission is hereby granted, free of charge, to any person obtaining a copy
6 | * of items software and associated documentation files (the "Software"), to deal
7 | * in the Software without restriction, including without limitation the rights
8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | * copies of the Software, and to permit persons to whom the Software is
10 | * furnished to do so, subject to the following conditions:
11 | *
12 | * The above copyright notice and items permission notice shall be included in
13 | * all copies or substantial portions of the Software.
14 | *
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | * THE SOFTWARE.
22 | */
23 |
24 | /**
25 | * Swaps two values in an array.
26 | * @param {Array} items The array containing the items.
27 | * @param {int} firstIndex Index of first item to swap.
28 | * @param {int} secondIndex Index of second item to swap.
29 | * @return {void}
30 | */
31 | function swap(items, firstIndex, secondIndex){
32 | var temp = items[firstIndex];
33 | items[firstIndex] = items[secondIndex];
34 | items[secondIndex] = temp;
35 | }
36 |
37 | function partition(items, left, right) {
38 |
39 | var pivot = items[Math.floor((right + left) / 2)], // pivot value is middle item
40 | i = left, // starts from left and goes right to pivot index
41 | j = right; // starts from right and goes left to pivot index
42 |
43 |
44 | // while the two indices don't match
45 | while (i <= j) {
46 |
47 | // if the item on the left is less than the pivot, continue right
48 | while (items[i] < pivot) {
49 | i++;
50 | }
51 |
52 | // if the item on the right is greater than the pivot, continue left
53 | while (items[j] > pivot) {
54 | j--;
55 | }
56 |
57 | // if the two indices still don't match, swap the values
58 | if (i <= j) {
59 | swap(items, i, j);
60 |
61 | // change indices to continue loop
62 | i++;
63 | j--;
64 | }
65 | }
66 |
67 | // this value is necessary for recursion
68 | return i;
69 | }
70 |
71 | /**
72 | * A quicksort implementation in JavaScript. The array
73 | * is sorted in place.
74 | * @param {Array} items An array of items to sort.
75 | * @return {Array} The sorted array.
76 | */
77 | function quickSort(items, left, right) {
78 |
79 | var index;
80 |
81 | // performance - don't sort an array with zero or one items
82 | if (items.length > 1) {
83 |
84 | // fix left and right values - might not be provided
85 | left = typeof left != "number" ? 0 : left;
86 | right = typeof right != "number" ? items.length - 1 : right;
87 |
88 | // split up the entire array
89 | index = partition(items, left, right);
90 |
91 | // if the returned index
92 | if (left < index - 1) {
93 | quickSort(items, left, index - 1);
94 | }
95 |
96 | if (index < right) {
97 | quickSort(items, index, right);
98 | }
99 |
100 | }
101 |
102 | return items;
103 | }
104 |
105 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Computer Science in JavaScript
2 |
3 | by [Nicholas C. Zakas](https://humanwhocodes.com)
4 |
5 | If you find this useful, please consider supporting my work with a [donation](https://humanwhocodes.com/donate).
6 |
7 | ## Description
8 |
9 | Collection of classic computer science paradigms, algorithms, and approaches written in JavaScript. This is the source code for the series of blog posts on my website.
10 |
11 | ## Folder Structure
12 |
13 | The most recent packages are found in these directories:
14 |
15 | * `src` - the implementation source code
16 | * `tests` - tests for the implementation source code
17 |
18 | These directories contain **old** implementations that will be replaced eventually, they are just here to avoid confusing people who find this repo through the old blog posts:
19 |
20 | * `data-structures` - data structure implementations that have not been updated yet
21 | * `encodings` - encoding implementations that have not been updated yet
22 | * `algorithms` - miscellanous algorithm implementations that have not been updated yet
23 |
24 | As I update these, implementations will move from these folders into `src`.
25 |
26 | ## Branches
27 |
28 | * **2009** - the branch containing all of the original implementations as reflected in my 2009 blog post series.
29 | * **master** - the branch where I'm updating the original implementations to use ECMAScript 2018 and later features.
30 |
31 | ## Installing
32 |
33 | You must be using Node.js v8 or later.
34 |
35 | First, clone the repo:
36 |
37 | ```
38 | $ git clone git://github.com/humanwhocodes/computer-science-in-javascript.git
39 | $ cd computer-science-in-javascript
40 | ```
41 |
42 | Then install the dependencies:
43 |
44 | ```
45 | $ npm install
46 | ```
47 |
48 | You can then run tests like this:
49 |
50 | ```
51 | $ npm test
52 | ```
53 |
54 | ## Updated Blog Posts (2019)
55 |
56 | These are the most recent blog posts covering the most recent version of the code.
57 |
58 | ### Data Structures
59 |
60 | * [Linked List](https://humanwhocodes.com/blog/2019/01/computer-science-in-javascript-linked-list/)
61 | * [Doubly-Linked List](https://humanwhocodes.com/blog/2019/02/computer-science-in-javascript-doubly-linked-lists/)
62 | * [Circular Doubly-Linked List](https://humanwhocodes.com/blog/2019/03/computer-science-in-javascript-circular-doubly-linked-lists/)
63 |
64 | ## Original Blog Posts
65 |
66 | At some point I will update these blog posts for the new implementations. For now, they still refer only to the 2009 version of this code.
67 |
68 | ### Data Structures
69 |
70 | * Binary Search Tree: [Part 1](https://humanwhocodes.com/blog/2009/06/09/computer-science-in-javascript-binary-search-tree-part-1/), [Part 2](https://humanwhocodes.com/blog/2009/06/16/computer-science-in-javascript-binary-search-tree-part-2/)
71 | * [Doubly Linked List](https://humanwhocodes.com/blog/2009/04/21/computer-science-in-javascript-doubly-linked-lists/)
72 | * [Linked List](https://humanwhocodes.com/blog/2009/04/13/computer-science-in-javascript-linked-list/)
73 |
74 | ### Sorting Algorithms
75 |
76 | * [Bubble Sort](https://humanwhocodes.com/blog/2009/05/26/computer-science-in-javascript-bubble-sort/)
77 | * [Selection Sort](https://humanwhocodes.com/blog/2009/09/08/computer-science-in-javascript-selection-sort/)
78 |
79 | ### Other Algorithms
80 |
81 | * [Base64 Encoding](https://humanwhocodes.com/blog/2009/12/08/computer-science-in-javascript-base64-encoding/)
82 | * [Binary Search](https://humanwhocodes.com/blog/2009/09/01/computer-science-in-javascript-binary-search/)
83 | * [Credit Card Number Validation](https://humanwhocodes.com/blog/2009/08/04/computer-science-in-javascript-credit-card-number-validation/)
84 |
85 | ## Note on Code Style
86 |
87 | You may find the code style of this module to be overly verbose with a lot of comments. That is intentional, as the primary use of this module is intended to be for educational purposes. There are frequently more concise ways of implementing the details of this class, but the more concise ways are difficult for newcomers who are unfamiliar with linked lists as a concept or JavaScript as a whole.
88 |
89 | ## Issues and Pull Requests
90 |
91 | As this is part of series of tutorials I'm writing, only bug fixes will be accepted. No new functionality will be added to this module.
92 |
93 | ## License
94 |
95 | MIT
--------------------------------------------------------------------------------
/encodings/base64/base64.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Base 64 implementation in JavaScript
3 | * Copyright (c) 2009 Nicholas C. Zakas. All rights reserved.
4 | *
5 | * Permission is hereby granted, free of charge, to any person obtaining a copy
6 | * of this software and associated documentation files (the "Software"), to deal
7 | * in the Software without restriction, including without limitation the rights
8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | * copies of the Software, and to permit persons to whom the Software is
10 | * furnished to do so, subject to the following conditions:
11 | *
12 | * The above copyright notice and this permission notice shall be included in
13 | * all copies or substantial portions of the Software.
14 | *
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | * THE SOFTWARE.
22 | */
23 |
24 | /**
25 | * Base64-encodes a string of text.
26 | * @param {String} text The text to encode.
27 | * @return {String} The base64-encoded string.
28 | */
29 | function base64Encode(text){
30 |
31 | if (/([^\u0000-\u00ff])/.test(text)){
32 | throw new Error("Can't base64 encode non-ASCII characters.");
33 | }
34 |
35 | var digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
36 | i = 0,
37 | cur, prev, byteNum,
38 | result=[];
39 |
40 | while(i < text.length){
41 |
42 | cur = text.charCodeAt(i);
43 | byteNum = i % 3;
44 |
45 | switch(byteNum){
46 | case 0: //first byte
47 | result.push(digits.charAt(cur >> 2));
48 | break;
49 |
50 | case 1: //second byte
51 | result.push(digits.charAt((prev & 3) << 4 | (cur >> 4)));
52 | break;
53 |
54 | case 2: //third byte
55 | result.push(digits.charAt((prev & 0x0f) << 2 | (cur >> 6)));
56 | result.push(digits.charAt(cur & 0x3f));
57 | break;
58 | }
59 |
60 | prev = cur;
61 | i++;
62 | }
63 |
64 | if (byteNum == 0){
65 | result.push(digits.charAt((prev & 3) << 4));
66 | result.push("==");
67 | } else if (byteNum == 1){
68 | result.push(digits.charAt((prev & 0x0f) << 2));
69 | result.push("=");
70 | }
71 |
72 | return result.join("");
73 | }
74 |
75 | /**
76 | * Base64-decodes a string of text.
77 | * @param {String} text The text to decode.
78 | * @return {String} The base64-decoded string.
79 | */
80 | function base64Decode(text){
81 |
82 | //ignore white space
83 | text = text.replace(/\s/g,"");
84 |
85 | //first check for any unexpected input
86 | if(!(/^[a-z0-9\+\/\s]+\={0,2}$/i.test(text)) || text.length % 4 > 0){
87 | throw new Error("Not a base64-encoded string.");
88 | }
89 |
90 | //local variables
91 | var digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
92 | cur, prev, digitNum,
93 | i=0,
94 | result = [];
95 |
96 | //remove any equals signs
97 | text = text.replace(/=/g, "");
98 |
99 | //loop over each character
100 | while(i < text.length){
101 |
102 | cur = digits.indexOf(text.charAt(i));
103 | digitNum = i % 4;
104 |
105 | switch(digitNum){
106 |
107 | //case 0: first digit - do nothing, not enough info to work with
108 |
109 | case 1: //second digit
110 | result.push(String.fromCharCode(prev << 2 | cur >> 4));
111 | break;
112 |
113 | case 2: //third digit
114 | result.push(String.fromCharCode((prev & 0x0f) << 4 | cur >> 2));
115 | break;
116 |
117 | case 3: //fourth digit
118 | result.push(String.fromCharCode((prev & 3) << 6 | cur));
119 | break;
120 | }
121 |
122 | prev = cur;
123 | i++;
124 | }
125 |
126 | //return a string
127 | return result.join("");
128 |
129 | }
--------------------------------------------------------------------------------
/encodings/base64/base64.htm:
--------------------------------------------------------------------------------
1 |
2 |
14 |
150 |
151 |
152 |
--------------------------------------------------------------------------------
/tests/data-structures/hash-map/hash-map.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileoverview Hash Map tests
3 | */
4 | /* global it, describe, beforeEach */
5 | "use strict";
6 |
7 | //-----------------------------------------------------------------------------
8 | // Requirements
9 | //-----------------------------------------------------------------------------
10 |
11 | const assert = require("chai").assert;
12 | const { HashMap } = require("../../../src/data-structures/hash-map/hash-map");
13 |
14 | //-----------------------------------------------------------------------------
15 | // Helpers
16 | //-----------------------------------------------------------------------------
17 |
18 | /**
19 | * Check that the contents of the hash map match the values of the array.
20 | * @param {HashMap} map The list to check
21 | * @param {Array} values An array of values that should match.
22 | * @throws {AssertionError} If the values in the list don't match the
23 | * values in the array.
24 | */
25 | function assertHashMapValues(map, values) {
26 | const hashMapValues = [...map];
27 | assert.deepStrictEqual(hashMapValues, values);
28 |
29 | }
30 |
31 | //-----------------------------------------------------------------------------
32 | // Tests
33 | //-----------------------------------------------------------------------------
34 |
35 | describe("HashMap", () => {
36 |
37 | let map;
38 |
39 | beforeEach(() => {
40 | map = new HashMap();
41 | });
42 |
43 | describe("set()", () => {
44 |
45 | it("should store a key in the hash map", () => {
46 | map.set("foo", 1);
47 | assertHashMapValues(map, [["foo", 1]]);
48 | });
49 |
50 | it("should store two keys in the hash map", () => {
51 | map.set("foo", 1);
52 | map.set("bar", 2);
53 | assertHashMapValues(map, [["foo", 1], ["bar", 2]]);
54 | });
55 |
56 | it("should store two keys with the same hash codes in the hash map", () => {
57 | map.set("foo", 1);
58 | map.set("oof", 2);
59 | assertHashMapValues(map, [["foo", 1], ["oof", 2]]);
60 | });
61 |
62 | it("should overwrite a key in the hash map when called twice with the same key", () => {
63 | map.set("foo", 1);
64 | map.set("bar", 2);
65 | map.set("foo", 3);
66 | assertHashMapValues(map, [["foo", 3], ["bar", 2]]);
67 | });
68 |
69 | });
70 |
71 | describe("get()", () => {
72 |
73 | it("should retrieve a key from the hash map", () => {
74 | map.set("foo", 1);
75 | assert.strictEqual(map.get("foo"), 1);
76 | });
77 |
78 | it("should retrieve two keys from the hash map", () => {
79 | map.set("foo", 1);
80 | map.set("bar", 2);
81 | assert.strictEqual(map.get("foo"), 1);
82 | assert.strictEqual(map.get("bar"), 2);
83 | });
84 |
85 | it("should retrieve two keys from the hash map when one key is overwritten", () => {
86 | map.set("foo", 1);
87 | map.set("bar", 2);
88 | map.set("foo", 3);
89 | assert.strictEqual(map.get("foo"), 3);
90 | assert.strictEqual(map.get("bar"), 2);
91 | });
92 |
93 | });
94 |
95 | describe("has()", () => {
96 |
97 | it("should return true when a key exists in the hash map", () => {
98 | map.set("foo", 1);
99 | assert.isTrue(map.has("foo"));
100 | });
101 |
102 | it("should false when a key doesn't exist in the hash map", () => {
103 | map.set("foo", 1);
104 | assert.isFalse(map.has("foox"));
105 | });
106 |
107 | it("should return true when multiple keys exist in the hash map", () => {
108 | map.set("foo", 1);
109 | map.set("bar", 2);
110 | assert.isTrue(map.has("foo"));
111 | assert.isTrue(map.has("bar"));
112 | });
113 |
114 | it("should return true when one key is overwritten", () => {
115 | map.set("foo", 1);
116 | map.set("bar", 2);
117 | map.set("foo", 3);
118 | assert.isTrue(map.has("foo"));
119 | assert.isTrue(map.has("bar"));
120 | });
121 |
122 | });
123 |
124 | describe("delete()", () => {
125 |
126 | it("should delete a key in the hash map", () => {
127 | map.set("foo", 1);
128 | map.delete("foo");
129 | assertHashMapValues(map, []);
130 | });
131 |
132 | it("should return true when a key is deleted from the hash map", () => {
133 | map.set("foo", 1);
134 | assert.isTrue(map.delete("foo"));
135 | });
136 |
137 | it("should return false when a key is not deleted from the hash map", () => {
138 | map.set("foo", 1);
139 | assert.isFalse(map.delete("f"));
140 | });
141 |
142 | it("should return false when the hash map is empty", () => {
143 | assert.isFalse(map.delete("f"));
144 | });
145 |
146 | it("should delete two keys in the hash map", () => {
147 | map.set("foo", 1);
148 | map.set("bar", 2);
149 | map.delete("foo");
150 | map.delete("bar");
151 | assertHashMapValues(map, []);
152 | });
153 |
154 | it("should delete one key when the hash map has two keys", () => {
155 | map.set("foo", 1);
156 | map.set("oof", 2);
157 | map.delete("foo");
158 | assertHashMapValues(map, [["oof", 2]]);
159 | });
160 |
161 | });
162 |
163 | describe("size", () => {
164 |
165 | it("should return the correct size when the hash map has no items", () => {
166 | assert.strictEqual(map.size, 0);
167 | });
168 |
169 | it("should return the correct size when the hash map has one item", () => {
170 | map.set("foo", 1);
171 | assert.strictEqual(map.size, 1);
172 | });
173 |
174 | it("should return the correct size when the hash map has two items", () => {
175 | map.set("bar", 2);
176 | map.set("foo", 1);
177 | assert.strictEqual(map.size, 2);
178 | });
179 |
180 | it("should return the correct size when the hash map has three items", () => {
181 | map.set("bar", 2);
182 | map.set("baz", 3);
183 | map.set("foo", 1);
184 | assert.strictEqual(map.size, 3);
185 | });
186 |
187 | it("should return the correct size when the hash map has four items", () => {
188 | map.set("bar", 2);
189 | map.set("baz", 3);
190 | map.set("foo", 1);
191 | map.set("bazoo", 0);
192 | assert.strictEqual(map.size, 4);
193 | });
194 | });
195 |
196 | ["entries", Symbol.iterator].forEach(method => {
197 |
198 | describe(String(method) + "()", () => {
199 |
200 | it("should create empty array when there are no items", () => {
201 | assert.deepStrictEqual([...map[method]()], []);
202 | });
203 |
204 | it("should iterate over list when there is one item", () => {
205 | map.set("foo", 1);
206 |
207 | assert.deepStrictEqual([...map[method]()], [["foo", 1]]);
208 | });
209 |
210 | it("should iterate over list when there are multiple items", () => {
211 | map.set("foo", 1);
212 | map.set("bar", 2);
213 | assert.deepStrictEqual([...map[method]()], [["foo", 1], ["bar", 2]]);
214 | });
215 |
216 | });
217 |
218 | });
219 |
220 | describe("keys()", () => {
221 |
222 | it("should create empty array when there are no items", () => {
223 | assert.deepStrictEqual([...map.keys()], []);
224 | });
225 |
226 | it("should iterate over list when there is one item", () => {
227 | map.set("foo", 1);
228 |
229 | assert.deepStrictEqual([...map.keys()], ["foo"]);
230 | });
231 |
232 | it("should iterate over list when there are multiple items", () => {
233 | map.set("foo", 1);
234 | map.set("bar", 2);
235 | assert.deepStrictEqual([...map.keys()], ["foo", "bar"]);
236 | });
237 |
238 | });
239 |
240 | describe("values()", () => {
241 |
242 | it("should create empty array when there are no items", () => {
243 | assert.deepStrictEqual([...map.values()], []);
244 | });
245 |
246 | it("should iterate over list when there is one item", () => {
247 | map.set("foo", 1);
248 |
249 | assert.deepStrictEqual([...map.values()], [1]);
250 | });
251 |
252 | it("should iterate over list when there are multiple items", () => {
253 | map.set("foo", 1);
254 | map.set("bar", 2);
255 | assert.deepStrictEqual([...map.values()], [1, 2]);
256 | });
257 |
258 | });
259 |
260 |
261 | });
262 |
--------------------------------------------------------------------------------
/tests/data-structures/binary-search-tree/binary-search-tree.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileoverview BinarySearchTree tests
3 | */
4 | /* global it, describe, beforeEach */
5 |
6 | "use strict";
7 |
8 | //-----------------------------------------------------------------------------
9 | // Requirements
10 | //-----------------------------------------------------------------------------
11 |
12 | const assert = require("chai").assert;
13 | const BinarySearchTree = require("../../../src/data-structures/binary-search-tree").BinarySearchTree;
14 |
15 | //-----------------------------------------------------------------------------
16 | // Helpers
17 | //-----------------------------------------------------------------------------
18 |
19 | /**
20 | * Check that the contents of the tree match the values of the array.
21 | * @param {BinarySearchTree} tree The tree to check
22 | * @param {Array} values An array of values that should match.
23 | * @throws {AssertionError} If the values in the tree don't match the
24 | * values in the array.
25 | */
26 | function assertTreeValues(tree, values) {
27 | const treeValues = [...tree.values()];
28 | assert.deepStrictEqual(treeValues, values);
29 | }
30 |
31 | //-----------------------------------------------------------------------------
32 | // Tests
33 | //-----------------------------------------------------------------------------
34 |
35 | describe("BinarySearchTree", () => {
36 |
37 | let tree;
38 |
39 | beforeEach(() => {
40 | tree = new BinarySearchTree();
41 | });
42 |
43 | describe("add()", () => {
44 |
45 | it("should store an node when one node is added", () => {
46 | tree.add(1);
47 | assertTreeValues(tree, [1]);
48 | });
49 |
50 | it("should store two nodes when two nodes are added", () => {
51 | tree.add(1);
52 | tree.add(2);
53 | assertTreeValues(tree, [1, 2]);
54 | });
55 |
56 | it("should store multiple nodes when multiple nodes are added", () => {
57 | tree.add(2);
58 | tree.add(1);
59 | tree.add(3);
60 | assertTreeValues(tree, [1, 2, 3]);
61 | });
62 |
63 | it("should not store duplicate nodes when multiple nodes are added", () => {
64 | tree.add(2);
65 | tree.add(1);
66 | tree.add(3);
67 | tree.add(1);
68 | assertTreeValues(tree, [1, 2, 3]);
69 | });
70 | });
71 |
72 | describe("has()", () => {
73 |
74 | it("should return true when there is one node in the tree and the value exists", () => {
75 | tree.add(1);
76 | assert.isTrue(tree.has(1));
77 | });
78 |
79 | it("should return false when there is one node in the tree and the value doesn't exist", () => {
80 | tree.add(1);
81 | assert.isFalse(tree.has(2));
82 | });
83 |
84 | it("should return false when there is an empty tree and the value doesn't exist", () => {
85 | assert.isFalse(tree.has(2));
86 | });
87 |
88 | it("should return true when there are two nodes in the tree and the value exists", () => {
89 | tree.add(2);
90 | tree.add(1);
91 | assert.isTrue(tree.has(1));
92 | });
93 |
94 | it("should return false when there is one node in the tree and the value doesn't exist", () => {
95 | tree.add(1);
96 | tree.add(2);
97 | assert.isFalse(tree.has(3));
98 | });
99 |
100 | it("should return true when there are three nodes in the tree and the value exists", () => {
101 | tree.add(2);
102 | tree.add(1);
103 | tree.add(3);
104 | assert.isTrue(tree.has(1));
105 | });
106 |
107 | it("should return false when there is one node in the tree and the value doesn't exist", () => {
108 | tree.add(2);
109 | tree.add(1);
110 | tree.add(3);
111 | assert.isFalse(tree.has(4));
112 | });
113 | });
114 |
115 | describe("delete()", () => {
116 |
117 | it("should delete node when there is only one node in the tree.", () => {
118 | tree.add(10);
119 | tree.delete(10);
120 | assertTreeValues(tree, []);
121 | });
122 |
123 | it("should delete node when tree has two nodes.", () => {
124 | tree.add(10);
125 | tree.add(5);
126 |
127 | tree.delete(10);
128 | assertTreeValues(tree, [5]);
129 | });
130 |
131 | it("should delete node when tree has left subtree nodes only.", () => {
132 | tree.add(10);
133 | tree.add(5);
134 | tree.add(2);
135 |
136 | tree.delete(5);
137 | assertTreeValues(tree, [2, 10]);
138 | });
139 |
140 | it("should delete node when tree has left subtree nodes only and root is removed.", () => {
141 | tree.add(10);
142 | tree.add(5);
143 | tree.add(2);
144 |
145 | tree.delete(10);
146 | assertTreeValues(tree, [2, 5]);
147 | });
148 |
149 | it("should delete node when tree has right subtree nodes only and root is removed.", () => {
150 | tree.add(10);
151 | tree.add(15);
152 | tree.add(20);
153 |
154 | tree.delete(15);
155 | assertTreeValues(tree, [10, 20]);
156 | });
157 |
158 | it("should delete node when tree has right subtree nodes only and root is removed.", () => {
159 | tree.add(10);
160 | tree.add(15);
161 | tree.add(20);
162 |
163 | tree.delete(15);
164 | assertTreeValues(tree, [10, 20]);
165 | });
166 |
167 | it("should remove node when there are two children in shallow tree", () => {
168 | tree.add(7);
169 | tree.add(11);
170 | tree.add(8);
171 | tree.add(13);
172 |
173 | tree.delete(11);
174 | assertTreeValues(tree, [7, 8, 13]);
175 | });
176 |
177 | it("should remove node when there are three children in shallow tree", () => {
178 | tree.add(7);
179 | tree.add(11);
180 | tree.add(8);
181 | tree.add(13);
182 | tree.add(9);
183 |
184 | tree.delete(11);
185 | assertTreeValues(tree, [7, 8, 9, 13]);
186 | });
187 |
188 | it("should remove node when there are two children in deep tree", () => {
189 |
190 | tree.add(8);
191 | tree.add(3);
192 | tree.add(1);
193 | tree.add(10);
194 | tree.add(6);
195 | tree.add(4);
196 | tree.add(7);
197 | tree.add(14);
198 | tree.add(13);
199 |
200 | tree.delete(3);
201 | assertTreeValues(tree, [1, 4, 6, 7, 8, 10, 13, 14]);
202 | });
203 |
204 | it("should remove an node when there is only one node", () => {
205 | tree.add(1);
206 | assertTreeValues(tree, [1]);
207 |
208 | tree.delete(1);
209 | assertTreeValues(tree, []);
210 | });
211 |
212 | it("should remove an node when multiple nodes are in the tree and the middle node is removed", () => {
213 | tree.add(1);
214 | tree.add(2);
215 | tree.add(3);
216 | assertTreeValues(tree, [1, 2, 3]);
217 |
218 | // remove middle node
219 | tree.delete(2);
220 | assertTreeValues(tree, [1, 3]);
221 | });
222 |
223 | });
224 |
225 | describe("clear()", () => {
226 |
227 | it("should not throw an error when the tree has no nodes", () => {
228 | assertTreeValues(tree, []);
229 |
230 | tree.clear();
231 | assertTreeValues(tree, []);
232 | });
233 |
234 | it("should remove all nodes when the tree has one node", () => {
235 | tree.add(1);
236 | assertTreeValues(tree, [1]);
237 |
238 | tree.clear();
239 | assertTreeValues(tree, []);
240 | });
241 |
242 | it("should remove all nodes when the tree has multiple nodes", () => {
243 | tree.add(1);
244 | tree.add(2);
245 | assertTreeValues(tree, [1, 2]);
246 |
247 | tree.clear();
248 | assertTreeValues(tree, []);
249 | });
250 |
251 | });
252 |
253 |
254 | describe("size", () => {
255 |
256 | it("should return 0 when the tree is empty", () => {
257 | assert.strictEqual(tree.size, 0);
258 | });
259 |
260 | it("should return 1 when the tree has one node", () => {
261 | tree.add(1);
262 | assert.strictEqual(tree.size, 1);
263 | });
264 |
265 | it("should return 2 when the tree has two nodes", () => {
266 | tree.add(1);
267 | tree.add(2);
268 | assert.strictEqual(tree.size, 2);
269 | });
270 |
271 | it("should return 3 when the tree has three nodes", () => {
272 | tree.add(2);
273 | tree.add(1);
274 | tree.add(4);
275 | assert.strictEqual(tree.size, 3);
276 | });
277 |
278 | it("should return 5 when the tree has five nodes", () => {
279 | tree.add(2);
280 | tree.add(1);
281 | tree.add(4);
282 | tree.add(9);
283 | tree.add(12);
284 | assert.strictEqual(tree.size, 5);
285 | });
286 |
287 | });
288 |
289 |
290 | ["values", Symbol.iterator].forEach(method => {
291 |
292 | describe(String(method) + "()", () => {
293 |
294 | it("should create empty array when there are no nodes", () => {
295 | assert.deepStrictEqual([...tree[method]()], []);
296 | });
297 |
298 | it("should iterate over tree when there is one node", () => {
299 | tree.add(1);
300 |
301 | assert.deepStrictEqual([...tree[method]()], [1]);
302 | });
303 |
304 | it("should iterate over tree when there are multiple nodes", () => {
305 | tree.add(1);
306 | tree.add(2);
307 | tree.add(3);
308 |
309 | assert.deepStrictEqual([...tree[method]()], [1, 2, 3]);
310 | });
311 |
312 | });
313 |
314 | });
315 |
316 |
317 |
318 | });
319 |
--------------------------------------------------------------------------------
/tests/data-structures/binary-heap/binary-heap.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileoverview Doubly Linked List tests
3 | */
4 | /* global it, describe, beforeEach */
5 | "use strict";
6 |
7 | //-----------------------------------------------------------------------------
8 | // Requirements
9 | //-----------------------------------------------------------------------------
10 |
11 | const assert = require("chai").assert;
12 | const { BinaryHeap } = require("../../../src/data-structures/binary-heap/binary-heap");
13 |
14 | //-----------------------------------------------------------------------------
15 | // Helpers
16 | //-----------------------------------------------------------------------------
17 |
18 | /**
19 | * Check that the contents of the heap match the values of the array.
20 | * @param {BinaryHeap} heap The list to check
21 | * @param {Array} values An array of values that should match.
22 | * @throws {AssertionError} If the values in the list don't match the
23 | * values in the array.
24 | */
25 | function assertHeapValues(heap, values) {
26 | const heapValues = [...heap.values()];
27 | assert.deepStrictEqual(heapValues, values);
28 |
29 | }
30 |
31 | //-----------------------------------------------------------------------------
32 | // Tests
33 | //-----------------------------------------------------------------------------
34 |
35 | describe("BinaryHeap", () => {
36 |
37 | let heap;
38 |
39 | beforeEach(() => {
40 | heap = new BinaryHeap();
41 | });
42 |
43 | describe("add()", () => {
44 |
45 | it("should store an item when one item is added", () => {
46 | heap.add(1);
47 | assertHeapValues(heap, [1]);
48 | });
49 |
50 | it("should store two items when multiple items are added", () => {
51 | heap.add(2);
52 | heap.add(1);
53 |
54 | assertHeapValues(heap, [1,2]);
55 | });
56 |
57 | it("should store two items when multiple items are added", () => {
58 | heap.add(2);
59 | heap.add(3);
60 | assertHeapValues(heap, [2,3]);
61 | });
62 |
63 | it("should store three items when multiple items are added", () => {
64 | heap.add(2);
65 | heap.add(3);
66 | heap.add(1);
67 | assertHeapValues(heap, [1,3,2]);
68 | });
69 |
70 | it("should store four items when multiple items are added", () => {
71 | heap.add(2);
72 | heap.add(3);
73 | heap.add(1);
74 | heap.add(0);
75 | assertHeapValues(heap, [0,1,2,3]);
76 | });
77 | });
78 |
79 | describe("size", () => {
80 |
81 | it("should return the correct size when the heap has no items", () => {
82 | assert.strictEqual(heap.size, 0);
83 | });
84 |
85 | it("should return the correct size when the heap has one item", () => {
86 | heap.add(1);
87 | assert.strictEqual(heap.size, 1);
88 | });
89 |
90 | it("should return the correct size when the heap has two items", () => {
91 | heap.add(2);
92 | heap.add(1);
93 | assert.strictEqual(heap.size, 2);
94 | });
95 |
96 | it("should return the correct size when the heap has three items", () => {
97 | heap.add(2);
98 | heap.add(3);
99 | heap.add(1);
100 | assert.strictEqual(heap.size, 3);
101 | });
102 |
103 | it("should return the correct size when the heap has four items", () => {
104 | heap.add(2);
105 | heap.add(3);
106 | heap.add(1);
107 | heap.add(0);
108 | assert.strictEqual(heap.size, 4);
109 | });
110 | });
111 |
112 | describe("isEmpty()", () => {
113 |
114 | it("should return true when the heap is empty", () => {
115 | assert.isTrue(heap.isEmpty());
116 | });
117 |
118 | it("should return false when the heap has one item", () => {
119 | heap.add(1);
120 | assert.isFalse(heap.isEmpty());
121 | });
122 |
123 | it("should return false when the heap has two items", () => {
124 | heap.add(2);
125 | heap.add(1);
126 | assert.isFalse(heap.isEmpty());
127 | });
128 |
129 | });
130 |
131 | describe("includes()", () => {
132 |
133 | it("should return false when the heap is empty", () => {
134 | assert.isFalse(heap.includes(5));
135 | });
136 |
137 | it("should return true when the item is found", () => {
138 | heap.add(1);
139 | assert.isTrue(heap.includes(1));
140 | });
141 |
142 | it("should return false when the item is not found", () => {
143 | heap.add(1);
144 | assert.isFalse(heap.includes(10));
145 | });
146 |
147 | it("should return true when the heap has two items and the item is found", () => {
148 | heap.add(2);
149 | heap.add(1);
150 | assert.isTrue(heap.includes(2));
151 | });
152 |
153 | });
154 |
155 | describe("peek()", () => {
156 |
157 | it("should return the only item from a one-item heap", () => {
158 | heap.add(1);
159 | assert.strictEqual(heap.peek(), 1);
160 | assert.strictEqual(heap.size, 1);
161 | });
162 |
163 | it("should return the lowest value from a two-item heap", () => {
164 | heap.add(2);
165 | heap.add(1);
166 | assert.strictEqual(heap.peek(), 1);
167 | assert.strictEqual(heap.size, 2);
168 | });
169 |
170 | it("should return the lowest value from a three-item heap", () => {
171 | heap.add(2);
172 | heap.add(3);
173 | heap.add(1);
174 | assert.strictEqual(heap.peek(), 1);
175 | assert.strictEqual(heap.size, 3);
176 | });
177 |
178 | it("should return the lowest value from a four-item heap", () => {
179 | heap.add(2);
180 | heap.add(3);
181 | heap.add(1);
182 | heap.add(0);
183 | assert.strictEqual(heap.peek(), 0);
184 | assert.strictEqual(heap.size, 4);
185 | });
186 | });
187 |
188 | describe("poll()", () => {
189 |
190 | it("should return the only item from a one-item heap", () => {
191 | heap.add(1);
192 | assert.strictEqual(heap.poll(), 1);
193 | assert.strictEqual(heap.size, 0);
194 | assertHeapValues(heap, []);
195 | });
196 |
197 | it("should return the lowest value from a two-item heap", () => {
198 | heap.add(2);
199 | heap.add(1);
200 | assert.strictEqual(heap.poll(), 1);
201 | assert.strictEqual(heap.size, 1);
202 | assertHeapValues(heap, [2]);
203 | });
204 |
205 | it("should return the lowest value from a three-item heap", () => {
206 | heap.add(2);
207 | heap.add(3);
208 | heap.add(1);
209 | assert.strictEqual(heap.poll(), 1);
210 | assert.strictEqual(heap.size, 2);
211 | assertHeapValues(heap, [2,3]);
212 | });
213 |
214 | it("should return the lowest value from a four-item heap", () => {
215 | heap.add(2);
216 | heap.add(3);
217 | heap.add(1);
218 | heap.add(0);
219 | assert.strictEqual(heap.poll(), 0);
220 | assert.strictEqual(heap.size, 3);
221 | assertHeapValues(heap, [1,3,2]);
222 | });
223 | });
224 |
225 | describe("Custom Comparator", () => {
226 |
227 | beforeEach(() => {
228 | heap = new BinaryHeap((a, b) => b - a);
229 | });
230 |
231 | describe("add()", () => {
232 |
233 | it("should store an item when one item is added", () => {
234 | heap.add(1);
235 | assertHeapValues(heap, [1]);
236 | });
237 |
238 | it("should store two items when multiple items are added", () => {
239 | heap.add(2);
240 | heap.add(1);
241 |
242 | assertHeapValues(heap, [2, 1]);
243 | });
244 |
245 | it("should store two items when multiple items are added", () => {
246 | heap.add(2);
247 | heap.add(3);
248 | assertHeapValues(heap, [3, 2]);
249 | });
250 |
251 | it("should store three items when multiple items are added", () => {
252 | heap.add(2);
253 | heap.add(3);
254 | heap.add(1);
255 | assertHeapValues(heap, [3, 2, 1]);
256 | });
257 |
258 | it("should store four items when multiple items are added", () => {
259 | heap.add(2);
260 | heap.add(3);
261 | heap.add(1);
262 | heap.add(0);
263 | assertHeapValues(heap, [3, 2, 1, 0]);
264 | });
265 | });
266 |
267 | describe("peek()", () => {
268 |
269 | it("should return the only item from a one-item heap", () => {
270 | heap.add(1);
271 | assert.strictEqual(heap.peek(), 1);
272 | assert.strictEqual(heap.size, 1);
273 | });
274 |
275 | it("should return the highest value from a two-item heap", () => {
276 | heap.add(2);
277 | heap.add(1);
278 | assert.strictEqual(heap.peek(), 2);
279 | assert.strictEqual(heap.size, 2);
280 | });
281 |
282 | it("should return the highest value from a three-item heap", () => {
283 | heap.add(2);
284 | heap.add(3);
285 | heap.add(1);
286 | assert.strictEqual(heap.peek(), 3);
287 | assert.strictEqual(heap.size, 3);
288 | });
289 |
290 | it("should return the highest value from a four-item heap", () => {
291 | heap.add(2);
292 | heap.add(3);
293 | heap.add(1);
294 | heap.add(0);
295 | assert.strictEqual(heap.peek(), 3);
296 | assert.strictEqual(heap.size, 4);
297 | });
298 | });
299 |
300 | describe("poll()", () => {
301 |
302 | it("should return the only item from a one-item heap", () => {
303 | heap.add(1);
304 | assert.strictEqual(heap.poll(), 1);
305 | assert.strictEqual(heap.size, 0);
306 | assertHeapValues(heap, []);
307 | });
308 |
309 | it("should return the highest value from a two-item heap", () => {
310 | heap.add(2);
311 | heap.add(1);
312 | assert.strictEqual(heap.poll(), 2);
313 | assert.strictEqual(heap.size, 1);
314 | assertHeapValues(heap, [1]);
315 | });
316 |
317 | it("should return the highest value from a three-item heap", () => {
318 | heap.add(2);
319 | heap.add(3);
320 | heap.add(1);
321 | assert.strictEqual(heap.poll(), 3);
322 | assert.strictEqual(heap.size, 2);
323 | assertHeapValues(heap, [2, 1]);
324 | });
325 |
326 | it("should return the highest value from a four-item heap", () => {
327 | heap.add(2);
328 | heap.add(3);
329 | heap.add(1);
330 | heap.add(0);
331 | assert.strictEqual(heap.poll(), 3);
332 | assert.strictEqual(heap.size, 3);
333 | assertHeapValues(heap, [2, 0, 1]);
334 | });
335 | });
336 | });
337 |
338 | });
339 |
--------------------------------------------------------------------------------
/tests/data-structures/linked-list/linked-list.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileoverview Linked List tests
3 | */
4 | /* global it, describe, beforeEach */
5 |
6 | "use strict";
7 |
8 | //-----------------------------------------------------------------------------
9 | // Requirements
10 | //-----------------------------------------------------------------------------
11 |
12 | const assert = require("chai").assert;
13 | const LinkedList = require("../../../src/data-structures/linked-list").LinkedList;
14 |
15 | //-----------------------------------------------------------------------------
16 | // Helpers
17 | //-----------------------------------------------------------------------------
18 |
19 | /**
20 | * Check that the contents of the list match the values of the array.
21 | * @param {LinkedList} list The list to check
22 | * @param {Array} values An array of values that should match.
23 | * @throws {AssertionError} If the values in the list don't match the
24 | * values in the array.
25 | */
26 | function assertListValues(list, values) {
27 | const listValues = [...list.values()];
28 | assert.deepStrictEqual(listValues, values);
29 | }
30 |
31 | //-----------------------------------------------------------------------------
32 | // Tests
33 | //-----------------------------------------------------------------------------
34 |
35 | describe("LinkedList", () => {
36 |
37 | let list;
38 |
39 | beforeEach(() => {
40 | list = new LinkedList();
41 | });
42 |
43 | describe("add()", () => {
44 |
45 | it("should store an item when one item is added", () => {
46 | list.add(1);
47 | assertListValues(list, [1]);
48 | });
49 |
50 | it("should store multiple items when multiple items are added", () => {
51 | list.add(1);
52 | list.add(2);
53 | assertListValues(list, [1, 2]);
54 | });
55 | });
56 |
57 | describe("insertBefore()", () => {
58 |
59 | it("should store an item when one item is inserted at the start", () => {
60 | list.add(1);
61 | list.add(2);
62 | list.add(3);
63 | assertListValues(list, [1, 2, 3]);
64 |
65 | list.insertBefore(0, 0);
66 | assertListValues(list, [0, 1, 2, 3]);
67 | });
68 |
69 | it("should store an item when one item is inserted in the middle", () => {
70 | list.add(1);
71 | list.add(2);
72 | list.add(3);
73 | assertListValues(list, [1, 2, 3]);
74 |
75 | list.insertBefore(1.5, 1);
76 | assertListValues(list, [1, 1.5, 2, 3]);
77 | });
78 |
79 | it("should store an item when one item is inserted at the end", () => {
80 | list.add(1);
81 | list.add(2);
82 | list.add(3);
83 | assertListValues(list, [1, 2, 3]);
84 |
85 | list.insertBefore(2.5, 2);
86 | assertListValues(list, [1, 2, 2.5, 3]);
87 | });
88 |
89 | it("should throw an error when the list is empty", () => {
90 | assert.throws(() => {
91 | list.insertBefore(1, 0);
92 | }, "Index 0 does not exist in the list.");
93 | });
94 |
95 | it("should throw an error when the index is out of range", () => {
96 | list.add(1);
97 | list.add(2);
98 | list.add(3);
99 | assertListValues(list, [1, 2, 3]);
100 |
101 | assert.throws(() => {
102 | list.insertBefore(1, 5);
103 | }, "Index 5 does not exist in the list.");
104 | });
105 |
106 | });
107 |
108 | describe("insertAfter()", () => {
109 |
110 | it("should store an item when one item is inserted at the start", () => {
111 | list.add(1);
112 | list.add(2);
113 | list.add(3);
114 | assertListValues(list, [1, 2, 3]);
115 |
116 | list.insertAfter(1.5, 0);
117 | assertListValues(list, [1, 1.5, 2, 3]);
118 | });
119 |
120 | it("should store an item when one item is inserted in the middle", () => {
121 | list.add(1);
122 | list.add(2);
123 | list.add(3);
124 | assertListValues(list, [1, 2, 3]);
125 |
126 | list.insertAfter(2.5, 1);
127 | assertListValues(list, [1, 2, 2.5, 3]);
128 | });
129 |
130 | it("should store an item when one item is inserted at the end", () => {
131 | list.add(1);
132 | list.add(2);
133 | list.add(3);
134 | assertListValues(list, [1, 2, 3]);
135 |
136 | list.insertAfter(3.5, 2);
137 | assertListValues(list, [1, 2, 3, 3.5]);
138 | });
139 |
140 | it("should throw an error when the list is empty", () => {
141 | assert.throws(() => {
142 | list.insertAfter(1, 0);
143 | }, "Index 0 does not exist in the list.");
144 | });
145 |
146 | it("should throw an error when the index is out of range", () => {
147 | list.add(1);
148 | list.add(2);
149 | list.add(3);
150 | assertListValues(list, [1, 2, 3]);
151 |
152 | assert.throws(() => {
153 | list.insertAfter(1, 5);
154 | }, "Index 5 does not exist in the list.");
155 | });
156 |
157 | });
158 |
159 | describe("get()", () => {
160 |
161 | it("should return the first item when get(0) is called", () => {
162 | list.add(1);
163 | assert.strictEqual(list.get(0), 1);
164 | });
165 |
166 | it("should return the correct value when get() is called multiple times", () => {
167 | list.add(1);
168 | list.add(2);
169 | assert.strictEqual(list.get(0), 1);
170 | assert.strictEqual(list.get(1), 2);
171 | });
172 |
173 | it("should return undefined when get() is called with -1", () => {
174 | assert.strictEqual(list.get(-1), undefined);
175 | });
176 |
177 | it("should return undefined when get() is called with an out-of-range index in an empty list", () => {
178 | assert.strictEqual(list.get(1), undefined);
179 | });
180 |
181 | it("should return undefined when get() is called with an out-of-range index in a non-empty list", () => {
182 | list.add(1);
183 | list.add(2);
184 | assert.strictEqual(list.get(5), undefined);
185 | });
186 |
187 | });
188 |
189 | describe("remove()", () => {
190 |
191 | it("should remove an item when there is only one item", () => {
192 | list.add(1);
193 | assertListValues(list, [1]);
194 |
195 | assert.strictEqual(list.remove(0), 1);
196 | assertListValues(list, []);
197 | });
198 |
199 | it("should remove an item when multiple items are in the list and the middle item is removed", () => {
200 | list.add(1);
201 | list.add(2);
202 | list.add(3);
203 | assertListValues(list, [1, 2, 3]);
204 |
205 | // remove middle item
206 | assert.strictEqual(list.remove(1), 2);
207 | assertListValues(list, [1, 3]);
208 | });
209 |
210 | it("should remove an item when multiple items are in the list and the last item is removed", () => {
211 | list.add(1);
212 | list.add(2);
213 | list.add(3);
214 | assertListValues(list, [1, 2, 3]);
215 |
216 | // remove last item
217 | assert.strictEqual(list.remove(2), 3);
218 | assertListValues(list, [1, 2]);
219 | });
220 |
221 | it("should remove an item when multiple items are in the list and the first item is removed", () => {
222 | list.add(1);
223 | list.add(2);
224 | list.add(3);
225 | assertListValues(list, [1, 2, 3]);
226 |
227 | // remove first item
228 | assert.strictEqual(list.remove(0), 1);
229 | assertListValues(list, [2, 3]);
230 | });
231 |
232 | it("should throw an error when multiple items are in the list and an out-of-bounds index is used", () => {
233 | list.add(1);
234 | list.add(2);
235 | list.add(3);
236 | assertListValues(list, [1, 2, 3]);
237 |
238 | // remove unknown item
239 | assert.throws(() => {
240 | list.remove(5);
241 | }, "Index 5 does not exist in the list.");
242 | });
243 |
244 | it("should throw an error when multiple items are in the list and a negative index is used", () => {
245 | list.add(1);
246 | list.add(2);
247 | list.add(3);
248 | assertListValues(list, [1, 2, 3]);
249 |
250 | // remove unknown item
251 | assert.throws(() => {
252 | list.remove(-1);
253 | }, "Index -1 does not exist in the list.");
254 | });
255 |
256 | it("should throw an error when the list is empty", () => {
257 | assert.throws(() => {
258 | list.remove(0);
259 | }, "Index 0 does not exist in the list.");
260 | });
261 | });
262 |
263 | describe("clear()", () => {
264 |
265 | it("should not throw an error when the list has no items", () => {
266 | assertListValues(list, []);
267 |
268 | list.clear();
269 | assertListValues(list, []);
270 | });
271 |
272 | it("should remove all items when the list has one item", () => {
273 | list.add(1);
274 | assertListValues(list, [1]);
275 |
276 | list.clear();
277 | assertListValues(list, []);
278 | });
279 |
280 | it("should remove all items when the list has multiple items", () => {
281 | list.add(1);
282 | list.add(2);
283 | assertListValues(list, [1, 2]);
284 |
285 | list.clear();
286 | assertListValues(list, []);
287 | });
288 |
289 | });
290 |
291 |
292 | describe("size", () => {
293 |
294 | it("should return 0 when the list is empty", () => {
295 | assert.strictEqual(list.size, 0);
296 | });
297 |
298 | it("should return 1 when the list has one item", () => {
299 | list.add(1);
300 | assert.strictEqual(list.size, 1);
301 | });
302 |
303 | it("should return 2 when the list has two items", () => {
304 | list.add(1);
305 | list.add(2);
306 | assert.strictEqual(list.size, 2);
307 | });
308 |
309 | });
310 |
311 | describe("indexOf()", () => {
312 |
313 | it("should return -1 when the list is empty", () => {
314 | assert.strictEqual(list.indexOf(1), -1);
315 | });
316 |
317 | it("should return 0 when the list has one item", () => {
318 | list.add(1);
319 | assert.strictEqual(list.indexOf(1), 0);
320 | });
321 |
322 | it("should return 1 when the list has two items", () => {
323 | list.add(1);
324 | list.add(2);
325 | assert.strictEqual(list.indexOf(2), 1);
326 | });
327 |
328 | it("should return -1 when the list doesn't contain the value", () => {
329 | list.add(1);
330 | list.add(2);
331 | assert.strictEqual(list.indexOf(3), -1);
332 | });
333 |
334 | });
335 |
336 | ["values", Symbol.iterator].forEach(method => {
337 |
338 | describe(String(method) + "()", () => {
339 |
340 | it("should create empty array when there are no items", () => {
341 | assert.deepStrictEqual([...list[method]()], []);
342 | });
343 |
344 | it("should iterate over list when there is one item", () => {
345 | list.add(1);
346 |
347 | assert.deepStrictEqual([...list[method]()], [1]);
348 | });
349 |
350 | it("should iterate over list when there are multiple items", () => {
351 | list.add(1);
352 | list.add(2);
353 | list.add(3);
354 |
355 | assert.deepStrictEqual([...list[method]()], [1, 2, 3]);
356 | });
357 |
358 | });
359 |
360 | });
361 |
362 |
363 |
364 | });
365 |
--------------------------------------------------------------------------------
/src/data-structures/binary-heap/binary-heap.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * @fileoverview Binary Heap implementation in JavaScript
4 | */
5 |
6 | //-----------------------------------------------------------------------------
7 | // Private
8 | //-----------------------------------------------------------------------------
9 |
10 | /**
11 | * Determines the index in an array that is the parent of the given index.
12 | * @param {int} index The index to find the parent of.
13 | * @returns {int} The index of the parent value.
14 | * @private
15 | */
16 | function getParentIndex(index) {
17 | return Math.floor((index - 1) / 2);
18 | }
19 |
20 | /**
21 | * Determines the index in an array that is the left child of the given index.
22 | * @param {int} index The index to find the left child of.
23 | * @returns {int} The index of the left child value.
24 | * @private
25 | */
26 | function getLeftChildIndex(index) {
27 | return (index * 2) + 1;
28 | }
29 |
30 | /**
31 | * Determines the index in an array that is the right child of the given index.
32 | * @param {int} index The index to find the right child of.
33 | * @returns {int} The index of the right child value.
34 | * @private
35 | */
36 | function getRightChildIndex(index) {
37 | return (index * 2) + 2;
38 | }
39 |
40 | /**
41 | * Determines if a left child exists for the given index in the array.
42 | * @param {Array} array The array to check.
43 | * @param {int} index The index to check.
44 | * @returns {boolean} True if the index has a left child, false if not.
45 | * @private
46 | */
47 | function hasLeftChild(array, index) {
48 | return getLeftChildIndex(index) < array.length;
49 | }
50 |
51 | /**
52 | * Determines if a right child exists for the given index in the array.
53 | * @param {Array} array The array to check.
54 | * @param {int} index The index to check.
55 | * @returns {boolean} True if the index has a right child, false if not.
56 | * @private
57 | */
58 | function hasRightChild(array, index) {
59 | return getRightChildIndex(index) < array.length;
60 | }
61 |
62 | /**
63 | * Swaps the positions of two values in an array.
64 | * @param {Array} array The array to swap values in.
65 | * @param {int} index1 The first index to swap.
66 | * @param {int} index2 The second index to swap.
67 | * @returns {void}
68 | * @private
69 | */
70 | function swap(array, index1, index2) {
71 | const value = array[index1];
72 | array[index1] = array[index2];
73 | array[index2] = value;
74 | }
75 |
76 | /**
77 | * Normalizes the heap by starting with the last inserted item and ensuring
78 | * the entire path up to the heap root is in the correct order.
79 | * @param {Array} array The array to adjust.
80 | * @param {Function} compare The comparator to use on values in the array.
81 | * @returns {void}
82 | * @private
83 | */
84 | function heapifyUp(array, compare) {
85 |
86 | /*
87 | * `currentIndex` is used to traverse the array. It starts at the last item
88 | * in the array and moves to the first item (the heap root).
89 | */
90 | let currentIndex = array.length - 1;
91 |
92 | /*
93 | * This loop continues so long as `currentIndex` is not the heap root (in
94 | * position 0). When `currentIndex` is 0, it means the path from the last
95 | * inserted value to the heap root is correct.
96 | */
97 | while (currentIndex > 0) {
98 |
99 | // get the index of this value's parent so we can get the value
100 | let parentIndex = getParentIndex(currentIndex);
101 |
102 | /*
103 | * If the value of the parent should come after the current value
104 | * (pointed to by `currentIndex`), then swap the two values so they
105 | * are in the correct order. Note that any value returned from the
106 | * comparator that is greater than zero means that the parent value
107 | * should come after the current value.
108 | */
109 | if (compare(array[parentIndex], array[currentIndex]) > 0) {
110 | swap(array, parentIndex, currentIndex);
111 |
112 | // move the current index to the parent so the loop can continue
113 | currentIndex = parentIndex;
114 | } else {
115 |
116 | /*
117 | * If we've reached here then the parent and current values are
118 | * already in the correct order. We can infer that this means
119 | * the rest of the path up to the root is also in the correct order
120 | * and so we can safely exit the loop.
121 | */
122 | break;
123 | }
124 | }
125 | }
126 |
127 | /**
128 | * Normalizes the heap by starting with the root item and ensuring
129 | * the entire heap is in the correct order. This is run after a node is
130 | * removed from the heap and the root is replaced with a value, so
131 | * the root is most likely incorrect.
132 | * @param {Array} array The array to adjust.
133 | * @param {Function} compare The comparator to use on values in the array.
134 | * @returns {void}
135 | * @private
136 | */
137 | function heapifyDown(array, compare) {
138 |
139 | /*
140 | * `currentIndex` is used to traverse the array. It starts at the first item
141 | * in the array and moves towards the last.
142 | */
143 | let currentIndex = 0;
144 |
145 | /*
146 | * This loop continues so long as the current item has at least one child.
147 | * Because the heap is filled in starting with the left child and then
148 | * moving to the right, simply checking if a left child is present is enough
149 | * to continue because we know there is at least one child.
150 | *
151 | * When the current item has no children, we know the entire path there is
152 | * in the correct state and so we can exit.
153 | */
154 | while (hasLeftChild(array, currentIndex)) {
155 |
156 | /*
157 | * This variable is called `smallerChildIndex` because we want to
158 | * identify which of the two children contain the smaller value.
159 | * We can start by assuming this will be the left child and then
160 | * change it if we discover the right child is actually smaller.
161 | */
162 | let smallerChildIndex = getLeftChildIndex(currentIndex);
163 |
164 | // if there is a right child, check that
165 | if (hasRightChild(array, currentIndex)) {
166 | let rightChildIndex = getRightChildIndex(currentIndex);
167 |
168 | /*
169 | * If the right child value should come after the left child value
170 | * (meaning the `compare()` function returns a value greater than
171 | * zero), then note that the smaller value is actually in the right
172 | * child by storing the right child index in `smallerChildIndex`.
173 | */
174 | if (compare(array[smallerChildIndex], array[rightChildIndex]) > 0) {
175 | smallerChildIndex = rightChildIndex;
176 | }
177 | }
178 |
179 | /*
180 | * If the current value should come after the smaller child value, then
181 | * the two values need to be swapped to be in the correct order.
182 | */
183 | if (compare(array[currentIndex], array[smallerChildIndex]) > 0) {
184 | swap(array, currentIndex, smallerChildIndex);
185 |
186 | // move down the tree to the previous location of the smaller child
187 | currentIndex = smallerChildIndex;
188 | } else {
189 |
190 | /*
191 | * If we've reached here then the current and child values are
192 | * already in the correct order. We can infer that this means
193 | * the rest of the path down is also in the correct order
194 | * and so we can safely exit the loop.
195 | */
196 | break;
197 | }
198 | }
199 |
200 | }
201 |
202 | //-----------------------------------------------------------------------------
203 | // BinaryHeap Class
204 | //-----------------------------------------------------------------------------
205 |
206 | /*
207 | * These symbols are used to represent properties that should not be part of
208 | * the public interface. You could also use ES2019 private fields, but those
209 | * are not yet widely available as of the time of my writing.
210 | */
211 | const array = Symbol("array");
212 | const compare = Symbol("compare");
213 |
214 | /**
215 | * A binary heap implementation in JavaScript.
216 | * @class BinaryHeap
217 | */
218 | class BinaryHeap {
219 |
220 | /**
221 | * Creates a new instance of BinaryHeap
222 | * @param {Function} [comparator] A comparator function.
223 | */
224 | constructor(comparator = (a, b) => a - b) {
225 |
226 | /**
227 | * Array used to manage the heap.
228 | * @property array
229 | * @type Array
230 | * @private
231 | */
232 | this[array] = [];
233 |
234 | /**
235 | * Comparator to compare values.
236 | * @property comparator
237 | * @type Function
238 | * @private
239 | */
240 | this[compare] = comparator;
241 | }
242 |
243 | /**
244 | * Appends some data to the heap.
245 | * @param {*} data The data to add to the heap.
246 | * @returns {void}
247 | */
248 | add(data) {
249 | this[array].push(data);
250 | heapifyUp(this[array], this[compare]);
251 | }
252 |
253 | /**
254 | * Determines if the heap is empty.
255 | * @returns {boolean} True if the heap is empty, false if not.
256 | */
257 | isEmpty() {
258 | return this[array].length === 0;
259 | }
260 |
261 | /**
262 | * Returns the value at the top of the heap but does not remove it from
263 | * the heap.
264 | * @returns {*} The value at the top of the heap.
265 | */
266 | peek() {
267 | if (this.isEmpty()) {
268 | throw new Error("Heap is empty.");
269 | }
270 |
271 | return this[array][0];
272 | }
273 |
274 | /**
275 | * Returns and removes the value at the top of the heap.
276 | * @returns {*} The value at the top of the heap.
277 | */
278 | poll() {
279 | if (this.isEmpty()) {
280 | throw new Error("Heap is empty.");
281 | }
282 |
283 | /*
284 | * If there are at least two items in the array, then we need to do a
285 | * remove and rebalance operation to ensure the heap remains consistent.
286 | */
287 | if (this[array].length > 1) {
288 |
289 | // first remove the top value for safe keeping
290 | const topValue = this[array][0];
291 |
292 | /*
293 | * Next, take the last item from the array and move it into the top
294 | * slot. This will likely not be the correct value but maintains the
295 | * tree hierarchy. Then, reorganize the heap so it remains properly
296 | * ordered.
297 | */
298 | const replacementValue = this[array].pop();
299 | this[array][0] = replacementValue;
300 | heapifyDown(this[array], this[compare]);
301 |
302 | // finally, return the value
303 | return topValue;
304 | } else {
305 |
306 | /*
307 | * In this case, the array has only one item, so it's simpler to just
308 | * pop the value off of the array and return it.
309 | */
310 | return this[array].pop();
311 | }
312 |
313 | }
314 |
315 | /**
316 | * Returns the number of values in the heap.
317 | * @returns {int} The number of values in the heap.
318 | */
319 | get size() {
320 | return this[array].length;
321 | }
322 |
323 | /**
324 | * Determines if the given value exists in the heap.
325 | * @param {*} value The value to search for.
326 | * @returns {boolean} True if the value exists in the heap, false if not.
327 | */
328 | includes(value) {
329 | return this[array].includes(value);
330 | }
331 |
332 | /**
333 | * Removes all values from the heap.
334 | * @returns {void}
335 | */
336 | clear() {
337 | this[array] = [];
338 | }
339 |
340 | /**
341 | * The default iterator for the class.
342 | * @returns {Iterator} An iterator for the class.
343 | */
344 | [Symbol.iterator]() {
345 | return this.values();
346 | }
347 |
348 | /**
349 | * Create an iterator that returns each node in the list.
350 | * @returns {Iterator} An iterator on the list.
351 | */
352 | values() {
353 | return this[array].values();
354 | }
355 |
356 | /**
357 | * Converts the heap into a string representation.
358 | * @returns {String} A string representation of the heap.
359 | */
360 | toString(){
361 | return [...this[array]].toString();
362 | }
363 | }
364 |
365 | exports.BinaryHeap = BinaryHeap;
--------------------------------------------------------------------------------
/tests/data-structures/circular-linked-list/circular-linked-list.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileoverview Linked List tests
3 | */
4 | /* global it, describe, beforeEach */
5 |
6 | "use strict";
7 |
8 | //-----------------------------------------------------------------------------
9 | // Requirements
10 | //-----------------------------------------------------------------------------
11 |
12 | const assert = require("chai").assert;
13 | const { CircularLinkedList } = require("../../../src/data-structures/circular-linked-list");
14 |
15 | //-----------------------------------------------------------------------------
16 | // Helpers
17 | //-----------------------------------------------------------------------------
18 |
19 | /**
20 | * Check that the contents of the list match the values of the array.
21 | * @param {LinkedList} list The list to check
22 | * @param {Array} values An array of values that should match.
23 | * @throws {AssertionError} If the values in the list don't match the
24 | * values in the array.
25 | */
26 | function assertListValues(list, values) {
27 | const listValues = [...list.values()];
28 | assert.deepStrictEqual(listValues, values);
29 | }
30 |
31 | //-----------------------------------------------------------------------------
32 | // Tests
33 | //-----------------------------------------------------------------------------
34 |
35 | describe("CircularLinkedList", () => {
36 |
37 | let list;
38 |
39 | beforeEach(() => {
40 | list = new CircularLinkedList();
41 | });
42 |
43 | describe("add()", () => {
44 |
45 | it("should store an item when one item is added", () => {
46 | list.add(1);
47 | assertListValues(list, [1]);
48 | });
49 |
50 | it("should store two items when two items are added", () => {
51 | list.add(1);
52 | list.add(2);
53 | assertListValues(list, [1, 2]);
54 | });
55 |
56 | it("should store three items when three items are added", () => {
57 | list.add(1);
58 | list.add(2);
59 | list.add(3);
60 | assertListValues(list, [1, 2, 3]);
61 | });
62 | });
63 |
64 | describe("insertBefore()", () => {
65 |
66 | it("should store an item when one item is inserted at the start", () => {
67 | list.add(1);
68 | list.add(2);
69 | list.add(3);
70 | assertListValues(list, [1, 2, 3]);
71 |
72 | list.insertBefore(0, 0);
73 | assertListValues(list, [0, 1, 2, 3]);
74 | });
75 |
76 | it("should store an item when one item is inserted in the middle", () => {
77 | list.add(1);
78 | list.add(2);
79 | list.add(3);
80 | assertListValues(list, [1, 2, 3]);
81 |
82 | list.insertBefore(1.5, 1);
83 | assertListValues(list, [1, 1.5, 2, 3]);
84 | });
85 |
86 | it("should store an item when one item is inserted at the end", () => {
87 | list.add(1);
88 | list.add(2);
89 | list.add(3);
90 | assertListValues(list, [1, 2, 3]);
91 |
92 | list.insertBefore(2.5, 2);
93 | assertListValues(list, [1, 2, 2.5, 3]);
94 | });
95 |
96 | it("should throw an error when the list is empty", () => {
97 | assert.throws(() => {
98 | list.insertBefore(1, 0);
99 | }, "Index 0 does not exist in the list.");
100 | });
101 |
102 | it("should throw an error when the index is out of range", () => {
103 | list.add(1);
104 | list.add(2);
105 | list.add(3);
106 | assertListValues(list, [1, 2, 3]);
107 |
108 | assert.throws(() => {
109 | list.insertBefore(1, 5);
110 | }, "Index 5 does not exist in the list.");
111 | });
112 |
113 | });
114 |
115 | describe("insertAfter()", () => {
116 |
117 | it("should store an item when one item is inserted at the start", () => {
118 | list.add(1);
119 | list.add(2);
120 | list.add(3);
121 | assertListValues(list, [1, 2, 3]);
122 |
123 | list.insertAfter(1.5, 0);
124 | assertListValues(list, [1, 1.5, 2, 3]);
125 | });
126 |
127 | it("should store an item when one item is inserted in the middle", () => {
128 | list.add(1);
129 | list.add(2);
130 | list.add(3);
131 | assertListValues(list, [1, 2, 3]);
132 |
133 | list.insertAfter(2.5, 1);
134 | assertListValues(list, [1, 2, 2.5, 3]);
135 | });
136 |
137 | it("should store an item when one item is inserted at the end", () => {
138 | list.add(1);
139 | list.add(2);
140 | list.add(3);
141 | assertListValues(list, [1, 2, 3]);
142 |
143 | list.insertAfter(3.5, 2);
144 | assertListValues(list, [1, 2, 3, 3.5]);
145 | });
146 |
147 | it("should throw an error when the list is empty", () => {
148 | assert.throws(() => {
149 | list.insertAfter(1, 0);
150 | }, "Index 0 does not exist in the list.");
151 | });
152 |
153 | it("should throw an error when the index is out of range", () => {
154 | list.add(1);
155 | list.add(2);
156 | list.add(3);
157 | assertListValues(list, [1, 2, 3]);
158 |
159 | assert.throws(() => {
160 | list.insertAfter(1, 5);
161 | }, "Index 5 does not exist in the list.");
162 | });
163 |
164 | });
165 |
166 | describe("get()", () => {
167 |
168 | it("should return the first item when get(0) is called", () => {
169 | list.add(1);
170 | assert.strictEqual(list.get(0), 1);
171 | });
172 |
173 | it("should return the correct value when get() is called multiple times", () => {
174 | list.add(1);
175 | list.add(2);
176 | assert.strictEqual(list.get(0), 1);
177 | assert.strictEqual(list.get(1), 2);
178 | });
179 |
180 | it("should return undefined when get() is called with -1", () => {
181 | assert.strictEqual(list.get(-1), undefined);
182 | });
183 |
184 | it("should return undefined when get() is called with an out-of-range index in an empty list", () => {
185 | assert.strictEqual(list.get(1), undefined);
186 | });
187 |
188 | it("should return undefined when get() is called with an out-of-range index in a non-empty list", () => {
189 | list.add(1);
190 | list.add(2);
191 | assert.strictEqual(list.get(5), undefined);
192 | });
193 |
194 | });
195 |
196 | describe("remove()", () => {
197 |
198 | it("should remove an item when there is only one item", () => {
199 | list.add(1);
200 | assertListValues(list, [1]);
201 |
202 | assert.strictEqual(list.remove(0), 1);
203 | assertListValues(list, []);
204 | });
205 |
206 | it("should remove an item when multiple items are in the list and the middle item is removed", () => {
207 | list.add(1);
208 | list.add(2);
209 | list.add(3);
210 | assertListValues(list, [1, 2, 3]);
211 |
212 | // remove middle item
213 | assert.strictEqual(list.remove(1), 2);
214 | assertListValues(list, [1, 3]);
215 | });
216 |
217 | it("should remove an item when multiple items are in the list and the last item is removed", () => {
218 | list.add(1);
219 | list.add(2);
220 | list.add(3);
221 | assertListValues(list, [1, 2, 3]);
222 |
223 | // remove last item
224 | assert.strictEqual(list.remove(2), 3);
225 | assertListValues(list, [1, 2]);
226 | });
227 |
228 | it("should remove an item when multiple items are in the list and the first item is removed", () => {
229 | list.add(1);
230 | list.add(2);
231 | list.add(3);
232 | assertListValues(list, [1, 2, 3]);
233 |
234 | // remove first item
235 | assert.strictEqual(list.remove(0), 1);
236 | assertListValues(list, [2, 3]);
237 | });
238 |
239 | it("should throw an error when multiple items are in the list and an out-of-bounds index is used", () => {
240 | list.add(1);
241 | list.add(2);
242 | list.add(3);
243 | assertListValues(list, [1, 2, 3]);
244 |
245 | // remove unknown item
246 | assert.throws(() => {
247 | list.remove(5);
248 | }, "Index 5 does not exist in the list.");
249 | });
250 |
251 | it("should throw an error when multiple items are in the list and a negative index is used", () => {
252 | list.add(1);
253 | list.add(2);
254 | list.add(3);
255 | assertListValues(list, [1, 2, 3]);
256 |
257 | // remove unknown item
258 | assert.throws(() => {
259 | list.remove(-1);
260 | }, "Index -1 does not exist in the list.");
261 | });
262 |
263 | it("should throw an error when the list is empty", () => {
264 | assert.throws(() => {
265 | list.remove(0);
266 | }, "Index 0 does not exist in the list.");
267 | });
268 | });
269 |
270 | describe("clear()", () => {
271 |
272 | it("should not throw an error when the list has no items", () => {
273 | assertListValues(list, []);
274 |
275 | list.clear();
276 | assertListValues(list, []);
277 | });
278 |
279 | it("should remove all items when the list has one item", () => {
280 | list.add(1);
281 | assertListValues(list, [1]);
282 |
283 | list.clear();
284 | assertListValues(list, []);
285 | });
286 |
287 | it("should remove all items when the list has multiple items", () => {
288 | list.add(1);
289 | list.add(2);
290 | assertListValues(list, [1, 2]);
291 |
292 | list.clear();
293 | assertListValues(list, []);
294 | });
295 |
296 | });
297 |
298 | describe("size", () => {
299 |
300 | it("should return 0 when the list is empty", () => {
301 | assert.strictEqual(list.size, 0);
302 | });
303 |
304 | it("should return 1 when the list has one item", () => {
305 | list.add(1);
306 | assert.strictEqual(list.size, 1);
307 | });
308 |
309 | it("should return 2 when the list has two items", () => {
310 | list.add(1);
311 | list.add(2);
312 | assert.strictEqual(list.size, 2);
313 | });
314 |
315 | });
316 |
317 | describe("indexOf()", () => {
318 |
319 | it("should return -1 when the list is empty", () => {
320 | assert.strictEqual(list.indexOf(1), -1);
321 | });
322 |
323 | it("should return 0 when the list has one item", () => {
324 | list.add(1);
325 | assert.strictEqual(list.indexOf(1), 0);
326 | });
327 |
328 | it("should return 1 when the list has two items", () => {
329 | list.add(1);
330 | list.add(2);
331 | assert.strictEqual(list.indexOf(2), 1);
332 | });
333 |
334 | it("should return -1 when the list doesn't contain the value", () => {
335 | list.add(1);
336 | list.add(2);
337 | assert.strictEqual(list.indexOf(3), -1);
338 | });
339 |
340 | });
341 |
342 | ["values", Symbol.iterator].forEach(method => {
343 |
344 | describe(String(method) + "()", () => {
345 |
346 | it("should create empty array when there are no items", () => {
347 | assert.deepStrictEqual([...list[method]()], []);
348 | });
349 |
350 | it("should iterate over list when there is one item", () => {
351 | list.add(1);
352 |
353 | assert.deepStrictEqual([...list[method]()], [1]);
354 | });
355 |
356 | it("should iterate over list when there are multiple items", () => {
357 | list.add(1);
358 | list.add(2);
359 | list.add(3);
360 |
361 | assert.deepStrictEqual([...list[method]()], [1, 2, 3]);
362 | });
363 |
364 | });
365 |
366 | });
367 |
368 | describe("circularValues()", () => {
369 |
370 | it("should create empty array when there are no items", () => {
371 | assert.deepStrictEqual([...list.circularValues()], []);
372 | });
373 |
374 | it("should iterate over list when there is one item", () => {
375 | list.add(1);
376 |
377 | const [first, second] = list.circularValues();
378 | assert.deepStrictEqual([first, second], [1, 1]);
379 | });
380 |
381 | it("should iterate over list when there are multiple items", () => {
382 | list.add(1);
383 | list.add(2);
384 | list.add(3);
385 |
386 | const [first, second, third, fourth, fifth] = list.circularValues();
387 | assert.deepStrictEqual([first, second, third, fourth, fifth], [1, 2, 3, 1, 2]);
388 | });
389 |
390 | });
391 |
392 |
393 | });
394 |
--------------------------------------------------------------------------------
/tests/data-structures/circular-doubly-linked-list/circular-doubly-linked-list.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileoverview Circular Doubly Linked List tests
3 | */
4 | /* global it, describe, beforeEach */
5 | "use strict";
6 |
7 | //-----------------------------------------------------------------------------
8 | // Requirements
9 | //-----------------------------------------------------------------------------
10 |
11 | const assert = require("chai").assert;
12 | const { CircularDoublyLinkedList } = require("../../../src/data-structures/circular-doubly-linked-list/circular-doubly-linked-list");
13 |
14 | //-----------------------------------------------------------------------------
15 | // Helpers
16 | //-----------------------------------------------------------------------------
17 |
18 | /**
19 | * Check that the contents of the list match the values of the array.
20 | * @param {CircularDoublyLinkedList} list The list to check
21 | * @param {Array} values An array of values that should match.
22 | * @throws {AssertionError} If the values in the list don't match the
23 | * values in the array.
24 | */
25 | function assertListValues(list, values) {
26 | const listValues = [...list.values()];
27 | assert.deepStrictEqual(listValues, values);
28 |
29 | const reverseValues = [...list.reverse()];
30 | assert.deepStrictEqual(reverseValues, values.reverse());
31 |
32 | }
33 |
34 | //-----------------------------------------------------------------------------
35 | // Tests
36 | //-----------------------------------------------------------------------------
37 |
38 | describe("CircularDoublyLinkedList", () => {
39 |
40 | let list;
41 |
42 | beforeEach(() => {
43 | list = new CircularDoublyLinkedList();
44 | });
45 |
46 | describe("add()", () => {
47 |
48 | it("should store an item when one item is added", () => {
49 | list.add(1);
50 | assertListValues(list, [1]);
51 | });
52 |
53 | it("should store multiple items when multiple items are added", () => {
54 | list.add(1);
55 | list.add(2);
56 | assertListValues(list, [1, 2]);
57 | });
58 | });
59 |
60 | describe("insertBefore()", () => {
61 |
62 | it("should store an item when one item is inserted at the start", () => {
63 | list.add(1);
64 | list.add(2);
65 | list.add(3);
66 | assertListValues(list, [1, 2, 3]);
67 |
68 | list.insertBefore(0, 0);
69 | assertListValues(list, [0, 1, 2, 3]);
70 | });
71 |
72 | it("should store an item when one item is inserted in the middle", () => {
73 | list.add(1);
74 | list.add(2);
75 | list.add(3);
76 | assertListValues(list, [1, 2, 3]);
77 |
78 | list.insertBefore(1.5, 1);
79 | assertListValues(list, [1, 1.5, 2, 3]);
80 | });
81 |
82 | it("should store an item when one item is inserted at the end", () => {
83 | list.add(1);
84 | list.add(2);
85 | list.add(3);
86 | assertListValues(list, [1, 2, 3]);
87 |
88 | list.insertBefore(2.5, 2);
89 | assertListValues(list, [1, 2, 2.5, 3]);
90 | });
91 |
92 | it("should throw an error when the list is empty", () => {
93 | assert.throws(() => {
94 | list.insertBefore(1, 0);
95 | }, "Index 0 does not exist in the list.");
96 | });
97 |
98 | it("should throw an error when the index is out of range", () => {
99 | list.add(1);
100 | list.add(2);
101 | list.add(3);
102 | assertListValues(list, [1, 2, 3]);
103 |
104 | assert.throws(() => {
105 | list.insertBefore(1, 5);
106 | }, "Index 5 does not exist in the list.");
107 | });
108 |
109 | });
110 |
111 | describe("insertAfter()", () => {
112 |
113 | it("should store an item when one item is inserted at the start", () => {
114 | list.add(1);
115 | list.add(2);
116 | list.add(3);
117 | assertListValues(list, [1, 2, 3]);
118 |
119 | list.insertAfter(1.5, 0);
120 | assertListValues(list, [1, 1.5, 2, 3]);
121 | });
122 |
123 | it("should store an item when one item is inserted in the middle", () => {
124 | list.add(1);
125 | list.add(2);
126 | list.add(3);
127 | assertListValues(list, [1, 2, 3]);
128 |
129 | list.insertAfter(2.5, 1);
130 | assertListValues(list, [1, 2, 2.5, 3]);
131 | });
132 |
133 | it("should store an item when one item is inserted at the end", () => {
134 | list.add(1);
135 | list.add(2);
136 | list.add(3);
137 | assertListValues(list, [1, 2, 3]);
138 |
139 | list.insertAfter(3.5, 2);
140 | assertListValues(list, [1, 2, 3, 3.5]);
141 | });
142 |
143 | it("should throw an error when the list is empty", () => {
144 | assert.throws(() => {
145 | list.insertAfter(1, 0);
146 | }, "Index 0 does not exist in the list.");
147 | });
148 |
149 | it("should throw an error when the index is out of range", () => {
150 | list.add(1);
151 | list.add(2);
152 | list.add(3);
153 | assertListValues(list, [1, 2, 3]);
154 |
155 | assert.throws(() => {
156 | list.insertAfter(1, 5);
157 | }, "Index 5 does not exist in the list.");
158 | });
159 |
160 | });
161 |
162 | describe("get()", () => {
163 |
164 | it("should return the first item when get(0) is called", () => {
165 | list.add(1);
166 | assert.strictEqual(list.get(0), 1);
167 | });
168 |
169 | it("should return the correct value when get() is called multiple times", () => {
170 | list.add(1);
171 | list.add(2);
172 | assert.strictEqual(list.get(0), 1);
173 | assert.strictEqual(list.get(1), 2);
174 | });
175 |
176 | it("should return undefined when get() is called with -1", () => {
177 | assert.strictEqual(list.get(-1), undefined);
178 | });
179 |
180 | it("should return undefined when get() is called with an out-of-range index in an empty list", () => {
181 | assert.strictEqual(list.get(1), undefined);
182 | });
183 |
184 | it("should return undefined when get() is called with an out-of-range index in a non-empty list", () => {
185 | list.add(1);
186 | list.add(2);
187 | assert.strictEqual(list.get(5), undefined);
188 | });
189 |
190 | });
191 |
192 | describe("remove()", () => {
193 |
194 | it("should remove an item when there is only one item", () => {
195 | list.add(1);
196 | assertListValues(list, [1]);
197 |
198 | assert.strictEqual(list.remove(0), 1);
199 | assertListValues(list, []);
200 | });
201 |
202 | it("should remove an item when multiple items are in the list and the middle item is removed", () => {
203 | list.add(1);
204 | list.add(2);
205 | list.add(3);
206 | assertListValues(list, [1, 2, 3]);
207 |
208 | // remove middle item
209 | assert.strictEqual(list.remove(1), 2);
210 | assertListValues(list, [1, 3]);
211 | });
212 |
213 | it("should remove an item when multiple items are in the list and the last item is removed", () => {
214 | list.add(1);
215 | list.add(2);
216 | list.add(3);
217 | assertListValues(list, [1, 2, 3]);
218 |
219 | // remove last item
220 | assert.strictEqual(list.remove(2), 3);
221 | assertListValues(list, [1, 2]);
222 | });
223 |
224 | it("should remove an item when multiple items are in the list and the first item is removed", () => {
225 | list.add(1);
226 | list.add(2);
227 | list.add(3);
228 | assertListValues(list, [1, 2, 3]);
229 |
230 | // remove first item
231 | assert.strictEqual(list.remove(0), 1);
232 | assertListValues(list, [2, 3]);
233 | });
234 |
235 | it("should throw an error when multiple items are in the list and an out-of-bounds index is used", () => {
236 | list.add(1);
237 | list.add(2);
238 | list.add(3);
239 | assertListValues(list, [1, 2, 3]);
240 |
241 | // remove unknown item
242 | assert.throws(() => {
243 | list.remove(5);
244 | }, "Index 5 does not exist in the list.");
245 | });
246 |
247 | it("should throw an error when multiple items are in the list and a negative index is used", () => {
248 | list.add(1);
249 | list.add(2);
250 | list.add(3);
251 | assertListValues(list, [1, 2, 3]);
252 |
253 | // remove unknown item
254 | assert.throws(() => {
255 | list.remove(-1);
256 | }, "Index -1 does not exist in the list.");
257 | });
258 |
259 | it("should throw an error when the list is empty", () => {
260 | assert.throws(() => {
261 | list.remove(0);
262 | }, "Index 0 does not exist in the list.");
263 | });
264 | });
265 |
266 | describe("clear()", () => {
267 |
268 | it("should not throw an error when the list has no items", () => {
269 | assertListValues(list, []);
270 |
271 | list.clear();
272 | assertListValues(list, []);
273 | });
274 |
275 | it("should remove all items when the list has one item", () => {
276 | list.add(1);
277 | assertListValues(list, [1]);
278 |
279 | list.clear();
280 | assertListValues(list, []);
281 | });
282 |
283 | it("should remove all items when the list has multiple items", () => {
284 | list.add(1);
285 | list.add(2);
286 | assertListValues(list, [1, 2]);
287 |
288 | list.clear();
289 | assertListValues(list, []);
290 | });
291 |
292 | });
293 |
294 |
295 | describe("size", () => {
296 |
297 | it("should return 0 when the list is empty", () => {
298 | assert.strictEqual(list.size, 0);
299 | });
300 |
301 | it("should return 1 when the list has one item", () => {
302 | list.add(1);
303 | assert.strictEqual(list.size, 1);
304 | });
305 |
306 | it("should return 2 when the list has two items", () => {
307 | list.add(1);
308 | list.add(2);
309 | assert.strictEqual(list.size, 2);
310 | });
311 |
312 | });
313 |
314 | describe("indexOf()", () => {
315 |
316 | it("should return -1 when the list is empty", () => {
317 | assert.strictEqual(list.indexOf(1), -1);
318 | });
319 |
320 | it("should return 0 when the list has one item", () => {
321 | list.add(1);
322 | assert.strictEqual(list.indexOf(1), 0);
323 | });
324 |
325 | it("should return 1 when the list has two items", () => {
326 | list.add(1);
327 | list.add(2);
328 | assert.strictEqual(list.indexOf(2), 1);
329 | });
330 |
331 | it("should return -1 when the list doesn't contain the value", () => {
332 | list.add(1);
333 | list.add(2);
334 | assert.strictEqual(list.indexOf(3), -1);
335 | });
336 |
337 | });
338 |
339 | ["values", Symbol.iterator].forEach(method => {
340 |
341 | describe(String(method) + "()", () => {
342 |
343 | it("should create empty array when there are no items", () => {
344 | assert.deepStrictEqual([...list[method]()], []);
345 | });
346 |
347 | it("should iterate over list when there is one item", () => {
348 | list.add(1);
349 |
350 | assert.deepStrictEqual([...list[method]()], [1]);
351 | });
352 |
353 | it("should iterate over list when there are multiple items", () => {
354 | list.add(1);
355 | list.add(2);
356 | list.add(3);
357 |
358 | assert.deepStrictEqual([...list[method]()], [1, 2, 3]);
359 | });
360 |
361 | });
362 |
363 | });
364 |
365 | describe("reverse()", () => {
366 |
367 | it("should create empty array when there are no items", () => {
368 | assert.deepStrictEqual([...list.reverse()], []);
369 | });
370 |
371 | it("should iterate over list when there is one item", () => {
372 | list.add(1);
373 |
374 | assert.deepStrictEqual([...list.reverse()], [1]);
375 | });
376 |
377 | it("should iterate over list when there are multiple items", () => {
378 | list.add(1);
379 | list.add(2);
380 | list.add(3);
381 |
382 | assert.deepStrictEqual([...list.reverse()], [3, 2, 1]);
383 | });
384 |
385 | });
386 |
387 | describe("circularValues()", () => {
388 |
389 | it("should create empty array when there are no items", () => {
390 | assert.deepStrictEqual([...list.circularValues()], []);
391 | });
392 |
393 | it("should iterate over list when there is one item", () => {
394 | list.add(1);
395 |
396 | const [first, second] = list.circularValues();
397 | assert.deepStrictEqual([first, second], [1, 1]);
398 | });
399 |
400 | it("should iterate over list when there are multiple items", () => {
401 | list.add(1);
402 | list.add(2);
403 | list.add(3);
404 |
405 | const [first, second, third, fourth, fifth] = list.circularValues();
406 | assert.deepStrictEqual([first, second, third, fourth, fifth], [1, 2, 3, 1, 2]);
407 | });
408 |
409 | });
410 |
411 |
412 | });
413 |
--------------------------------------------------------------------------------
/tests/data-structures/doubly-linked-list/doubly-linked-list.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileoverview Doubly Linked List tests
3 | */
4 | /* global it, describe, beforeEach */
5 | "use strict";
6 |
7 | //-----------------------------------------------------------------------------
8 | // Requirements
9 | //-----------------------------------------------------------------------------
10 |
11 | const assert = require("chai").assert;
12 | const { DoublyLinkedList } = require("../../../src/data-structures/doubly-linked-list/doubly-linked-list");
13 |
14 | //-----------------------------------------------------------------------------
15 | // Helpers
16 | //-----------------------------------------------------------------------------
17 |
18 | /**
19 | * Check that the contents of the list match the values of the array.
20 | * @param {DoublyLinkedList} list The list to check
21 | * @param {Array} values An array of values that should match.
22 | * @throws {AssertionError} If the values in the list don't match the
23 | * values in the array.
24 | */
25 | function assertListValues(list, values) {
26 | const listValues = [...list.values()];
27 | assert.deepStrictEqual(listValues, values);
28 |
29 | const reverseValues = [...list.reverse()];
30 | assert.deepStrictEqual(reverseValues, values.reverse());
31 |
32 | }
33 |
34 | //-----------------------------------------------------------------------------
35 | // Tests
36 | //-----------------------------------------------------------------------------
37 |
38 | describe("DoublyLinkedList", () => {
39 |
40 | let list;
41 |
42 | beforeEach(() => {
43 | list = new DoublyLinkedList();
44 | });
45 |
46 | describe("add()", () => {
47 |
48 | it("should store an item when one item is added", () => {
49 | list.add(1);
50 | assertListValues(list, [1]);
51 | });
52 |
53 | it("should store multiple items when multiple items are added", () => {
54 | list.add(1);
55 | list.add(2);
56 | assertListValues(list, [1, 2]);
57 | });
58 | });
59 |
60 | describe("insertBefore()", () => {
61 |
62 | it("should store an item when one item is inserted at the start", () => {
63 | list.add(1);
64 | list.add(2);
65 | list.add(3);
66 | assertListValues(list, [1, 2, 3]);
67 |
68 | list.insertBefore(0, 0);
69 | assertListValues(list, [0, 1, 2, 3]);
70 | });
71 |
72 | it("should store an item when one item is inserted in the middle", () => {
73 | list.add(1);
74 | list.add(2);
75 | list.add(3);
76 | assertListValues(list, [1, 2, 3]);
77 |
78 | list.insertBefore(1.5, 1);
79 | assertListValues(list, [1, 1.5, 2, 3]);
80 | });
81 |
82 | it("should store an item when one item is inserted at the end", () => {
83 | list.add(1);
84 | list.add(2);
85 | list.add(3);
86 | assertListValues(list, [1, 2, 3]);
87 |
88 | list.insertBefore(2.5, 2);
89 | assertListValues(list, [1, 2, 2.5, 3]);
90 | });
91 |
92 | it("should throw an error when the list is empty", () => {
93 | assert.throws(() => {
94 | list.insertBefore(1, 0);
95 | }, "Index 0 does not exist in the list.");
96 | });
97 |
98 | it("should throw an error when the index is out of range", () => {
99 | list.add(1);
100 | list.add(2);
101 | list.add(3);
102 | assertListValues(list, [1, 2, 3]);
103 |
104 | assert.throws(() => {
105 | list.insertBefore(1, 5);
106 | }, "Index 5 does not exist in the list.");
107 | });
108 |
109 | });
110 |
111 | describe("insertAfter()", () => {
112 |
113 | it("should store an item when one item is inserted at the start", () => {
114 | list.add(1);
115 | list.add(2);
116 | list.add(3);
117 | assertListValues(list, [1, 2, 3]);
118 |
119 | list.insertAfter(1.5, 0);
120 | assertListValues(list, [1, 1.5, 2, 3]);
121 | });
122 |
123 | it("should store an item when one item is inserted in the middle", () => {
124 | list.add(1);
125 | list.add(2);
126 | list.add(3);
127 | assertListValues(list, [1, 2, 3]);
128 |
129 | list.insertAfter(2.5, 1);
130 | assertListValues(list, [1, 2, 2.5, 3]);
131 | });
132 |
133 | it("should store an item when one item is inserted at the end", () => {
134 | list.add(1);
135 | list.add(2);
136 | list.add(3);
137 | assertListValues(list, [1, 2, 3]);
138 |
139 | list.insertAfter(3.5, 2);
140 | assertListValues(list, [1, 2, 3, 3.5]);
141 | });
142 |
143 | it("should throw an error when the list is empty", () => {
144 | assert.throws(() => {
145 | list.insertAfter(1, 0);
146 | }, "Index 0 does not exist in the list.");
147 | });
148 |
149 | it("should throw an error when the index is out of range", () => {
150 | list.add(1);
151 | list.add(2);
152 | list.add(3);
153 | assertListValues(list, [1, 2, 3]);
154 |
155 | assert.throws(() => {
156 | list.insertAfter(1, 5);
157 | }, "Index 5 does not exist in the list.");
158 | });
159 |
160 | });
161 |
162 | describe("get()", () => {
163 |
164 | it("should return the first item when get(0) is called", () => {
165 | list.add(1);
166 | assert.strictEqual(list.get(0), 1);
167 | });
168 |
169 | it("should return the correct value when get() is called multiple times", () => {
170 | list.add(1);
171 | list.add(2);
172 | assert.strictEqual(list.get(0), 1);
173 | assert.strictEqual(list.get(1), 2);
174 | });
175 |
176 | it("should return undefined when get() is called with -1", () => {
177 | assert.strictEqual(list.get(-1), undefined);
178 | });
179 |
180 | it("should return undefined when get() is called with an out-of-range index in an empty list", () => {
181 | assert.strictEqual(list.get(1), undefined);
182 | });
183 |
184 | it("should return undefined when get() is called with an out-of-range index in a non-empty list", () => {
185 | list.add(1);
186 | list.add(2);
187 | assert.strictEqual(list.get(5), undefined);
188 | });
189 |
190 | });
191 |
192 | describe("remove()", () => {
193 |
194 | it("should remove an item when there is only one item", () => {
195 | list.add(1);
196 | assertListValues(list, [1]);
197 |
198 | assert.strictEqual(list.remove(0), 1);
199 | assertListValues(list, []);
200 | });
201 |
202 | it("should remove an item when multiple items are in the list and the middle item is removed", () => {
203 | list.add(1);
204 | list.add(2);
205 | list.add(3);
206 | assertListValues(list, [1, 2, 3]);
207 |
208 | // remove middle item
209 | assert.strictEqual(list.remove(1), 2);
210 | assertListValues(list, [1, 3]);
211 | });
212 |
213 | it("should remove an item when multiple items are in the list and the last item is removed", () => {
214 | list.add(1);
215 | list.add(2);
216 | list.add(3);
217 | assertListValues(list, [1, 2, 3]);
218 |
219 | // remove last item
220 | assert.strictEqual(list.remove(2), 3);
221 | assertListValues(list, [1, 2]);
222 | });
223 |
224 | it("should remove an item when multiple items are in the list and the first item is removed", () => {
225 | list.add(1);
226 | list.add(2);
227 | list.add(3);
228 | assertListValues(list, [1, 2, 3]);
229 |
230 | // remove first item
231 | assert.strictEqual(list.remove(0), 1);
232 | assertListValues(list, [2, 3]);
233 | });
234 |
235 | it("should throw an error when multiple items are in the list and an out-of-bounds index is used", () => {
236 | list.add(1);
237 | list.add(2);
238 | list.add(3);
239 | assertListValues(list, [1, 2, 3]);
240 |
241 | // remove unknown item
242 | assert.throws(() => {
243 | list.remove(5);
244 | }, "Index 5 does not exist in the list.");
245 | });
246 |
247 | it("should throw an error when multiple items are in the list and a negative index is used", () => {
248 | list.add(1);
249 | list.add(2);
250 | list.add(3);
251 | assertListValues(list, [1, 2, 3]);
252 |
253 | // remove unknown item
254 | assert.throws(() => {
255 | list.remove(-1);
256 | }, "Index -1 does not exist in the list.");
257 | });
258 |
259 | it("should throw an error when the list is empty", () => {
260 | assert.throws(() => {
261 | list.remove(0);
262 | }, "Index 0 does not exist in the list.");
263 | });
264 | });
265 |
266 | describe("clear()", () => {
267 |
268 | it("should not throw an error when the list has no items", () => {
269 | assertListValues(list, []);
270 |
271 | list.clear();
272 | assertListValues(list, []);
273 | });
274 |
275 | it("should remove all items when the list has one item", () => {
276 | list.add(1);
277 | assertListValues(list, [1]);
278 |
279 | list.clear();
280 | assertListValues(list, []);
281 | });
282 |
283 | it("should remove all items when the list has multiple items", () => {
284 | list.add(1);
285 | list.add(2);
286 | assertListValues(list, [1, 2]);
287 |
288 | list.clear();
289 | assertListValues(list, []);
290 | });
291 |
292 | });
293 |
294 |
295 | describe("size", () => {
296 |
297 | it("should return 0 when the list is empty", () => {
298 | assert.strictEqual(list.size, 0);
299 | });
300 |
301 | it("should return 1 when the list has one item", () => {
302 | list.add(1);
303 | assert.strictEqual(list.size, 1);
304 | });
305 |
306 | it("should return 2 when the list has two items", () => {
307 | list.add(1);
308 | list.add(2);
309 | assert.strictEqual(list.size, 2);
310 | });
311 |
312 | });
313 |
314 | describe("indexOf()", () => {
315 |
316 | it("should return -1 when the list is empty", () => {
317 | assert.strictEqual(list.indexOf(1), -1);
318 | });
319 |
320 | it("should return 0 when the list has one item", () => {
321 | list.add(1);
322 | assert.strictEqual(list.indexOf(1), 0);
323 | });
324 |
325 | it("should return 1 when the list has two items", () => {
326 | list.add(1);
327 | list.add(2);
328 | assert.strictEqual(list.indexOf(2), 1);
329 | });
330 |
331 | it("should return -1 when the list doesn't contain the value", () => {
332 | list.add(1);
333 | list.add(2);
334 | assert.strictEqual(list.indexOf(3), -1);
335 | });
336 |
337 | });
338 |
339 | describe("find()", () => {
340 |
341 | it("should return undefined when the list is empty", () => {
342 | assert.isUndefined(list.find(() => true));
343 | });
344 |
345 | it("should return 1 when the matching value is found", () => {
346 | list.add(1);
347 | assert.strictEqual(list.find(value => (value > 0)), 1);
348 | });
349 |
350 | it("should return 2 when the matching value is second", () => {
351 | list.add(1);
352 | list.add(2);
353 | assert.strictEqual(list.find(value => (value > 1)), 2);
354 | });
355 |
356 | it("should return undefined when the list doesn't contain a match", () => {
357 | list.add(1);
358 | list.add(2);
359 | assert.isUndefined(list.find((value) => value > 2));
360 | });
361 |
362 | });
363 |
364 | describe("findIndex()", () => {
365 |
366 | it("should return -1 when the list is empty", () => {
367 | assert.strictEqual(list.findIndex(() => true), -1);
368 | });
369 |
370 | it("should return 0 when the matching value is found in the first item", () => {
371 | list.add(1);
372 | assert.strictEqual(list.findIndex(value => (value > 0)), 0);
373 | });
374 |
375 | it("should return 1 when the matching value is second", () => {
376 | list.add(1);
377 | list.add(2);
378 | assert.strictEqual(list.findIndex(value => (value > 1)), 1);
379 | });
380 |
381 | it("should return -1 when the list doesn't contain a match", () => {
382 | list.add(1);
383 | list.add(2);
384 | assert.strictEqual(list.findIndex((value) => value > 2), -1);
385 | });
386 |
387 | });
388 |
389 | ["values", Symbol.iterator].forEach(method => {
390 |
391 | describe(String(method) + "()", () => {
392 |
393 | it("should create empty array when there are no items", () => {
394 | assert.deepStrictEqual([...list[method]()], []);
395 | });
396 |
397 | it("should iterate over list when there is one item", () => {
398 | list.add(1);
399 |
400 | assert.deepStrictEqual([...list[method]()], [1]);
401 | });
402 |
403 | it("should iterate over list when there are multiple items", () => {
404 | list.add(1);
405 | list.add(2);
406 | list.add(3);
407 |
408 | assert.deepStrictEqual([...list[method]()], [1, 2, 3]);
409 | });
410 |
411 | });
412 |
413 | });
414 |
415 | describe("reverse()", () => {
416 |
417 | it("should create empty array when there are no items", () => {
418 | assert.deepStrictEqual([...list.reverse()], []);
419 | });
420 |
421 | it("should iterate over list when there is one item", () => {
422 | list.add(1);
423 |
424 | assert.deepStrictEqual([...list.reverse()], [1]);
425 | });
426 |
427 | it("should iterate over list when there are multiple items", () => {
428 | list.add(1);
429 | list.add(2);
430 | list.add(3);
431 |
432 | assert.deepStrictEqual([...list.reverse()], [3, 2, 1]);
433 | });
434 |
435 | });
436 |
437 | });
438 |
--------------------------------------------------------------------------------
/src/data-structures/binary-search-tree/binary-search-tree.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileoverview Binary Search Tree implementation in JavaScript
3 | */
4 |
5 | /*
6 | * These symbols are used to represent properties that should not be part of
7 | * the public interface. You could also use ES2019 private fields, but those
8 | * are not yet widely available as of the time of my writing.
9 | */
10 | const root = Symbol("root");
11 |
12 | /**
13 | * Represents a single node in a BinarySearchTree.
14 | * @class BinarySearchTree
15 | */
16 | class BinarySearchTreeNode {
17 |
18 | /**
19 | * Creates a new instance of BinarySearchTreeNode.
20 | * @param {*} value The value to store in the node.
21 | */
22 | constructor(value) {
23 |
24 | /**
25 | * The value that this node stores.
26 | * @property value
27 | * @type *
28 | */
29 | this.value = value;
30 |
31 | /**
32 | * A pointer to the left node in the BinarySearchTree.
33 | * @property left
34 | * @type ?BinarySearchTreeNode
35 | */
36 | this.left = null;
37 |
38 | /**
39 | * A pointer to the right node in the BinarySearchTree.
40 | * @property right
41 | * @type ?BinarySearchTreeNode
42 | */
43 | this.right = null;
44 |
45 | }
46 | }
47 |
48 | /**
49 | * A linked tree implementation in JavaScript.
50 | * @class BinarySearchTree
51 | */
52 | class BinarySearchTree {
53 |
54 | /**
55 | * Creates a new instance of BinarySearchTree
56 | */
57 | constructor() {
58 |
59 | /**
60 | * Pointer to the root node in the tree.
61 | * @property root
62 | * @type ?BinarySearchTreeNode
63 | * @private
64 | */
65 | this[root] = null;
66 | }
67 |
68 | /**
69 | * Adds some value into the tree. This method traverses the tree to find
70 | * the correct location to insert the value. Duplicate values are discarded.
71 | * @param {*} value The value to add to the tree.
72 | * @returns {void}
73 | */
74 | add(value) {
75 |
76 | /*
77 | * Create a new node to insert into the tree and store the value in it.
78 | * This node will be added into the tree.
79 | */
80 | const newNode = new BinarySearchTreeNode(value);
81 |
82 | // special case: no nodes in the tree yet
83 | if (this[root] === null) {
84 | this[root] = newNode;
85 | } else {
86 |
87 | /*
88 | * The `current` variable is used to track the item that is being
89 | * used inside of the loop below. It starts out pointing to
90 | * `this[root]` and is overwritten inside of the loop.
91 | */
92 | let current = this[root];
93 |
94 | /*
95 | * This is loop is broken only when the node has been inserted
96 | * in the correct spot or a duplicate value is found.
97 | */
98 | while (current !== null) {
99 |
100 | // if the new value is less than this node's value, go left
101 | if (value < current.value) {
102 |
103 | //if there's no left, then the new node belongs there
104 | if (current.left === null) {
105 | current.left = newNode;
106 | break;
107 | } else {
108 | current = current.left;
109 | }
110 |
111 | // if the new value is greater than this node's value, go right
112 | } else if (value > current.value) {
113 |
114 | //if there's no right, then the new node belongs there
115 | if (current.right === null) {
116 | current.right = newNode;
117 | break;
118 | } else {
119 | current = current.right;
120 | }
121 |
122 | // if the new value is equal to the nodeToRemove one, just ignore
123 | } else {
124 | break;
125 | }
126 | }
127 | }
128 | }
129 |
130 |
131 | /**
132 | * Determines if a given value exists in the tree.
133 | * @param {*} value The value to find.
134 | * @returns {boolean} True if the value is found in the tree, false if not.
135 | */
136 | has(value) {
137 |
138 |
139 | /*
140 | * `found` keeps track of whether or not the value in question was
141 | * found in the tree. This variable is used for both the control
142 | * condition of the loop below and is the return value of this function.
143 | */
144 | let found = false;
145 |
146 | /*
147 | * The `current` variable is used to track the item that is being
148 | * used inside of the loop below. It starts out pointing to
149 | * `this[root]` and is overwritten inside of the loop.
150 | */
151 | let current = this[root];
152 |
153 | /*
154 | * The loop continues until either the value is found (so `found`
155 | * is `true`) or the tree has been completely searched
156 | * (`current` is `null`).
157 | */
158 | while (!found && current !== null) {
159 |
160 | // if the value is less than the current node's, go left
161 | if (value < current.value) {
162 | current = current.left;
163 |
164 | // if the value is greater than the current node's, go right
165 | } else if (value > current.value) {
166 | current = current.right;
167 |
168 | //values are equal, found it!
169 | } else {
170 | found = true;
171 | }
172 | }
173 |
174 | /*
175 | * Execution reaches here either because `found` is `true`, or because
176 | * the tree was completely searched and the value was not found. Either
177 | * way, the value of `found` is now the result of the search, so just
178 | * return it.
179 | */
180 | return found;
181 | }
182 |
183 |
184 | /**
185 | * Deletes the value from the tree.
186 | * @param {int} index The zero-based index of the item to remove.
187 | * @returns {*} The value in the given position in the tree.
188 | * @throws {RangeError} If index is out of range.
189 | */
190 | delete(value) {
191 |
192 | // special case: the tree is empty, just exit
193 | if (this[root] === null) {
194 | return;
195 | }
196 |
197 | /*
198 | * The `found` variable keeps track of whether or not the value has
199 | * been found in the tree.
200 | */
201 | let found = false;
202 |
203 | /*
204 | * These two variables keep track of the location during traversal.
205 | * The `current` variable is the node we have traversed to while the
206 | * `parent` variable is the parent node of `current`. Unlike with other
207 | * traversals, we need to keep track of the parent so we can remove
208 | * the child node.
209 | */
210 | let current = this[root],
211 | parent = null;
212 |
213 | /*
214 | * The first step is to do a search for the value to remove. This is
215 | * the same algorithm as the `has()` method, with the difference being
216 | * that the parent is also tracked.
217 | */
218 | while (!found && current !== null) {
219 |
220 | // if the value is less than the current node's, go left
221 | if (value < current.value) {
222 | parent = current;
223 | current = current.left;
224 |
225 | // if the value is greater than the current node's, go right
226 | } else if (value > current.value) {
227 | parent = current;
228 | current = current.right;
229 |
230 | // values are equal, found it!
231 | } else {
232 | found = true;
233 | }
234 | }
235 |
236 | // if the value wasn't found, just exit
237 | if (!found) {
238 | return;
239 | }
240 |
241 | /*
242 | * If we make it to here, the `nodeToRemove` variable continues the node
243 | * to remove. This assignment isn't necessary but makes it easier to
244 | * figure out what's going on in the code below.
245 | */
246 | const nodeToRemove = current;
247 |
248 | /*
249 | * The `replacement` variable is filled with what should replace
250 | * `nodeToRemove`. It starts out set to `null` but can change based
251 | * on what we find later.
252 | */
253 | let replacement = null;
254 |
255 | /*
256 | * The most complicated case is when the `nodeToRemove` node has both a left
257 | * and a right child. In that case, we need to move things around to
258 | * ensure the tree remains properly structured.
259 | */
260 | if ((nodeToRemove.left !== null) && (nodeToRemove.right !== null)) {
261 |
262 | /*
263 | * We need to find the best replacement for the removed node by
264 | * traversing the subtrees. To start, we assume that the best
265 | * replacement is `nodeToRemove.left`.
266 | */
267 | replacement = nodeToRemove.left;
268 |
269 | /*
270 | * We need to keep track of the replacement's parent to modify
271 | * the subtree as we go.
272 | */
273 | let replacementParent = nodeToRemove;
274 |
275 | /*
276 | * The best replacement is found by traversing the right subtree
277 | * of `replacement`. The rightmost node in this subtree is the
278 | * largest value in `nodeToRemove`'s left subtree and so is the
279 | * easiest one to use as a replacement to minimize the number of
280 | * modifications we need to do.
281 | */
282 | while (replacement.right !== null) {
283 | replacementParent = replacement;
284 | replacement = replacement.right;
285 | }
286 |
287 |
288 | /*
289 | * Because `replacement` has no right subtree, we can copy over the
290 | * `nodeToRemove`'s right subtree directly.
291 | */
292 | replacement.right = nodeToRemove.right;
293 |
294 | /*
295 | * Special case: if `nodeToRemove.left` doesn't have a right subtree,
296 | * then `replacementParent` will be equal to `nodeToRemove`. In that
297 | * case, we should not make any further changes. Otherwise, we need
298 | * to rearrange some more nodes.
299 | */
300 | if (replacementParent !== nodeToRemove) {
301 |
302 | /*
303 | * Remove `replacement` from its current location and replace it
304 | * with `replacement.left` (we know `replacement.right` is `null`).
305 | * It's possible that `replacement.left` is `null`, but that doesn't
306 | * matter. Both `null` and non-`null` values keep the tree intact.
307 | */
308 | replacementParent.right = replacement.left;
309 |
310 | /*
311 | * Assign the complete left subtree of `nodeToRemove` to
312 | * `replacement` to maintain the proper structure.
313 | */
314 | replacement.left = nodeToRemove.left;
315 | }
316 |
317 | } else if (nodeToRemove.left !== null) {
318 | replacement = nodeToRemove.left;
319 | } else if (nodeToRemove.right !== null) {
320 | replacement = nodeToRemove.right;
321 | }
322 |
323 | /*
324 | * If the `nodeToRemove` node has no children, then the default value of
325 | * `null` is used for `replacement`.
326 | */
327 |
328 | // special case: the `nodeToRemove` node is the root
329 | if (nodeToRemove === this[root]) {
330 | this[root] = replacement;
331 | } else {
332 |
333 | if (nodeToRemove.value < parent.value) {
334 | parent.left = replacement;
335 | } else {
336 | parent.right = replacement;
337 | }
338 |
339 | }
340 |
341 |
342 |
343 | }
344 |
345 | /**
346 | * Removes all nodes from the tree.
347 | * @returns {void}
348 | */
349 | clear() {
350 | this[root] = null;
351 | }
352 |
353 | /**
354 | * Returns the number of items in the tree.
355 | * @returns {int} The number of items in the tree.
356 | */
357 | get size() {
358 |
359 | // special case: the tree is empty
360 | if (this[root] === null) {
361 | return 0;
362 | }
363 |
364 | /*
365 | * The `count` variable is used to keep track of how many items have
366 | * been visited inside the loop below. This is important because this
367 | * is the value to return from this method.
368 | */
369 | let count = 0;
370 |
371 | /*
372 | * Traversal is easiest when using a recursive function, so define
373 | * a helper function here. This function does an in-order traversal
374 | * of the tree, meaning it yields values in sorted order from
375 | * lowest value to highest. It does this by traversing to the leftmost
376 | * node first, then working its way back up the tree, visiting right nodes
377 | * along the way.
378 | */
379 | const traverse = (node) => {
380 |
381 | // special case: there is no node
382 | if (node) {
383 |
384 | //traverse the left subtree
385 | if (node.left !== null) {
386 | traverse(node.left);
387 | }
388 |
389 | // increment the counter
390 | count++;
391 |
392 | //traverse the right subtree
393 | if (node.right !== null) {
394 | traverse(node.right);
395 | }
396 | }
397 | };
398 |
399 | // start traversing from the root
400 | traverse(this[root]);
401 |
402 | // return the final count, which was updated inside traverse()
403 | return count;
404 |
405 | }
406 |
407 | /**
408 | * The default iterator for the class.
409 | * @returns {Iterator} An iterator for the class.
410 | */
411 | [Symbol.iterator]() {
412 | return this.values();
413 | }
414 |
415 | /**
416 | * Create an iterator that returns each node in the tree.
417 | * @returns {Iterator} An iterator on the tree.
418 | */
419 | *values(){
420 |
421 | /*
422 | * Traversal is easiest when using a recursive function, so define
423 | * a helper function here. This function does an in-order traversal
424 | * of the tree, meaning it yields values in sorted order from
425 | * lowest value to highest. It does this by traversing to the leftmost
426 | * node first, then working its way back up the tree, visiting right nodes
427 | * along the way.
428 | *
429 | * This function cannot be an arrow function because arrow functions
430 | * cannot be generators.
431 | */
432 | function *traverse(node) {
433 |
434 | // special case: there is no node
435 | if (node) {
436 |
437 | //traverse the left subtree
438 | if (node.left !== null) {
439 | yield* traverse(node.left);
440 | }
441 |
442 | // emit the value
443 | yield node.value;
444 |
445 | //traverse the right subtree
446 | if (node.right !== null) {
447 | yield* traverse(node.right);
448 | }
449 | }
450 | }
451 |
452 | yield* traverse(this[root]);
453 | }
454 |
455 | /**
456 | * Converts the tree into a string representation.
457 | * @returns {String} A string representation of the tree.
458 | */
459 | toString(){
460 | return [...this].toString();
461 | }
462 | }
463 |
464 | exports.BinarySearchTree = BinarySearchTree;
--------------------------------------------------------------------------------
/src/data-structures/hash-map/hash-map.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * @fileoverview Hash Map implementation in JavaScript
4 | */
5 |
6 | "use strict";
7 |
8 | //-----------------------------------------------------------------------------
9 | // Requirements
10 | //-----------------------------------------------------------------------------
11 |
12 | const { DoublyLinkedList } = require("@humanwhocodes/doubly-linked-list");
13 |
14 | //-----------------------------------------------------------------------------
15 | // Private
16 | //-----------------------------------------------------------------------------
17 |
18 | const ARRAY_SIZE = 16;
19 |
20 | /**
21 | * Adds up all of the code points in a string to create a numeric hash.
22 | * @param {string} key The text key to hash.
23 | * @returns {int} A numeric hash of text.
24 | * @private
25 | */
26 | function hashCodePoints(key) {
27 |
28 | let result = 0;
29 |
30 | // Iterate over each character (not each byte) in the string
31 | for (const character of key) {
32 |
33 | /*
34 | * The `codePointAt()` method has support for multi-byte characters,
35 | * so that's better than `charCodeAt()` for this purpose.
36 | *
37 | * `character` is a single-character string, so we only want the code
38 | * point at position 0.
39 | */
40 | result += character.codePointAt(0);
41 | }
42 |
43 | return result;
44 | }
45 |
46 | /**
47 | * Determines the correct array index for the given hash code.
48 | * @param {int} hashCode The hash code to compute an index for.
49 | * @returns {int} An index between 0 and `ARRAY_SIZE - 1`.
50 | */
51 | function getArrayIndexFromHashCode(hashCode) {
52 | return hashCode % ARRAY_SIZE;
53 | }
54 |
55 | /**
56 | * Creates an array to use as the basis for a hash map.
57 | * @returns {void}
58 | * @private
59 | */
60 | function createArray() {
61 |
62 | /*
63 | * FYI: It's not necessary to use an instance of `Array` for the
64 | * purpose of creating a hash map. You could just as easily use a
65 | * regular object. This implementation uses an array strictly because
66 | * it's the most direct equivalent to how other languages implement
67 | * hash maps.
68 | */
69 |
70 | // Object.seal() ensures we don't accidentally add more items
71 | return Object.seal(
72 |
73 | /*
74 | * Creates a new array with a predefined length of 16. This doesn't
75 | * actually create each of the properties in the array, so the
76 | * `fill()` method is used to do so. This is necessary because a
77 | * sealed array cannot have new properties defined, and since the
78 | * array starts out without any numeric properties defined, it
79 | * would prevent us from even assigning a value to array[0].
80 | */
81 | new Array(ARRAY_SIZE).fill(undefined)
82 | );
83 | }
84 |
85 | /**
86 | * Checks that a key is a non-empty string.
87 | * @param {string} key The key to validate.
88 | * @returns {void}
89 | * @throws {TypeError} When the key is either not a string or is an empty
90 | * string.
91 | */
92 | function assertNonEmptyString(key) {
93 | if (typeof key !== "string" || key.length === 0) {
94 | throw new TypeError("Key must be a non-empty string.");
95 | }
96 | }
97 |
98 | //-----------------------------------------------------------------------------
99 | // HashMap Class
100 | //-----------------------------------------------------------------------------
101 |
102 | /*
103 | * These symbols are used to represent properties that should not be part of
104 | * the public interface. You could also use ES2019 private fields, but those
105 | * are not yet widely available as of the time of my writing.
106 | */
107 | const array = Symbol("array");
108 |
109 | /**
110 | * A binary heap implementation in JavaScript.
111 | * @class HashMap
112 | */
113 | class HashMap {
114 |
115 | /**
116 | * Creates a new instance of HashMap
117 | */
118 | constructor() {
119 |
120 | /**
121 | * Array used to manage the hash map. The array is sealed to ensure
122 | * no new items accidentally get added.
123 | * @property array
124 | * @type Array
125 | * @private
126 | */
127 | this[array] = createArray();
128 | }
129 |
130 | /**
131 | * Adds a key-value pair into the hash map.
132 | * @param {string} key The key to add.
133 | * @param {*} value The value to add.
134 | * @returns {void}
135 | * @throws {TypeError} If the key isn't a non-empty string.
136 | */
137 | set(key, value) {
138 |
139 | // first, ensure the key is valid
140 | assertNonEmptyString(key);
141 |
142 | /*
143 | * Next, calculate the hash code from the string key. The hash code
144 | * is then used to determine the index into the array where the data
145 | * should be stored.
146 | */
147 | const hashCode = hashCodePoints(key);
148 | const index = getArrayIndexFromHashCode(hashCode);
149 |
150 | /*
151 | * Special case: If the calculated index in the array hasn't yet
152 | * been initialized, then create a new linked list in that
153 | * location before continuing.
154 | *
155 | * Note: It's not necessary to use a linked list. Because JavaScript
156 | * has an excellent `Array` class, you could also just use an array
157 | * here. This implementation sticks with a linked list because that's
158 | * the most common implementation in other languages.
159 | */
160 | if (this[array][index] === undefined) {
161 | this[array][index] = new DoublyLinkedList();
162 | }
163 |
164 | /*
165 | * Check to see if the exact key is already present
166 | * in the hash map. If so, then just update the value.
167 | */
168 | const result = this[array][index].find((value) => {
169 | return value.key === key;
170 | });
171 |
172 | // If the key doesn't already exist, then add it
173 | if (result === undefined) {
174 |
175 | /*
176 | * Add everything we know about this key-value pair, including the key,
177 | * the value, and the hash code. We will need all of these later.
178 | */
179 | this[array][index].add({
180 | key,
181 | value
182 | });
183 | } else {
184 |
185 | // The key already exists in the hash map, so just update the value.
186 | result.value = value;
187 | }
188 |
189 | }
190 |
191 | /**
192 | * Retrieves a key-value pair from the hash map.
193 | * @param {string} key The key whose value should be retrieved.
194 | * @returns {*} The value associated with the key or `undefined` if the
195 | * key doesn't exist in the hash map.
196 | * @throws {TypeError} If the key isn't a non-empty string.
197 | */
198 | get(key) {
199 |
200 | // first, ensure the key is valid
201 | assertNonEmptyString(key);
202 |
203 | /*
204 | * Next, calculate the hash code from the string key. The hash code
205 | * is then used to determine the index into the array where the data
206 | * should be stored.
207 | */
208 | const hashCode = hashCodePoints(key);
209 | const index = getArrayIndexFromHashCode(hashCode);
210 |
211 | /*
212 | * Special case: If the calculated index in the array hasn't yet
213 | * been initialized, then the key doesn't exist. Return
214 | * `undefined` to indicate the key doesn't exist.
215 | */
216 | if (this[array][index] === undefined) {
217 | return undefined;
218 | }
219 |
220 | /*
221 | * If we've made it to here, then there is a linked list in the
222 | * array at this location, so try to find the key that matches.
223 | */
224 | const result = this[array][index].find((value) => {
225 | return value.key === key;
226 | });
227 |
228 | /*
229 | * If an item with the given hash code and key was not found, then
230 | * there is no value to return. Just return undefined.
231 | */
232 | if (result === undefined) {
233 | return undefined;
234 | }
235 |
236 | /*
237 | * If we've made it to here, it means that the hash code and key were
238 | * found, so return the value.
239 | */
240 | return result.value;
241 | }
242 |
243 | /**
244 | * Determines if a given key is present in the hash map.
245 | * @param {string} key The key to check.
246 | * @returns {boolean} True if the key exists in the hash map, false if not.
247 | * @throws {TypeError} If the key isn't a non-empty string.
248 | */
249 | has(key) {
250 |
251 | // first, ensure the key is valid
252 | assertNonEmptyString(key);
253 |
254 | /*
255 | * Next, calculate the hash code from the string key. The hash code
256 | * is then used to determine the index into the array where the data
257 | * should be stored.
258 | */
259 | const hashCode = hashCodePoints(key);
260 | const index = getArrayIndexFromHashCode(hashCode);
261 |
262 | /*
263 | * Special case: If the calculated index in the array hasn't yet
264 | * been initialized, then the key doesn't exist. Return
265 | * `false` to indicate the key doesn't exist.
266 | */
267 | if (this[array][index] === undefined) {
268 | return false;
269 | }
270 |
271 | /*
272 | * If we've made it to here, then there is a linked list in the
273 | * array at this location, so try to find the key that matches.
274 | */
275 | const resultIndex = this[array][index].findIndex((value) => {
276 | return value.key === key;
277 | });
278 |
279 | /*
280 | * Any value greater than -1 indicates that the key was found in the
281 | * hash map and therefore this method should return `true`.
282 | */
283 | return resultIndex > -1;
284 | }
285 |
286 | /**
287 | * Deletes the given key from the hash map.
288 | * @param {string} key The key to delete.
289 | * @returns {boolean} True if the key exists and was deleted, false if the
290 | * key didn't exist.
291 | * @throws {TypeError} If the key isn't a non-empty string.
292 | */
293 | delete(key) {
294 |
295 | // first, ensure the key is valid
296 | assertNonEmptyString(key);
297 |
298 | /*
299 | * Next, calculate the hash code from the string key. The hash code
300 | * is then used to determine the index into the array where the data
301 | * should be stored.
302 | */
303 | const hashCode = hashCodePoints(key);
304 | const index = getArrayIndexFromHashCode(hashCode);
305 |
306 | /*
307 | * Special case: If the calculated index in the array hasn't yet
308 | * been initialized, then the key doesn't exist. Return
309 | * `false` to indicate the key doesn't exist.
310 | */
311 | if (this[array][index] === undefined) {
312 | return false;
313 | }
314 |
315 | /*
316 | * If we've made it to here, then there is a linked list in the
317 | * array at this location, so try to find the key that matches.
318 | */
319 | const resultIndex = this[array][index].findIndex((value) => {
320 | return value.key === key;
321 | });
322 |
323 | /*
324 | * Special case: If `resultIndex` is -1, meaning the value wasn't
325 | * found, just return -1.
326 | */
327 | if (resultIndex === -1) {
328 | return -1;
329 | }
330 |
331 | /*
332 | * If we've made it to here, then `resultIndex` is greater than -1
333 | * and we need to remove the given key from the hash map.
334 | */
335 | this[array][index].remove(resultIndex);
336 |
337 | /*
338 | * Because we actually performed the removal, we need to return `true`
339 | * to give feedback that this happened.
340 | */
341 | return true;
342 |
343 | }
344 |
345 | /**
346 | * Returns the number of key-value pairs in the hash map.
347 | * @returns {int} The number of key-value pairs in the hash map.
348 | */
349 | get size() {
350 |
351 | /*
352 | * Because the `entries()` generator already implements the correct
353 | * traversal algorithm, use that here instead of duplicating the
354 | * algorithm.
355 | *
356 | * The first step is to create a new iterator by calling `entries()`.
357 | */
358 | const iterator = this.entries();
359 |
360 | /*
361 | * The `count` variable stores the number of entries we see and is
362 | * incremented inside of a loop later on.
363 | */
364 | let count = 0;
365 |
366 | /*
367 | * Get the first entry from the iterator. Each entry has two properties:
368 | * `value`, containing the value from the hash map, and `done`, a
369 | * a Boolean value that is `true` when there are no more entries.
370 | */
371 | let entry = iterator.next();
372 |
373 | // Continue the loop as long as there are more entries
374 | while (!entry.done) {
375 |
376 | // Increment the count to reflect the last entry
377 | count++;
378 |
379 | // Get the entry from the iterator and repeat the loop
380 | entry = iterator.next();
381 | }
382 |
383 | /*
384 | * Once the loop exits, the `count` variable contains the number of
385 | * entries in the iterator so return that value.
386 | */
387 | return count;
388 | }
389 |
390 | /**
391 | * Removes all values from the heap.
392 | * @returns {void}
393 | */
394 | clear() {
395 |
396 | /*
397 | * The simplest way to clear all values is just to overwrite the array
398 | * we started with.
399 | */
400 | this[array] = createArray();
401 | }
402 |
403 | /**
404 | * The default iterator for the class.
405 | * @returns {Iterator} An iterator for the class.
406 | */
407 | [Symbol.iterator]() {
408 | return this.entries();
409 | }
410 |
411 | /**
412 | * Create an iterator that returns each entry in the hash map.
413 | * @returns {Iterator} An iterator on the hash map.
414 | */
415 | *entries() {
416 |
417 | // For each item in the array
418 | for (const list of this[array]) {
419 |
420 | // If there is no linked list then no need to go any further
421 | if (list !== undefined) {
422 |
423 | // If there is a linked list then yield each key-value pair
424 | for (const item of list) {
425 | yield [item.key, item.value];
426 | }
427 | }
428 | }
429 | }
430 |
431 | /**
432 | * Create an iterator that returns each key in the hash map.
433 | * @returns {Iterator} An iterator on the hash map keys.
434 | */
435 | *keys() {
436 |
437 | /*
438 | * Because this iterator just returns a subset of the information
439 | * returned by the `entries()` iterator, we just use `entries()`
440 | * and take the information we care about.
441 | */
442 | for (const [key] of this.entries()) {
443 | yield key;
444 | }
445 | }
446 |
447 | /**
448 | * Create an iterator that returns each value in the hash map.
449 | * @returns {Iterator} An iterator on the hash map value.
450 | */
451 | *values() {
452 |
453 | /*
454 | * Because this iterator just returns a subset of the information
455 | * returned by the `entries()` iterator, we just use `entries()`
456 | * and take the information we care about.
457 | */
458 | for (const [,value] of this.entries()) {
459 | yield value;
460 | }
461 | }
462 |
463 | /**
464 | * Converts the heap into a string representation.
465 | * @returns {String} A string representation of the heap.
466 | */
467 | toString(){
468 | // TODO
469 | return [...this[array]].toString();
470 | }
471 | }
472 |
473 | exports.HashMap = HashMap;
--------------------------------------------------------------------------------
/src/data-structures/linked-list/linked-list.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @fileoverview Linked List implementation in JavaScript
3 | */
4 |
5 | /*
6 | * These symbols are used to represent properties that should not be part of
7 | * the public interface. You could also use ES2019 private fields, but those
8 | * are not yet widely available as of the time of my writing.
9 | */
10 | const head = Symbol("head");
11 |
12 | /**
13 | * Represents a single node in a LinkedList.
14 | * @class LinkedListNode
15 | */
16 | class LinkedListNode {
17 |
18 | /**
19 | * Creates a new instance of LinkedListNode.
20 | * @param {*} data The data to store in the node.
21 | */
22 | constructor(data) {
23 |
24 | /**
25 | * The data that this node stores.
26 | * @property data
27 | * @type *
28 | */
29 | this.data = data;
30 |
31 | /**
32 | * A pointer to the next node in the LinkedList.
33 | * @property next
34 | * @type ?LinkedListNode
35 | */
36 | this.next = null;
37 |
38 | }
39 | }
40 |
41 | /**
42 | * A linked list implementation in JavaScript.
43 | * @class LinkedList
44 | */
45 | class LinkedList {
46 |
47 | /**
48 | * Creates a new instance of LinkedList
49 | */
50 | constructor() {
51 |
52 | /**
53 | * Pointer to first node in the list.
54 | * @property head
55 | * @type ?LinkedListNode
56 | * @private
57 | */
58 | this[head] = null;
59 | }
60 |
61 | /**
62 | * Appends some data to the end of the list. This method traverses
63 | * the existing list and places the data at the end in a new node.
64 | * @param {*} data The data to add to the list.
65 | * @returns {void}
66 | */
67 | add(data) {
68 |
69 | /*
70 | * Create a new list node object and store the data in it.
71 | * This node will be added to the end of the existing list.
72 | */
73 | const newNode = new LinkedListNode(data);
74 |
75 | //special case: no nodes in the list yet
76 | if (this[head] === null) {
77 |
78 | /*
79 | * Because there are no nodes in the list, just set the
80 | * `this[head]` pointer to the new node.
81 | */
82 | this[head] = newNode;
83 | } else {
84 |
85 | /*
86 | * The `current` variable is used to track the node that is being
87 | * used inside of the loop below. It starts out pointing to
88 | * `this[head]` and is overwritten inside of the loop.
89 | */
90 | let current = this[head];
91 |
92 | /*
93 | * Follow each `next` pointer until the end. The last node in the
94 | * list has `next` equal to `null`, so when we reach that node,
95 | * we know we're at the end.
96 | */
97 | while (current.next !== null) {
98 | current = current.next;
99 | }
100 |
101 | /*
102 | * At this point, `current` is equal to the last node in the list.
103 | * Setting its `current.next` equal to node means adding a new node
104 | * at the end of the list.
105 | */
106 | current.next = newNode;
107 | }
108 | }
109 |
110 | /**
111 | * Inserts some data into the middle of the list. This method traverses
112 | * the existing list and places the data in a new node at a specific index.
113 | * @param {*} data The data to add to the list.
114 | * @param {int} index The zero-based index at which to insert the data.
115 | * @returns {void}
116 | * @throws {RangeError} If the index doesn't exist in the list.
117 | */
118 | insertBefore(data, index) {
119 |
120 | /*
121 | * Create a new list node object and store the data in it.
122 | * This node will be inserted into the existing list.
123 | */
124 | const newNode = new LinkedListNode(data);
125 |
126 | // special case: no nodes in the list yet
127 | if (this[head] === null) {
128 | throw new RangeError(`Index ${index} does not exist in the list.`);
129 | }
130 |
131 | /*
132 | * Special case: if `index` is `0`, then no traversal is needed
133 | * and we need to update `this[head]` to point to `node`. First,
134 | * set `node.next` to the current `this[head]` so the previous
135 | * head of the list is now the second node in the list. Then it's
136 | * safe to update `this[head]` to point to `node`.
137 | */
138 | if (index === 0) {
139 | newNode.next = this[head];
140 | this[head] = newNode;
141 | } else {
142 |
143 | /*
144 | * The `current` variable is used to track the node that is being
145 | * used inside of the loop below. It starts out pointing to
146 | * `this[head]` and is overwritten inside of the loop.
147 | *
148 | * The `previous` variable tracks one step behind `current`, which
149 | * is necessary because we need to adjust the node at `index`-1's
150 | * `next` pointer to point to the new node.
151 | */
152 | let current = this[head],
153 | previous = null;
154 |
155 | /*
156 | * The `i` variable is used to track how deep into the list we've
157 | * gone. This important because it's the only way to know when
158 | * we've hit the `index` to insert into.
159 | */
160 | let i = 0;
161 |
162 | /*
163 | * Traverse the list nodes similar to the `add()` method, but make
164 | * sure to keep track of how many nodes have been visited and update
165 | * the `previous` pointer in addition to `current`. When
166 | * `i` is the same as `index`, it means we've found the location to
167 | * insert the new data.
168 | */
169 | while ((current.next !== null) && (i < index)) {
170 | previous = current;
171 | current = current.next;
172 | i++;
173 | }
174 |
175 | /*
176 | * At this point, `current` is either the node to insert the new data
177 | * before, or the last node in the list. The only way to tell is if
178 | * `i` is still less than `index`, that means the index is out of range
179 | * and an error should be thrown.
180 | */
181 | if (i < index) {
182 | throw new RangeError(`Index ${index} does not exist in the list.`);
183 | }
184 |
185 | /*
186 | * If code continues to execute here, it means `current` is the node
187 | * to insert new data before and `previous` is the node to insert
188 | * new data after. So `previous.next` must point to `node` and
189 | * `node.next` must point to `current`.
190 | */
191 | previous.next = newNode;
192 | newNode.next = current;
193 | }
194 | }
195 |
196 | /**
197 | * Inserts some data into the middle of the list. This method traverses
198 | * the existing list and places the data in a new node after a specific index.
199 | * @param {*} data The data to add to the list.
200 | * @param {int} index The zero-based index after which to insert the data.
201 | * @returns {void}
202 | * @throws {RangeError} If the index doesn't exist in the list.
203 | */
204 | insertAfter(data, index) {
205 |
206 | /*
207 | * Create a new list node object and store the data in it.
208 | * This node will be inserted into the existing list.
209 | */
210 | const newNode = new LinkedListNode(data);
211 |
212 | // special case: no nodes in the list yet
213 | if (this[head] === null) {
214 | throw new RangeError(`Index ${index} does not exist in the list.`);
215 | }
216 |
217 | /*
218 | * The `current` variable is used to track the node that is being
219 | * used inside of the loop below. It starts out pointing to
220 | * `this[head]` and is overwritten inside of the loop.
221 | */
222 | let current = this[head];
223 |
224 | /*
225 | * The `i` variable is used to track how deep into the list we've
226 | * gone. This important because it's the only way to know when
227 | * we've hit the `index` to insert into.
228 | */
229 | let i = 0;
230 |
231 | /*
232 | * Traverse the list nodes similar to the `add()` method, but make
233 | * sure to keep track of how many nodes have been visited and update
234 | * the `previous` pointer in addition to `current`. When
235 | * `i` is the same as `index`, it means we've found the location to
236 | * insert the new data.
237 | */
238 | while ((current !== null) && (i < index)) {
239 | current = current.next;
240 | i++;
241 | }
242 |
243 | /*
244 | * At this point, `current` is either the node to insert the new data
245 | * before, or the last node in the list. The only way to tell is if
246 | * `i` is still less than `index`, that means the index is out of range
247 | * and an error should be thrown.
248 | */
249 | if (i < index) {
250 | throw new RangeError(`Index ${index} does not exist in the list.`);
251 | }
252 |
253 | /*
254 | * If code continues to execute here, it means `current` is the node
255 | * to insert new data after. So `current.next` must point to
256 | * `node` for the data to be in the correct spot, but before that,
257 | * `node.next` must point to `current.next` to ensure the list
258 | * remains intact.
259 | */
260 | newNode.next = current.next;
261 | current.next = newNode;
262 | }
263 |
264 | /**
265 | * Retrieves the data in the given position in the list.
266 | * @param {int} index The zero-based index of the node whose data
267 | * should be returned.
268 | * @returns {*} The data in the "data" portion of the given node
269 | * or undefined if the node doesn't exist.
270 | */
271 | get(index) {
272 |
273 | // ensure `index` is a positive value
274 | if (index > -1) {
275 |
276 | /*
277 | * The `current` variable is used to track the node that is being
278 | * used inside of the loop below. It starts out pointing to
279 | * `this[head]` and is overwritten inside of the loop.
280 | */
281 | let current = this[head];
282 |
283 | /*
284 | * The `i` variable is used to track how deep into the list we've
285 | * gone. This is important because it's the only way to know when
286 | * we've hit the `index` to insert into.
287 | */
288 | let i = 0;
289 |
290 | /*
291 | * Traverse the list nodes similar to the `add()` method, but make
292 | * sure to keep track of how many nodes have been visited and update
293 | * the `previous` pointer in addition to `current`. When
294 | * `i` is the same as `index`, it means we've found the location to
295 | * insert the new data.
296 | */
297 | while ((current !== null) && (i < index)) {
298 | current = current.next;
299 | i++;
300 | }
301 |
302 | /*
303 | * At this point, `current` might be null if we've gone past the
304 | * end of the list. In that case, we return `undefined` to indicate
305 | * that the node at `index` was not found. If `current` is not
306 | * `null`, then it's safe to return `current.data`.
307 | */
308 | return current !== null ? current.data : undefined;
309 | } else {
310 | return undefined;
311 | }
312 | }
313 |
314 | /**
315 | * Retrieves the index of the data in the list.
316 | * @param {*} data The data to search for.
317 | * @returns {int} The index of the first instance of the data in the list
318 | * or -1 if not found.
319 | */
320 | indexOf(data) {
321 |
322 | /*
323 | * The `current` variable is used to iterate over the list nodes.
324 | * It starts out pointing to the head and is overwritten inside
325 | * of the loop below.
326 | */
327 | let current = this[head];
328 |
329 | /*
330 | * The `index` variable is used to track how deep into the list we've
331 | * gone. This is important because this is the value that is returned
332 | * from this method.
333 | */
334 | let index = 0;
335 |
336 | /*
337 | * This loop checks each node in the list to see if it matches `data`.
338 | * If a match is found, it returns `index` immediately, exiting the
339 | * loop because there's no reason to keep searching. The search
340 | * continues until there are no more nodes to search (when `current` is `null`).
341 | */
342 | while (current !== null) {
343 | if (current.data === data) {
344 | return index;
345 | }
346 |
347 | // traverse to the next node in the list
348 | current = current.next;
349 |
350 | // keep track of where we are
351 | index++;
352 | }
353 |
354 | /*
355 | * If execution gets to this point, it means we reached the end of the
356 | * list and didn't find `data`. Just return -1 as the "not found" value.
357 | */
358 | return -1;
359 | }
360 |
361 | /**
362 | * Removes the node from the given location in the list.
363 | * @param {int} index The zero-based index of the node to remove.
364 | * @returns {*} The data in the given position in the list.
365 | * @throws {RangeError} If index is out of range.
366 | */
367 | remove(index) {
368 |
369 | // special cases: empty list or invalid `index`
370 | if ((this[head] === null) || (index < 0)) {
371 | throw new RangeError(`Index ${index} does not exist in the list.`);
372 | }
373 |
374 | // special case: removing the first node
375 | if (index === 0) {
376 |
377 | // temporarily store the data from the node
378 | const data = this[head].data;
379 |
380 | // just replace the head with the next node in the list
381 | this[head] = this[head].next;
382 |
383 | // return the data at the previous head of the list
384 | return data;
385 | }
386 |
387 | /*
388 | * The `current` variable is used to iterate over the list nodes.
389 | * It starts out pointing to the head and is overwritten inside
390 | * of the loop below.
391 | */
392 | let current = this[head];
393 |
394 | /*
395 | * The `previous` variable keeps track of the node just before
396 | * `current` in the loop below. This is necessary because removing
397 | * an node means updating the previous node's `next` pointer.
398 | */
399 | let previous = null;
400 |
401 | /*
402 | * The `i` variable is used to track how deep into the list we've
403 | * gone. This is important because it's the only way to know when
404 | * we've hit the `index` to remove.
405 | */
406 | let i = 0;
407 |
408 | /*
409 | * Traverse the list nodes similar to the `add()` method, but make
410 | * sure to keep track of how many nodes have been visited and update
411 | * the `previous` pointer in addition to `current`. When
412 | * `i` is the same as `index`, it means we've found the location to
413 | * remove.
414 | */
415 | while ((current !== null) && (i < index)) {
416 |
417 | // save the value of current
418 | previous = current;
419 |
420 | // traverse to the next node
421 | current = current.next;
422 |
423 | // increment the count
424 | i++;
425 | }
426 |
427 | /*
428 | * If `current` isn't `null`, then that means we've found the node
429 | * to remove.
430 | */
431 | if (current !== null) {
432 |
433 | // skip over the node to remove
434 | previous.next = current.next;
435 |
436 | // return the value that was just removed from the list
437 | return current.data;
438 | }
439 |
440 | /*
441 | * If we've made it this far, it means `index` is a value that
442 | * doesn't exist in the list, so throw an error.
443 | */
444 | throw new RangeError(`Index ${index} does not exist in the list.`);
445 | }
446 |
447 | /**
448 | * Removes all nodes from the list.
449 | * @returns {void}
450 | */
451 | clear() {
452 | this[head] = null;
453 | }
454 |
455 | /**
456 | * Returns the number of nodes in the list.
457 | * @returns {int} The number of nodes in the list.
458 | */
459 | get size() {
460 |
461 | // special case: the list is empty
462 | if (this[head] === null) {
463 | return 0;
464 | }
465 |
466 | /*
467 | * The `current` variable is used to iterate over the list nodes.
468 | * It starts out pointing to the head and is overwritten inside
469 | * of the loop below.
470 | */
471 | let current = this[head];
472 |
473 | /*
474 | * The `count` variable is used to keep track of how many nodes have
475 | * been visited inside the loop below. This is important because this
476 | * is the value to return from this method.
477 | */
478 | let count = 0;
479 |
480 | /*
481 | * As long as `current` is not `null`, that means we're not yet at the
482 | * end of the list, so adding 1 to `count` and traverse to the next node.
483 | */
484 | while (current !== null) {
485 | count++;
486 | current = current.next;
487 | }
488 |
489 | /*
490 | * When `current` is `null`, the loop is exited at the value of `count`
491 | * is the number of nodes that were counted in the loop.
492 | */
493 | return count;
494 | }
495 |
496 | /**
497 | * The default iterator for the class.
498 | * @returns {Iterator} An iterator for the class.
499 | */
500 | [Symbol.iterator]() {
501 | return this.values();
502 | }
503 |
504 | /**
505 | * Create an iterator that returns each node in the list.
506 | * @returns {Iterator} An iterator on the list.
507 | */
508 | *values(){
509 |
510 | /*
511 | * The `current` variable is used to iterate over the list nodes.
512 | * It starts out pointing to the head and is overwritten inside
513 | * of the loop below.
514 | */
515 | let current = this[head];
516 |
517 | /*
518 | * As long as `current` is not `null`, there is a piece of data
519 | * to yield.
520 | */
521 | while (current !== null) {
522 | yield current.data;
523 | current = current.next;
524 | }
525 | }
526 |
527 | /**
528 | * Converts the list into a string representation.
529 | * @returns {String} A string representation of the list.
530 | */
531 | toString(){
532 | return [...this].toString();
533 | }
534 | }
535 |
536 | exports.LinkedList = LinkedList;
--------------------------------------------------------------------------------