├── .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 | 3 | Base64 Tests 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |

Base64 Encoding/Decoding Tests

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; --------------------------------------------------------------------------------