Showing preview only (278K chars total). Download the full file or copy to clipboard to get everything.
Repository: humanwhocodes/computer-science-in-javascript
Branch: master
Commit: 7a14efde3751
Files: 49
Total size: 261.4 KB
Directory structure:
gitextract_sd20h0oq/
├── .eslintrc.js
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── algorithms/
│ ├── checksums/
│ │ └── luhn-algorithm/
│ │ └── luhn-algorithm.js
│ ├── searching/
│ │ └── binary-search/
│ │ └── binary-search.js
│ └── sorting/
│ ├── insertion-sort/
│ │ └── insertion-sort.js
│ ├── merge-sort-iterative/
│ │ └── merge-sort-iterative.js
│ ├── merge-sort-recursive/
│ │ ├── merge-sort-inplace.js
│ │ └── merge-sort-recursive.js
│ ├── quicksort/
│ │ └── quicksort.js
│ └── selection-sort/
│ └── selection-sort.js
├── encodings/
│ └── base64/
│ ├── base64.htm
│ └── base64.js
├── package.json
├── src/
│ ├── algorithms/
│ │ └── sorting/
│ │ └── bubble-sort/
│ │ ├── README.md
│ │ ├── bubble-sort.js
│ │ └── package.json
│ └── data-structures/
│ ├── binary-heap/
│ │ ├── README.md
│ │ ├── binary-heap.js
│ │ └── package.json
│ ├── binary-search-tree/
│ │ ├── README.md
│ │ ├── binary-search-tree.js
│ │ └── package.json
│ ├── circular-doubly-linked-list/
│ │ ├── README.md
│ │ ├── circular-doubly-linked-list.js
│ │ └── package.json
│ ├── circular-linked-list/
│ │ ├── README.md
│ │ ├── circular-linked-list.js
│ │ └── package.json
│ ├── doubly-linked-list/
│ │ ├── README.md
│ │ ├── doubly-linked-list.js
│ │ └── package.json
│ ├── hash-map/
│ │ ├── README.md
│ │ ├── hash-map.js
│ │ └── package.json
│ └── linked-list/
│ ├── README.md
│ ├── linked-list.js
│ └── package.json
└── tests/
├── algorithms/
│ └── sorting/
│ ├── bubble-sort/
│ │ └── bubble-sort.js
│ └── quicksort/
│ └── quicksort.js
└── data-structures/
├── binary-heap/
│ └── binary-heap.js
├── binary-search-tree/
│ └── binary-search-tree.js
├── circular-doubly-linked-list/
│ └── circular-doubly-linked-list.js
├── circular-linked-list/
│ └── circular-linked-list.js
├── doubly-linked-list/
│ └── doubly-linked-list.js
├── hash-map/
│ └── hash-map.js
└── linked-list/
└── linked-list.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .eslintrc.js
================================================
module.exports = {
"env": {
"commonjs": true,
"es6": true,
"node": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {
"indent": [
"error",
4
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"double"
],
"semi": [
"error",
"always"
]
}
};
================================================
FILE: .gitignore
================================================
node_modules/
holding/
================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
- "8"
- "9"
- "10"
- "11"
sudo: false
branches:
only:
- master
# Run npm test always
script:
- "npm test"
================================================
FILE: LICENSE
================================================
Copyright (c) 2009 Nicholas C. Zakas. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================
FILE: README.md
================================================
# Computer Science in JavaScript
by [Nicholas C. Zakas](https://humanwhocodes.com)
If you find this useful, please consider supporting my work with a [donation](https://humanwhocodes.com/donate).
## Description
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.
## Folder Structure
The most recent packages are found in these directories:
* `src` - the implementation source code
* `tests` - tests for the implementation source code
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:
* `data-structures` - data structure implementations that have not been updated yet
* `encodings` - encoding implementations that have not been updated yet
* `algorithms` - miscellanous algorithm implementations that have not been updated yet
As I update these, implementations will move from these folders into `src`.
## Branches
* **2009** - the branch containing all of the original implementations as reflected in my 2009 blog post series.
* **master** - the branch where I'm updating the original implementations to use ECMAScript 2018 and later features.
## Installing
You must be using Node.js v8 or later.
First, clone the repo:
```
$ git clone git://github.com/humanwhocodes/computer-science-in-javascript.git
$ cd computer-science-in-javascript
```
Then install the dependencies:
```
$ npm install
```
You can then run tests like this:
```
$ npm test
```
## Updated Blog Posts (2019)
These are the most recent blog posts covering the most recent version of the code.
### Data Structures
* [Linked List](https://humanwhocodes.com/blog/2019/01/computer-science-in-javascript-linked-list/)
* [Doubly-Linked List](https://humanwhocodes.com/blog/2019/02/computer-science-in-javascript-doubly-linked-lists/)
* [Circular Doubly-Linked List](https://humanwhocodes.com/blog/2019/03/computer-science-in-javascript-circular-doubly-linked-lists/)
## Original Blog Posts
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.
### Data Structures
* 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/)
* [Doubly Linked List](https://humanwhocodes.com/blog/2009/04/21/computer-science-in-javascript-doubly-linked-lists/)
* [Linked List](https://humanwhocodes.com/blog/2009/04/13/computer-science-in-javascript-linked-list/)
### Sorting Algorithms
* [Bubble Sort](https://humanwhocodes.com/blog/2009/05/26/computer-science-in-javascript-bubble-sort/)
* [Selection Sort](https://humanwhocodes.com/blog/2009/09/08/computer-science-in-javascript-selection-sort/)
### Other Algorithms
* [Base64 Encoding](https://humanwhocodes.com/blog/2009/12/08/computer-science-in-javascript-base64-encoding/)
* [Binary Search](https://humanwhocodes.com/blog/2009/09/01/computer-science-in-javascript-binary-search/)
* [Credit Card Number Validation](https://humanwhocodes.com/blog/2009/08/04/computer-science-in-javascript-credit-card-number-validation/)
## Note on Code Style
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.
## Issues and Pull Requests
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.
## License
MIT
================================================
FILE: algorithms/checksums/luhn-algorithm/luhn-algorithm.js
================================================
/*
* Luhn algorithm implementation in JavaScript
* Copyright (c) 2009 Nicholas C. Zakas
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Uses Luhn algorithm to validate a numeric identifier.
* @param {String} identifier The identifier to validate.
* @return {Boolean} True if the identifier is valid, false if not.
*/
function isValidIdentifier(identifier) {
var sum = 0,
alt = false,
i = identifier.length-1,
num;
while (i >= 0){
//get the next digit
num = parseInt(identifier.charAt(i), 10);
//if it's not a valid number, abort
if (isNaN(num)){
return false;
}
//if it's an alternate number...
if (alt) {
num *= 2;
if (num > 9){
num = (num % 10) + 1;
}
}
//flip the alternate bit
alt = !alt;
//add to the rest of the sum
sum += num;
//go to next digit
i--;
}
//determine if it's valid
return (sum % 10 == 0);
}
================================================
FILE: algorithms/searching/binary-search/binary-search.js
================================================
/*
* Binary search implementation in JavaScript
* Copyright (c) 2009 Nicholas C. Zakas
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Uses a binary search algorithm to locate a value in the specified array.
* @param {Array} items The array containing the item.
* @param {variant} value The value to search for.
* @return {int} The zero-based index of the value in the array or -1 if not found.
*/
function binarySearch(items, value){
var startIndex = 0,
stopIndex = items.length - 1,
middle = Math.floor((stopIndex + startIndex)/2);
while(items[middle] != value && startIndex < stopIndex){
//adjust search area
if (value < items[middle]){
stopIndex = middle - 1;
} else if (value > items[middle]){
startIndex = middle + 1;
}
//recalculate middle
middle = Math.floor((stopIndex + startIndex)/2);
}
//make sure it's the right value
return (items[middle] != value) ? -1 : middle;
}
================================================
FILE: algorithms/sorting/insertion-sort/insertion-sort.js
================================================
/*
* Insertion sort implementation in JavaScript
* Copyright (c) 2012 Nicholas C. Zakas
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of items software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and items permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* An insertion sort implementation in JavaScript. The array
* is sorted in-place.
* @param {Array} items An array of items to sort.
* @return {Array} The sorted array.
*/
function insertionSort(items) {
var len = items.length, // number of items in the array
value, // the value currently being compared
i, // index into unsorted section
j; // index into sorted section
for (i=0; i < len; i++) {
// store the current value because it may shift later
value = items[i];
/*
* Whenever the value in the sorted section is greater than the value
* in the unsorted section, shift all items in the sorted section over
* by one. This creates space in which to insert the value.
*/
for (j=i-1; j > -1 && items[j] > value; j--) {
items[j+1] = items[j];
}
items[j+1] = value;
}
return items;
}
================================================
FILE: algorithms/sorting/merge-sort-iterative/merge-sort-iterative.js
================================================
/*
* Iterative merge sort implementation in JavaScript
* Copyright (c) 2009-2011 Nicholas C. Zakas
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Merges to arrays in order based on their natural
* relationship.
* @param {Array} left The first array to merge.
* @param {Array} right The second array to merge.
* @return {Array} The merged array.
*/
function merge(left, right){
var result = [];
while (left.length > 0 && right.length > 0){
if (left[0] < right[0]){
result.push(left.shift());
} else {
result.push(right.shift());
}
}
result = result.concat(left).concat(right);
//make sure remaining arrays are empty
left.splice(0, left.length);
right.splice(0, right.length);
return result;
}
/**
* Sorts an array in ascending natural order using
* merge sort.
* @param {Array} items The array to sort.
* @return {Array} The sorted array.
*/
function mergeSort(items){
// Terminal condition - don't need to do anything for arrays with 0 or 1 items
if (items.length < 2) {
return items;
}
var work = [],
i,
len;
for (i=0, len=items.length; i < len; i++){
work.push([items[i]]);
}
work.push([]); //in case of odd number of items
for (var lim=len; lim > 1; lim = Math.floor((lim+1)/2)){
for (var j=0,k=0; k < lim; j++, k+=2){
work[j] = merge(work[k], work[k+1]);
}
work[j] = []; //in case of odd number of items
}
return work[0];
}
================================================
FILE: algorithms/sorting/merge-sort-recursive/merge-sort-inplace.js
================================================
/*
* Recursive merge sort implementation in JavaScript
* Copyright (c) 2012 Nicholas C. Zakas
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Merges to arrays in order based on their natural
* relationship.
* @param {Array} left The first array to merge.
* @param {Array} right The second array to merge.
* @return {Array} The merged array.
*/
function merge(left, right){
var result = [],
il = 0,
ir = 0;
while (il < left.length && ir < right.length){
if (left[il] < right[ir]){
result.push(left[il++]);
} else {
result.push(right[ir++]);
}
}
return result.concat(left.slice(il)).concat(right.slice(ir));
}
/**
* Sorts an array in ascending natural order using
* merge sort.
* @param {Array} items The array to sort.
* @return {Array} The sorted array.
*/
function mergeSort(items){
if (items.length < 2) {
return items;
}
var middle = Math.floor(items.length / 2),
left = items.slice(0, middle),
right = items.slice(middle),
params = merge(mergeSort(left), mergeSort(right));
// Add the arguments to replace everything between 0 and last item in the array
params.unshift(0, items.length);
items.splice.apply(items, params);
return items;
}
================================================
FILE: algorithms/sorting/merge-sort-recursive/merge-sort-recursive.js
================================================
/*
* Recursive merge sort implementation in JavaScript
* Copyright (c) 2009 Nicholas C. Zakas
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Merges two arrays in order based on their natural
* relationship.
* @param {Array} left The first array to merge.
* @param {Array} right The second array to merge.
* @return {Array} The merged array.
*/
function merge(left, right){
var result = [],
il = 0,
ir = 0;
while (il < left.length && ir < right.length){
if (left[il] < right[ir]){
result.push(left[il++]);
} else {
result.push(right[ir++]);
}
}
return result.concat(left.slice(il)).concat(right.slice(ir));
}
/**
* Sorts an array in ascending natural order using
* merge sort.
* @param {Array} items The array to sort.
* @return {Array} The sorted array.
*/
function mergeSort(items){
if (items.length < 2) {
return items;
}
var middle = Math.floor(items.length / 2),
left = items.slice(0, middle),
right = items.slice(middle);
return merge(mergeSort(left), mergeSort(right));
}
================================================
FILE: algorithms/sorting/quicksort/quicksort.js
================================================
/*
* Quick sort implementation in JavaScript
* Copyright (c) 2012 Nicholas C. Zakas
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of items software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and items permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Swaps two values in an array.
* @param {Array} items The array containing the items.
* @param {int} firstIndex Index of first item to swap.
* @param {int} secondIndex Index of second item to swap.
* @return {void}
*/
function swap(items, firstIndex, secondIndex){
var temp = items[firstIndex];
items[firstIndex] = items[secondIndex];
items[secondIndex] = temp;
}
function partition(items, left, right) {
var pivot = items[Math.floor((right + left) / 2)], // pivot value is middle item
i = left, // starts from left and goes right to pivot index
j = right; // starts from right and goes left to pivot index
// while the two indices don't match
while (i <= j) {
// if the item on the left is less than the pivot, continue right
while (i <= right && items[i] < pivot) {
i++;
}
// if the item on the right is greater than the pivot, continue left
while (j >= left && items[j] > pivot) {
j--;
}
// if the two indices still don't match, swap the values
if (i <= j) {
swap(items, i, j);
// change indices to continue loop
i++;
j--;
}
}
// this value is necessary for recursion
return i;
}
/**
* A quicksort implementation in JavaScript. The array
* is sorted in place.
* @param {Array} items An array of items to sort.
* @return {Array} The sorted array.
*/
function quickSort(items, left, right) {
var index;
// performance - don't sort an array with zero or one items
if (items.length > 1) {
// fix left and right values - might not be provided
left = typeof left != "number" ? 0 : left;
right = typeof right != "number" ? items.length - 1 : right;
// split up the entire array
index = partition(items, left, right);
// if the returned index
if (left < index - 1) {
quickSort(items, left, index - 1);
}
if (index < right) {
quickSort(items, index, right);
}
}
return items;
}
================================================
FILE: algorithms/sorting/selection-sort/selection-sort.js
================================================
/*
* Selection sort implementation in JavaScript
* Copyright (c) 2009 Nicholas C. Zakas
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Swaps two values in an array.
* @param {Array} items The array containing the items.
* @param {int} firstIndex Index of first item to swap.
* @param {int} secondIndex Index of second item to swap.
* @return {void}
*/
function swap(items, firstIndex, secondIndex){
var temp = items[firstIndex];
items[firstIndex] = items[secondIndex];
items[secondIndex] = temp;
}
/**
* A selection sort implementation in JavaScript. The array
* is sorted in-place.
* @param {Array} items An array of items to sort.
* @return {Array} The sorted array.
*/
function selectionSort(items){
var len = items.length,
min, i, j;
for (i=0; i < len; i++){
// set minimum to this position
min = i;
// check the rest of the array to see if anything is smaller
for (j=i+1; j < len; j++){
if (items[j] < items[min]){
min = j;
}
}
// if the minimum isn't in the position, swap it
if (i != min){
swap(items, i, min);
}
}
return items;
}
================================================
FILE: encodings/base64/base64.htm
================================================
<html>
<head>
<title>Base64 Tests</title>
<!-- Combo-handled YUI CSS files: -->
<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/combo?2.7.0/build/logger/assets/logger.css&2.7.0/build/yuitest/assets/testlogger.css">
<!-- Combo-handled YUI JS files: -->
<script type="text/javascript" src="http://yui.yahooapis.com/combo?2.7.0/build/yahoo-dom-event/yahoo-dom-event.js&2.7.0/build/logger/logger-min.js&2.7.0/build/yuitest/yuitest-min.js"></script>
<script type="text/javascript" src="base64.js"></script>
</head>
<body>
<h1>Base64 Encoding/Decoding Tests</h1>
<script type="text/javascript">
YAHOO.namespace("test");
YAHOO.test.Base64 = (function(){
var assert = YAHOO.util.Assert;
//-------------------------------------------------------------------------
// Base Test Suite
//-------------------------------------------------------------------------
var suite = new YAHOO.tool.TestSuite("Base64 Tests");
//-------------------------------------------------------------------------
// Test Case for encoding
//-------------------------------------------------------------------------
suite.add(new YAHOO.tool.TestCase({
name : "Base 64 Encoding Tests",
_should: {
error: {
testInvalidChar: new Error("Can't base64 encode non-ASCII characters.")
}
},
//---------------------------------------------------------------------
// Tests
//---------------------------------------------------------------------
testMan: function(){
var result = base64Encode("Man");
assert.areEqual("TWFu", result);
},
testHelloWorld: function(){
var result = base64Encode("Hello world");
assert.areEqual("SGVsbG8gd29ybGQ=", result);
},
testPhrase: function(){
var expected = "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=";
var result = base64Encode("Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure.");
assert.areEqual(expected, result);
},
testInvalidChar: function(){
base64Encode(String.fromCharCode(256) + "Hello!");
}
}));
//-------------------------------------------------------------------------
// Test Case for decoding
//-------------------------------------------------------------------------
suite.add(new YAHOO.tool.TestCase({
name : "Base 64 Decoding Tests",
_should: {
error: {
testInvalidChar: new Error("Not a base64-encoded string."),
testInvalidString: new Error("Not a base64-encoded string."),
testMissingPaddingIndicator: new Error("Not a base64-encoded string.")
}
},
//---------------------------------------------------------------------
// Tests
//---------------------------------------------------------------------
testMan: function(){
var result = base64Decode("TWFu");
assert.areEqual("Man", result);
},
testHelloWorld: function(){
var result = base64Decode("SGVsbG8gd29ybGQ=");
assert.areEqual("Hello world", result);
},
testPhrase: function(){
var result = base64Decode("TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=");
var expected = "Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure.";
assert.areEqual(expected, result);
},
testPhraseWithWhitespace: function(){
var result = base64Decode("TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24 sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHB hc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYn kgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aW\ndhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZSBzaG\t9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=");
var expected = "Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure.";
assert.areEqual(expected, result);
},
testA: function(){
var expected = "a";
var result = base64Decode("YQ==");
assert.areEqual(expected, result);
},
testMissingPaddingIndicator: function(){
var expected = "hatch";
var result = base64Decode("aGF0Y2g");
assert.areEqual(expected, result);
},
testInvalidChar: function(){
base64Decode("aGF,0Y2g");
},
testInvalidString: function(){
base64Decode("aGF0Y2g==");
}
}));
//return it
return suite;
})();
(function (){
//create the logger
var logger = new YAHOO.tool.TestLogger();
//add the tests
YAHOO.tool.TestRunner.add(YAHOO.test.Base64);
YAHOO.tool.TestRunner.run();
})();
</script>
</body>
</html>
================================================
FILE: encodings/base64/base64.js
================================================
/*
* Base 64 implementation in JavaScript
* Copyright (c) 2009 Nicholas C. Zakas. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Base64-encodes a string of text.
* @param {String} text The text to encode.
* @return {String} The base64-encoded string.
*/
function base64Encode(text){
if (/([^\u0000-\u00ff])/.test(text)){
throw new Error("Can't base64 encode non-ASCII characters.");
}
var digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
i = 0,
cur, prev, byteNum,
result=[];
while(i < text.length){
cur = text.charCodeAt(i);
byteNum = i % 3;
switch(byteNum){
case 0: //first byte
result.push(digits.charAt(cur >> 2));
break;
case 1: //second byte
result.push(digits.charAt((prev & 3) << 4 | (cur >> 4)));
break;
case 2: //third byte
result.push(digits.charAt((prev & 0x0f) << 2 | (cur >> 6)));
result.push(digits.charAt(cur & 0x3f));
break;
}
prev = cur;
i++;
}
if (byteNum == 0){
result.push(digits.charAt((prev & 3) << 4));
result.push("==");
} else if (byteNum == 1){
result.push(digits.charAt((prev & 0x0f) << 2));
result.push("=");
}
return result.join("");
}
/**
* Base64-decodes a string of text.
* @param {String} text The text to decode.
* @return {String} The base64-decoded string.
*/
function base64Decode(text){
//ignore white space
text = text.replace(/\s/g,"");
//first check for any unexpected input
if(!(/^[a-z0-9\+\/\s]+\={0,2}$/i.test(text)) || text.length % 4 > 0){
throw new Error("Not a base64-encoded string.");
}
//local variables
var digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
cur, prev, digitNum,
i=0,
result = [];
//remove any equals signs
text = text.replace(/=/g, "");
//loop over each character
while(i < text.length){
cur = digits.indexOf(text.charAt(i));
digitNum = i % 4;
switch(digitNum){
//case 0: first digit - do nothing, not enough info to work with
case 1: //second digit
result.push(String.fromCharCode(prev << 2 | cur >> 4));
break;
case 2: //third digit
result.push(String.fromCharCode((prev & 0x0f) << 4 | cur >> 2));
break;
case 3: //fourth digit
result.push(String.fromCharCode((prev & 3) << 6 | cur));
break;
}
prev = cur;
i++;
}
//return a string
return result.join("");
}
================================================
FILE: package.json
================================================
{
"name": "@humanwhocodes/computer-science-in-javascript",
"version": "2.0.0",
"private": true,
"description": "Collection of classic computer science paradigms, algorithms, and approaches written in JavaScript.",
"main": "README.md",
"scripts": {
"lint": "eslint src/ tests/",
"test": "npm run lint && mocha tests/**/*.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/nzakas/computer-science-in-javascript.git"
},
"keywords": [
"cs",
"algorithms",
"data",
"structures"
],
"author": "nzakas",
"license": "MIT",
"bugs": {
"url": "https://github.com/nzakas/computer-science-in-javascript/issues"
},
"homepage": "https://github.com/nzakas/computer-science-in-javascript#readme",
"directories": {
"test": "tests"
},
"devDependencies": {
"chai": "^4.2.0",
"eslint": "^5.10.0",
"mocha": "^5.2.0"
},
"engines": {
"node": ">=8.0.0"
}
}
================================================
FILE: src/algorithms/sorting/bubble-sort/README.md
================================================
# Bubble Sort in JavaScript
by [Nicholas C. Zakas](https://humanwhocodes.com)
If you find this useful, please consider supporting my work with a [donation](https://humanwhocodes.com/donate).
## Overview
This is an implementation of the bubble sort algorithm in JavaScript. The function sorts an array in place.
**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.
## Usage
Use CommonJS to get access to the `bubbleSort()` function:
```js
const { bubbleSort } = require("@humanwhocodes/bubble-sort");
const items = [1, 5, 2];
const result = bubbleSort(items);
console.log(result); // [1, 2, 5]
```
## Note on Code Style
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.
## Issues and Pull Requests
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.
## License
MIT
================================================
FILE: src/algorithms/sorting/bubble-sort/bubble-sort.js
================================================
/**
* @fileoverview Bubble sort implementation in JavaScript
*/
/**
* Swaps two values in an array.
* @param {Array} items The array containing the items.
* @param {int} firstIndex Index of first item to swap.
* @param {int} secondIndex Index of second item to swap.
* @returns {void}
*/
function swap(items, firstIndex, secondIndex){
var temp = items[firstIndex];
items[firstIndex] = items[secondIndex];
items[secondIndex] = temp;
}
/**
* A bubble sort implementation in JavaScript. The array
* is sorted in-place.
* @param {Array} items An array of items to sort.
* @return {Array} The sorted array.
*/
exports.bubbleSort = (items) => {
/*
* The outer loop moves from the first item in the array to the last item
* in the array.
*/
for (let i = 0; i < items.length; i++){
/*
* The inner loop also moves from the first item in the array towards
* a stopping point. The `stop` value is the length of the array
* minus the position of the outer loop minus one. The stop position
* is used because items start by being sorted at the back of the
* array and increasing towards the front of the array. The minus one
* is necessary because we are comparing each item to the next
* item, and the last item doesn't have a next item to compare to.
*/
for (let j = 0, stop = items.length - i - 1; j < stop; j++){
/*
* If the item at index `j` is greater than the item at index
* `j + 1`, then swap the values.
*/
if (items[j] > items[j + 1]){
swap(items, j, j + 1);
}
}
}
return items;
};
================================================
FILE: src/algorithms/sorting/bubble-sort/package.json
================================================
{
"name": "@humanwhocodes/bubble-sort",
"version": "2.0.0",
"description": "A bubble sort implementation in JavaScript",
"main": "bubble-sort.js",
"scripts": {
"test": "npx mocha ../../../../tests/algorithms/sorting/bubble-sort/bubble-sort.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/humanwhocodes/computer-science-in-javascript.git"
},
"keywords": [
"sorting",
"algorithm",
"bubble sort"
],
"author": "Nicholas C. Zakas",
"license": "MIT",
"bugs": {
"url": "https://github.com/humanwhocodes/computer-science-in-javascript/issues"
},
"homepage": "https://github.com/humanwhocodes/computer-science-in-javascript#readme",
"engines": {
"node": ">=8.0.0"
}
}
================================================
FILE: src/data-structures/binary-heap/README.md
================================================
# JavaScript Binary Heap Class
by [Nicholas C. Zakas](https://humanwhocodes.com)
If you find this useful, please consider supporting my work with a [donation](https://humanwhocodes.com/donate).
## Overview
A JavaScript implementation of a binary heap. This class uses the conventions of built-in JavaScript collection objects, such as:
1. There is a `[Symbol.iterator]` method so each instance is iterable.
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.
1. Defining a `values()` generator method.
1. Using `includes()` instead of `contains()`.
## Usage
Use CommonJS to get access to the `BinaryHeap` constructor:
```js
const { BinaryHeap } = require("@humanwhocodes/binary-heap");
```
Each instance of `BinaryHeap` has the following properties and methods:
```js
const heap = new BinaryHeap();
// add an item to the end
heap.add("foo");
// get the minimum value without removing
let value = heap.peek();
// get the minimum value and remove
let value = heap.poll();
// get the number of items
let count = heap.size;
// does the value exist in the heap?
let found = heap.includes(5);
// convert to an array using iterators
let array1 = [...heap.values()];
let array2 = [...heap];
// remove all items
heap.clear();
```
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:
```js
// create a max numeric heap
let heap = new BinaryHeap((a, b) => b - a);
```
The comparator function uses the same format as comparator functions for JavaScript arrays, two values are passed in and you must return:
* A negative number if the first value should come before the second
* Zero if the ordering of the two values should remain unchanged
* A positive number if the first value should come after the second
## Note on Code Style
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.
## Issues and Pull Requests
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.
## License
MIT
================================================
FILE: src/data-structures/binary-heap/binary-heap.js
================================================
/**
* @fileoverview Binary Heap implementation in JavaScript
*/
//-----------------------------------------------------------------------------
// Private
//-----------------------------------------------------------------------------
/**
* Determines the index in an array that is the parent of the given index.
* @param {int} index The index to find the parent of.
* @returns {int} The index of the parent value.
* @private
*/
function getParentIndex(index) {
return Math.floor((index - 1) / 2);
}
/**
* Determines the index in an array that is the left child of the given index.
* @param {int} index The index to find the left child of.
* @returns {int} The index of the left child value.
* @private
*/
function getLeftChildIndex(index) {
return (index * 2) + 1;
}
/**
* Determines the index in an array that is the right child of the given index.
* @param {int} index The index to find the right child of.
* @returns {int} The index of the right child value.
* @private
*/
function getRightChildIndex(index) {
return (index * 2) + 2;
}
/**
* Determines if a left child exists for the given index in the array.
* @param {Array} array The array to check.
* @param {int} index The index to check.
* @returns {boolean} True if the index has a left child, false if not.
* @private
*/
function hasLeftChild(array, index) {
return getLeftChildIndex(index) < array.length;
}
/**
* Determines if a right child exists for the given index in the array.
* @param {Array} array The array to check.
* @param {int} index The index to check.
* @returns {boolean} True if the index has a right child, false if not.
* @private
*/
function hasRightChild(array, index) {
return getRightChildIndex(index) < array.length;
}
/**
* Swaps the positions of two values in an array.
* @param {Array} array The array to swap values in.
* @param {int} index1 The first index to swap.
* @param {int} index2 The second index to swap.
* @returns {void}
* @private
*/
function swap(array, index1, index2) {
const value = array[index1];
array[index1] = array[index2];
array[index2] = value;
}
/**
* Normalizes the heap by starting with the last inserted item and ensuring
* the entire path up to the heap root is in the correct order.
* @param {Array} array The array to adjust.
* @param {Function} compare The comparator to use on values in the array.
* @returns {void}
* @private
*/
function heapifyUp(array, compare) {
/*
* `currentIndex` is used to traverse the array. It starts at the last item
* in the array and moves to the first item (the heap root).
*/
let currentIndex = array.length - 1;
/*
* This loop continues so long as `currentIndex` is not the heap root (in
* position 0). When `currentIndex` is 0, it means the path from the last
* inserted value to the heap root is correct.
*/
while (currentIndex > 0) {
// get the index of this value's parent so we can get the value
let parentIndex = getParentIndex(currentIndex);
/*
* If the value of the parent should come after the current value
* (pointed to by `currentIndex`), then swap the two values so they
* are in the correct order. Note that any value returned from the
* comparator that is greater than zero means that the parent value
* should come after the current value.
*/
if (compare(array[parentIndex], array[currentIndex]) > 0) {
swap(array, parentIndex, currentIndex);
// move the current index to the parent so the loop can continue
currentIndex = parentIndex;
} else {
/*
* If we've reached here then the parent and current values are
* already in the correct order. We can infer that this means
* the rest of the path up to the root is also in the correct order
* and so we can safely exit the loop.
*/
break;
}
}
}
/**
* Normalizes the heap by starting with the root item and ensuring
* the entire heap is in the correct order. This is run after a node is
* removed from the heap and the root is replaced with a value, so
* the root is most likely incorrect.
* @param {Array} array The array to adjust.
* @param {Function} compare The comparator to use on values in the array.
* @returns {void}
* @private
*/
function heapifyDown(array, compare) {
/*
* `currentIndex` is used to traverse the array. It starts at the first item
* in the array and moves towards the last.
*/
let currentIndex = 0;
/*
* This loop continues so long as the current item has at least one child.
* Because the heap is filled in starting with the left child and then
* moving to the right, simply checking if a left child is present is enough
* to continue because we know there is at least one child.
*
* When the current item has no children, we know the entire path there is
* in the correct state and so we can exit.
*/
while (hasLeftChild(array, currentIndex)) {
/*
* This variable is called `smallerChildIndex` because we want to
* identify which of the two children contain the smaller value.
* We can start by assuming this will be the left child and then
* change it if we discover the right child is actually smaller.
*/
let smallerChildIndex = getLeftChildIndex(currentIndex);
// if there is a right child, check that
if (hasRightChild(array, currentIndex)) {
let rightChildIndex = getRightChildIndex(currentIndex);
/*
* If the right child value should come after the left child value
* (meaning the `compare()` function returns a value greater than
* zero), then note that the smaller value is actually in the right
* child by storing the right child index in `smallerChildIndex`.
*/
if (compare(array[smallerChildIndex], array[rightChildIndex]) > 0) {
smallerChildIndex = rightChildIndex;
}
}
/*
* If the current value should come after the smaller child value, then
* the two values need to be swapped to be in the correct order.
*/
if (compare(array[currentIndex], array[smallerChildIndex]) > 0) {
swap(array, currentIndex, smallerChildIndex);
// move down the tree to the previous location of the smaller child
currentIndex = smallerChildIndex;
} else {
/*
* If we've reached here then the current and child values are
* already in the correct order. We can infer that this means
* the rest of the path down is also in the correct order
* and so we can safely exit the loop.
*/
break;
}
}
}
//-----------------------------------------------------------------------------
// BinaryHeap Class
//-----------------------------------------------------------------------------
/*
* These symbols are used to represent properties that should not be part of
* the public interface. You could also use ES2019 private fields, but those
* are not yet widely available as of the time of my writing.
*/
const array = Symbol("array");
const compare = Symbol("compare");
/**
* A binary heap implementation in JavaScript.
* @class BinaryHeap
*/
class BinaryHeap {
/**
* Creates a new instance of BinaryHeap
* @param {Function} [comparator] A comparator function.
*/
constructor(comparator = (a, b) => a - b) {
/**
* Array used to manage the heap.
* @property array
* @type Array
* @private
*/
this[array] = [];
/**
* Comparator to compare values.
* @property comparator
* @type Function
* @private
*/
this[compare] = comparator;
}
/**
* Appends some data to the heap.
* @param {*} data The data to add to the heap.
* @returns {void}
*/
add(data) {
this[array].push(data);
heapifyUp(this[array], this[compare]);
}
/**
* Determines if the heap is empty.
* @returns {boolean} True if the heap is empty, false if not.
*/
isEmpty() {
return this[array].length === 0;
}
/**
* Returns the value at the top of the heap but does not remove it from
* the heap.
* @returns {*} The value at the top of the heap.
*/
peek() {
if (this.isEmpty()) {
throw new Error("Heap is empty.");
}
return this[array][0];
}
/**
* Returns and removes the value at the top of the heap.
* @returns {*} The value at the top of the heap.
*/
poll() {
if (this.isEmpty()) {
throw new Error("Heap is empty.");
}
/*
* If there are at least two items in the array, then we need to do a
* remove and rebalance operation to ensure the heap remains consistent.
*/
if (this[array].length > 1) {
// first remove the top value for safe keeping
const topValue = this[array][0];
/*
* Next, take the last item from the array and move it into the top
* slot. This will likely not be the correct value but maintains the
* tree hierarchy. Then, reorganize the heap so it remains properly
* ordered.
*/
const replacementValue = this[array].pop();
this[array][0] = replacementValue;
heapifyDown(this[array], this[compare]);
// finally, return the value
return topValue;
} else {
/*
* In this case, the array has only one item, so it's simpler to just
* pop the value off of the array and return it.
*/
return this[array].pop();
}
}
/**
* Returns the number of values in the heap.
* @returns {int} The number of values in the heap.
*/
get size() {
return this[array].length;
}
/**
* Determines if the given value exists in the heap.
* @param {*} value The value to search for.
* @returns {boolean} True if the value exists in the heap, false if not.
*/
includes(value) {
return this[array].includes(value);
}
/**
* Removes all values from the heap.
* @returns {void}
*/
clear() {
this[array] = [];
}
/**
* The default iterator for the class.
* @returns {Iterator} An iterator for the class.
*/
[Symbol.iterator]() {
return this.values();
}
/**
* Create an iterator that returns each node in the list.
* @returns {Iterator} An iterator on the list.
*/
values() {
return this[array].values();
}
/**
* Converts the heap into a string representation.
* @returns {String} A string representation of the heap.
*/
toString(){
return [...this[array]].toString();
}
}
exports.BinaryHeap = BinaryHeap;
================================================
FILE: src/data-structures/binary-heap/package.json
================================================
{
"name": "@humanwhocodes/binary-heap",
"version": "2.0.1",
"description": "A binary heap implementation in JavaScript",
"main": "binary-heap.js",
"scripts": {
"test": "npx mocha ../../../tests/data-structures/binary-heap/binary-heap.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/humanwhocodes/computer-science-in-javascript.git"
},
"keywords": [
"heap",
"binary heap",
"data structure"
],
"author": "Nicholas C. Zakas",
"license": "MIT",
"bugs": {
"url": "https://github.com/humanwhocodes/computer-science-in-javascript/issues"
},
"homepage": "https://github.com/humanwhocodes/computer-science-in-javascript#readme",
"engines": {
"node": ">=8.0.0"
}
}
================================================
FILE: src/data-structures/binary-search-tree/README.md
================================================
# JavaScript Binary Search Tree Class
by [Nicholas C. Zakas](https://humanwhocodes.com)
If you find this useful, please consider supporting my work with a [donation](https://humanwhocodes.com/donate).
## Overview
A JavaScript implementation of a binary search tree. This class uses the conventions of built-in JavaScript collection objects, such as:
1. There is a `[Symbol.iterator]` method so each instance is iterable.
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.
1. Defining a `values()` generator method.
Additionally, this implementation follows the JavaScript `Set` interface for adding, detecting, and removing values:
* `add(value)` to add a value into the tree
* `has(value)` to detect if a value is in the tree
* `delete(value)` to remove a value from the tree
## Usage
Use CommonJS to get access to the `BinarySearchTree` constructor:
```js
const { BinarySearchTree } = require("@humanwhocodes/binary-search-tree");
```
Each instance of `BinarySearchTree` has the following properties and methods:
```js
const tree = new BinarySearchTree();
// add an item to the tree
tree.add(2);
// determine if a value is in the tree
let found = tree.has(2);
// get the number of nodes in the tree
let count = tree.size;
// convert to an array using iterators
let array1 = [...tree.values()];
let array2 = [...tree];
// remove a node with the given value
let value = tree.delete(2);
// remove all nodes
tree.clear();
```
## Note on Code Style
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.
## Issues and Pull Requests
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.
## License
MIT
================================================
FILE: src/data-structures/binary-search-tree/binary-search-tree.js
================================================
/**
* @fileoverview Binary Search Tree implementation in JavaScript
*/
/*
* These symbols are used to represent properties that should not be part of
* the public interface. You could also use ES2019 private fields, but those
* are not yet widely available as of the time of my writing.
*/
const root = Symbol("root");
/**
* Represents a single node in a BinarySearchTree.
* @class BinarySearchTree
*/
class BinarySearchTreeNode {
/**
* Creates a new instance of BinarySearchTreeNode.
* @param {*} value The value to store in the node.
*/
constructor(value) {
/**
* The value that this node stores.
* @property value
* @type *
*/
this.value = value;
/**
* A pointer to the left node in the BinarySearchTree.
* @property left
* @type ?BinarySearchTreeNode
*/
this.left = null;
/**
* A pointer to the right node in the BinarySearchTree.
* @property right
* @type ?BinarySearchTreeNode
*/
this.right = null;
}
}
/**
* A linked tree implementation in JavaScript.
* @class BinarySearchTree
*/
class BinarySearchTree {
/**
* Creates a new instance of BinarySearchTree
*/
constructor() {
/**
* Pointer to the root node in the tree.
* @property root
* @type ?BinarySearchTreeNode
* @private
*/
this[root] = null;
}
/**
* Adds some value into the tree. This method traverses the tree to find
* the correct location to insert the value. Duplicate values are discarded.
* @param {*} value The value to add to the tree.
* @returns {void}
*/
add(value) {
/*
* Create a new node to insert into the tree and store the value in it.
* This node will be added into the tree.
*/
const newNode = new BinarySearchTreeNode(value);
// special case: no nodes in the tree yet
if (this[root] === null) {
this[root] = newNode;
} else {
/*
* The `current` variable is used to track the item that is being
* used inside of the loop below. It starts out pointing to
* `this[root]` and is overwritten inside of the loop.
*/
let current = this[root];
/*
* This is loop is broken only when the node has been inserted
* in the correct spot or a duplicate value is found.
*/
while (current !== null) {
// if the new value is less than this node's value, go left
if (value < current.value) {
//if there's no left, then the new node belongs there
if (current.left === null) {
current.left = newNode;
break;
} else {
current = current.left;
}
// if the new value is greater than this node's value, go right
} else if (value > current.value) {
//if there's no right, then the new node belongs there
if (current.right === null) {
current.right = newNode;
break;
} else {
current = current.right;
}
// if the new value is equal to the nodeToRemove one, just ignore
} else {
break;
}
}
}
}
/**
* Determines if a given value exists in the tree.
* @param {*} value The value to find.
* @returns {boolean} True if the value is found in the tree, false if not.
*/
has(value) {
/*
* `found` keeps track of whether or not the value in question was
* found in the tree. This variable is used for both the control
* condition of the loop below and is the return value of this function.
*/
let found = false;
/*
* The `current` variable is used to track the item that is being
* used inside of the loop below. It starts out pointing to
* `this[root]` and is overwritten inside of the loop.
*/
let current = this[root];
/*
* The loop continues until either the value is found (so `found`
* is `true`) or the tree has been completely searched
* (`current` is `null`).
*/
while (!found && current !== null) {
// if the value is less than the current node's, go left
if (value < current.value) {
current = current.left;
// if the value is greater than the current node's, go right
} else if (value > current.value) {
current = current.right;
//values are equal, found it!
} else {
found = true;
}
}
/*
* Execution reaches here either because `found` is `true`, or because
* the tree was completely searched and the value was not found. Either
* way, the value of `found` is now the result of the search, so just
* return it.
*/
return found;
}
/**
* Deletes the value from the tree.
* @param {int} index The zero-based index of the item to remove.
* @returns {*} The value in the given position in the tree.
* @throws {RangeError} If index is out of range.
*/
delete(value) {
// special case: the tree is empty, just exit
if (this[root] === null) {
return;
}
/*
* The `found` variable keeps track of whether or not the value has
* been found in the tree.
*/
let found = false;
/*
* These two variables keep track of the location during traversal.
* The `current` variable is the node we have traversed to while the
* `parent` variable is the parent node of `current`. Unlike with other
* traversals, we need to keep track of the parent so we can remove
* the child node.
*/
let current = this[root],
parent = null;
/*
* The first step is to do a search for the value to remove. This is
* the same algorithm as the `has()` method, with the difference being
* that the parent is also tracked.
*/
while (!found && current !== null) {
// if the value is less than the current node's, go left
if (value < current.value) {
parent = current;
current = current.left;
// if the value is greater than the current node's, go right
} else if (value > current.value) {
parent = current;
current = current.right;
// values are equal, found it!
} else {
found = true;
}
}
// if the value wasn't found, just exit
if (!found) {
return;
}
/*
* If we make it to here, the `nodeToRemove` variable continues the node
* to remove. This assignment isn't necessary but makes it easier to
* figure out what's going on in the code below.
*/
const nodeToRemove = current;
/*
* The `replacement` variable is filled with what should replace
* `nodeToRemove`. It starts out set to `null` but can change based
* on what we find later.
*/
let replacement = null;
/*
* The most complicated case is when the `nodeToRemove` node has both a left
* and a right child. In that case, we need to move things around to
* ensure the tree remains properly structured.
*/
if ((nodeToRemove.left !== null) && (nodeToRemove.right !== null)) {
/*
* We need to find the best replacement for the removed node by
* traversing the subtrees. To start, we assume that the best
* replacement is `nodeToRemove.left`.
*/
replacement = nodeToRemove.left;
/*
* We need to keep track of the replacement's parent to modify
* the subtree as we go.
*/
let replacementParent = nodeToRemove;
/*
* The best replacement is found by traversing the right subtree
* of `replacement`. The rightmost node in this subtree is the
* largest value in `nodeToRemove`'s left subtree and so is the
* easiest one to use as a replacement to minimize the number of
* modifications we need to do.
*/
while (replacement.right !== null) {
replacementParent = replacement;
replacement = replacement.right;
}
/*
* Because `replacement` has no right subtree, we can copy over the
* `nodeToRemove`'s right subtree directly.
*/
replacement.right = nodeToRemove.right;
/*
* Special case: if `nodeToRemove.left` doesn't have a right subtree,
* then `replacementParent` will be equal to `nodeToRemove`. In that
* case, we should not make any further changes. Otherwise, we need
* to rearrange some more nodes.
*/
if (replacementParent !== nodeToRemove) {
/*
* Remove `replacement` from its current location and replace it
* with `replacement.left` (we know `replacement.right` is `null`).
* It's possible that `replacement.left` is `null`, but that doesn't
* matter. Both `null` and non-`null` values keep the tree intact.
*/
replacementParent.right = replacement.left;
/*
* Assign the complete left subtree of `nodeToRemove` to
* `replacement` to maintain the proper structure.
*/
replacement.left = nodeToRemove.left;
}
} else if (nodeToRemove.left !== null) {
replacement = nodeToRemove.left;
} else if (nodeToRemove.right !== null) {
replacement = nodeToRemove.right;
}
/*
* If the `nodeToRemove` node has no children, then the default value of
* `null` is used for `replacement`.
*/
// special case: the `nodeToRemove` node is the root
if (nodeToRemove === this[root]) {
this[root] = replacement;
} else {
if (nodeToRemove.value < parent.value) {
parent.left = replacement;
} else {
parent.right = replacement;
}
}
}
/**
* Removes all nodes from the tree.
* @returns {void}
*/
clear() {
this[root] = null;
}
/**
* Returns the number of items in the tree.
* @returns {int} The number of items in the tree.
*/
get size() {
// special case: the tree is empty
if (this[root] === null) {
return 0;
}
/*
* The `count` variable is used to keep track of how many items have
* been visited inside the loop below. This is important because this
* is the value to return from this method.
*/
let count = 0;
/*
* Traversal is easiest when using a recursive function, so define
* a helper function here. This function does an in-order traversal
* of the tree, meaning it yields values in sorted order from
* lowest value to highest. It does this by traversing to the leftmost
* node first, then working its way back up the tree, visiting right nodes
* along the way.
*/
const traverse = (node) => {
// special case: there is no node
if (node) {
//traverse the left subtree
if (node.left !== null) {
traverse(node.left);
}
// increment the counter
count++;
//traverse the right subtree
if (node.right !== null) {
traverse(node.right);
}
}
};
// start traversing from the root
traverse(this[root]);
// return the final count, which was updated inside traverse()
return count;
}
/**
* The default iterator for the class.
* @returns {Iterator} An iterator for the class.
*/
[Symbol.iterator]() {
return this.values();
}
/**
* Create an iterator that returns each node in the tree.
* @returns {Iterator} An iterator on the tree.
*/
*values(){
/*
* Traversal is easiest when using a recursive function, so define
* a helper function here. This function does an in-order traversal
* of the tree, meaning it yields values in sorted order from
* lowest value to highest. It does this by traversing to the leftmost
* node first, then working its way back up the tree, visiting right nodes
* along the way.
*
* This function cannot be an arrow function because arrow functions
* cannot be generators.
*/
function *traverse(node) {
// special case: there is no node
if (node) {
//traverse the left subtree
if (node.left !== null) {
yield* traverse(node.left);
}
// emit the value
yield node.value;
//traverse the right subtree
if (node.right !== null) {
yield* traverse(node.right);
}
}
}
yield* traverse(this[root]);
}
/**
* Converts the tree into a string representation.
* @returns {String} A string representation of the tree.
*/
toString(){
return [...this].toString();
}
}
exports.BinarySearchTree = BinarySearchTree;
================================================
FILE: src/data-structures/binary-search-tree/package.json
================================================
{
"name": "@humanwhocodes/binary-search-tree",
"version": "2.0.0",
"description": "A binary search tree implementation in JavaScript",
"main": "binary-search-tree.js",
"scripts": {
"test": "npx mocha ../../../tests/data-structures/binary-search-tree/binary-search-tree.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/humanwhocodes/computer-science-in-javascript.git"
},
"keywords": [
"binary search tree",
"linked list",
"data structure",
"iterable"
],
"author": "Nicholas C. Zakas",
"license": "MIT",
"bugs": {
"url": "https://github.com/humanwhocodes/computer-science-in-javascript/issues"
},
"homepage": "https://github.com/humanwhocodes/computer-science-in-javascript#readme",
"engines": {
"node": ">=8.0.0"
}
}
================================================
FILE: src/data-structures/circular-doubly-linked-list/README.md
================================================
# JavaScript Circular Doubly Linked List Class
by [Nicholas C. Zakas](https://humanwhocodes.com)
If you find this useful, please consider supporting my work with a [donation](https://humanwhocodes.com/donate).
## Overview
A JavaScript implementation of a circular doubly linked list. This class uses the conventions of built-in JavaScript collection objects, such as:
1. There is a `[Symbol.iterator]` method so each instance is iterable.
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.
1. Defining a `values()` generator method.
1. Returning `undefined` from `get()` when no such index exists.
Read the [blog post](https://humanwhocodes.com/blog/2019/03/computer-science-in-javascript-circular-doubly-linked-lists/) about the design of this class.
## Usage
Use CommonJS to get access to the `CircularDoublyLinkedList` constructor:
```js
const { CircularDoublyLinkedList } = require("@humanwhocodes/circular-doubly-linked-list");
```
Each instance of `CircularDoublyLinkedList` has the following properties and methods:
```js
const list = new CircularDoublyLinkedList();
// add an item to the end
list.add("foo");
// insert an item
list.insertBefore("bar", 0);
list.insertAfter("baz", 1);
// get the value at an index
let value = list.get(0);
// get the number of items
let count = list.size;
// get the index of a value
let index = list.indexOf("foo");
// convert to an array using iterators
let array1 = [...list.values()];
let array2 = [...list];
// create a circular iterator to keep iterating over values
const iterator = list.circularValues();
// convert to an array in reverse order using an iterator
let array3 = [...list.reverse()];
// remove an item at the given index and return the data that was removed
let data = list.remove(0);
// remove all items
list.clear();
```
## Note on Code Style
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.
## Issues and Pull Requests
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.
## License
MIT
================================================
FILE: src/data-structures/circular-doubly-linked-list/circular-doubly-linked-list.js
================================================
/**
* @fileoverview Circular Doubly linked list implementation in JavaScript
*/
/*
* These symbols are used to represent properties that should not be part of
* the public interface. You could also use ES2019 private fields, but those
* are not yet widely available as of the time of my writing.
*/
const head = Symbol("head");
/**
* Represents a single item in a CircularDoublyLinkedList.
* @class CircularDoublyLinkedListNode
*/
class CircularDoublyLinkedListNode {
/**
* Creates a new instance of CircularDoublyLinkedListNode.
* @param {*} data The data to store in the node.
*/
constructor(data) {
/**
* The data that this node stores.
* @property data
* @type *
*/
this.data = data;
/**
* A pointer to the next item in the CircularDoublyLinkedList.
* @property next
* @type ?CircularDoublyLinkedListNode
*/
this.next = null;
/**
* A pointer to the previous item in the CircularDoublyLinkedList.
* @property previous
* @type ?CircularDoublyLinkedListNode
*/
this.previous = null;
}
}
/**
* A doubly linked list implementation in JavaScript.
* @class CircularDoublyLinkedList
*/
class CircularDoublyLinkedList {
/**
* Creates a new instance of CircularDoublyLinkedList
*/
constructor() {
/**
* Pointer to first item in the list.
* @property head
* @type ?CircularDoublyLinkedListNode
* @private
*/
this[head] = null;
}
/**
* Appends some data to the end of the list. This method traverses
* the existing list and places the data at the end in a new item.
* @param {*} data The data to add to the list.
* @returns {void}
*/
add(data) {
/*
* Create a new list item object and store the data in it.
* This item will be added to the end of the existing list.
*/
const newNode = new CircularDoublyLinkedListNode(data);
// special case: no items in the list yet
if (this[head] === null) {
/*
* Because there are no items in the list, just set the
* `this[head]` pointer to the new item.
*/
this[head] = newNode;
/*
* Setup the new node to point to itself in both directions
* to create the circular link.
*/
newNode.next = newNode;
newNode.previous = newNode;
} else {
// get a reference to the last item in the list
const tail = this[head].previous;
/*
* Setup the tail and `newNode` to point to each other, effectively
* adding `newNode` to the end of the list.
*/
tail.next = newNode;
newNode.previous = tail;
/*
* Because `newNode` is now the last item, `newNode.next` must point
* to the head and vice versa.
*/
newNode.next = this[head];
this[head].previous = newNode;
}
}
/**
* Inserts some data into the middle of the list. This method traverses
* the existing list and places the data in a new item at a specific index.
* @param {*} data The data to add to the list.
* @param {int} index The zero-based index at which to insert the data.
* @returns {void}
* @throws {RangeError} If the index doesn't exist in the list.
*/
insertBefore(data, index) {
/*
* Create a new list item object and store the data in it.
* This item will be inserted into the existing list.
*/
const newNode = new CircularDoublyLinkedListNode(data);
// special case: no nodes in the list yet
if (this[head] === null) {
throw new RangeError(`Index ${index} does not exist in the list.`);
}
/*
* Special case: if `index` is `0`, then no traversal is needed
* and we need to update `this[head]` to point to `newNode`.
*/
if (index === 0) {
// get the last item in the list to make things a bit clearer
const tail = this[head].previous;
// first make `tail` and `newNode` point to each other
tail.next = newNode;
newNode.previous = tail;
// then make `this[head]` and `newNode` point to each other
newNode.next = this[head];
this[head].previous = newNode;
// now it's safe to update `this[head]` to be `newNode`
this[head] = newNode;
} else {
/*
* The `current` variable is used to track the node that is being
* used inside of the loop below. It starts out pointing to
* `this[head]` and is overwritten inside of the loop.
*
* The `previous` variable tracks one step behind `current`, which
* is necessary because we need to adjust the node at `index`-1's
* `next` pointer to point to the new node.
*/
let current = this[head],
previous = null;
/*
* The `i` variable is used to track how deep into the list we've
* gone. This important because it's the only way to know when
* we've hit the `index` to insert into.
*/
let i = 0;
/*
* Traverse and make sure to keep track of how many nodes have
* been visited and update the `previous` pointer in addition to
* `current`. When `i` is the same as `index`, it means we've
* found the location to insert the new data.
*/
while ((current.next !== this[head]) && (i < index)) {
previous = current;
current = current.next;
i++;
}
/*
* At this point, `current` is either the item to insert the new data
* before, or the last item in the list. The only way to tell is if
* `i` is still less than `index`, that means the index is out of range
* and an error should be thrown.
*/
if (i < index) {
throw new RangeError(`Index ${index} does not exist in the list.`);
}
/*
* If code continues to execute here, it means `current` is the node
* to insert new data before and `previous` is the node to insert
* new data after. So `previous.next` must point to `newNode` and
* `newNode.next` must point to `current`.
*/
previous.next = newNode;
newNode.previous = previous;
newNode.next = current;
current.previous = newNode;
}
}
/**
* Inserts some data into the middle of the list. This method traverses
* the existing list and places the data in a new item after a specific index.
* @param {*} data The data to add to the list.
* @param {int} index The zero-based index after which to insert the data.
* @returns {void}
* @throws {RangeError} If the index doesn't exist in the list.
*/
insertAfter(data, index) {
/*
* Create a new list item object and store the data in it.
* This item will be inserted into the existing list.
*/
const newNode = new CircularDoublyLinkedListNode(data);
// special case: no nodes in the list yet
if (this[head] === null) {
throw new RangeError(`Index ${index} does not exist in the list.`);
}
/*
* The `current` variable is used to track the node that is being
* used inside of the loop below. It starts out pointing to
* `this[head]` (the first node) and is overwritten inside of the loop.
*/
let current = this[head];
// special case: insert after index 0 doesn't require a traversal
if (index > 0) {
/*
* The `i` variable is used to track how deep into the list we've
* gone. This important because it's the only way to know when
* we've hit the `index` to insert into.
*/
let i = 0;
/*
* Traverse and keep track of how many nodes have been visited and
* update the `current` pointer. When `i` is the same as `index`,
* it means we've found the location to insert the new data.
*/
do {
current = current.next;
i++;
} while ((current !== this[head]) && (i < index));
/*
* At this point, `current` is either the node to insert the new data
* before, or the last node in the list. The only way to tell is if
* `i` is still less than `index`, that means the index is out of range
* and an error should be thrown.
*/
if (i < index) {
throw new RangeError(`Index ${index} does not exist in the list.`);
}
}
/*
* If code continues to execute here, it means `current` is the node
* to insert new data after. So `current.next` must point to
* `newNode` for the data to be in the correct spot, but before that,
* `newNode.next` must point to `current.next` to ensure the list
* remains intact.
*/
newNode.next = current.next;
current.next.previous = newNode;
current.next = newNode;
newNode.previous = current;
}
/**
* Retrieves the data in the given position in the list.
* @param {int} index The zero-based index of the item whose data
* should be returned.
* @returns {*} The data in the "data" portion of the given item
* or undefined if the item doesn't exist.
*/
get(index) {
// ensure `index` is a positive value and the list isn't empty
if ((index > -1) && (this[head] !== null)) {
/*
* The `current` variable is used to track the node that is being
* used inside of the loop below. It starts out pointing to
* `this[head]` (the first node) and is overwritten inside of the loop.
*/
let current = this[head];
/*
* The `i` variable is used to track how deep into the list we've
* gone. This is important because it's the only way to know when
* we've hit the `index` to insert into.
*/
let i = 0;
/*
* Traverse the nodes in the list and keep track of how deep we are
* into the list. If we've reached the first node in the list
* (`this[head]`) or we've gone past the end of the list
* (`i > index`), then exit the loop.
*/
do {
/*
* If the current position matches, the index being requested,
* then return the data in the current node and exit immediately.
*/
if (i === index) {
return current.data;
}
// otherwise, go on to the next node
current = current.next;
// and increment the index
i++;
} while ((current !== this[head]) && (i <= index));
/*
* If we've made it here, it means that that the index is past the
* end of the list. Execution now falls through the last `return`
* statement in this method, returning `undefined` to indicate no
* data was found.
*/
}
return undefined;
}
/**
* Retrieves the index of the data in the list.
* @param {*} data The data to search for.
* @returns {int} The index of the first instance of the data in the list
* or -1 if not found.
*/
indexOf(data) {
// special case: the list is empty so there's nothing to search
if (this[head] === null) {
return -1;
}
/*
* The `current` variable is used to iterate over the list nodes.
* It starts out pointing to the head and is overwritten inside
* of the loop below.
*/
let current = this[head];
/*
* The `index` variable is used to track how deep into the list we've
* gone. This is important because this is the value that is returned
* from this method.
*/
let index = 0;
/*
* This loop checks each node in the list to see if it matches `data`.
* If a match is found, it returns `index` immediately, exiting the
* loop because there's no reason to keep searching. The search
* continues until there are no more nodes to search because `current`
* is once again pointing the first node in the list.
*/
do {
/*
* If the data in the current node matches the data we are looking
* for, then return `index`. This exits the loop immediately.
*/
if (current.data === data) {
return index;
}
// otherwise, go on to the next node
current = current.next;
// and increment the index
index++;
} while (current !== this[head]);
/*
* If execution gets to this point, it means we reached the end of the
* list and didn't find `data`. Just return -1 as the "not found" value.
*/
return -1;
}
/**
* Removes the item from the given location in the list.
* @param {int} index The zero-based index of the item to remove.
* @returns {*} The data in the given position in the list.
* @throws {RangeError} If index is out of range.
*/
remove(index) {
// special cases: no nodes in the list or `index` is an invalid value
if ((this[head] === null) || (index < 0)) {
throw new RangeError(`Index ${index} does not exist in the list.`);
}
/*
* The `current` variable is used to iterate over the list nodes.
* It starts out pointing to the head and is overwritten inside
* of the loop below.
*/
let current = this[head];
// special case: removing the first node
if (index === 0) {
// if there's only one node, null out `this[head]`
if (current.next === this[head]) {
this[head] = null;
} else {
// get the last item in the list
const tail = this[head].previous;
/*
* Set the tail to point to the second item in the list.
* Then make sure that item also points back to the tail.
*/
tail.next = current.next;
current.next.previous = tail;
// now it's safe to update the head
this[head] = tail.next;
}
// return the data at the previous head of the list
return current.data;
}
/*
* The `i` variable is used to track how deep into the list we've
* gone. This is important because it's the only way to know when
* we've hit the `index` to remove.
*/
let i = 0;
/*
* Traverse the list and exit the loop when either the start of the
* list is encountered or `i` is no longer less than `index` (meaning
* we have found the node to remove).
*/
do {
// traverse to the next node
current = current.next;
// increment the count
i++;
} while ((current !== this[head]) && (i < index));
/*
* If `current` isn't `this[head]`, then that means we've found the node
* to remove.
*/
if (current !== this[head]) {
// skip over the node to remove
current.previous.next = current.next;
current.next.previous = current.previous;
// return the value that was just removed from the list
return current.data;
}
/*
* If we've made it this far, it means `index` is a value that
* doesn't exist in the list, so throw an error.
*/
throw new RangeError(`Index ${index} does not exist in the list.`);
}
/**
* Removes all items from the list.
* @returns {void}
*/
clear() {
// just reset the head pointer to null
this[head] = null;
}
/**
* Returns the number of items in the list.
* @returns {int} The number of items in the list.
*/
get size() {
// special case: the list is empty
if (this[head] === null) {
return 0;
}
/*
* The `current` variable is used to iterate over the list nodes.
* It starts out pointing to the head and is overwritten inside
* of the loop below. `this[head]` points to the first node
* in the list because the last node always points to the first node.
*/
let current = this[head];
/*
* The `count` variable is used to keep track of how many nodes have
* been visited inside the loop below. This is important because this
* is the value to return from this method.
*/
let count = 0;
/*
* Because the list is circular, we need to stop when `current` is
* equal to `this[head]`, otherwise this will be an infinite loop.
*/
do {
count++;
current = current.next;
} while (current !== this[head]);
/*
* The loop is exited and the value of `count`
* is the number of nodes that were counted in the loop.
*/
return count;
}
/**
* The default iterator for the class.
* @returns {Iterator} An iterator for the class.
*/
[Symbol.iterator]() {
return this.values();
}
/**
* Create an iterator that returns each node in the list.
* @returns {Iterator} An iterator on the list.
*/
*values() {
// special case: list is empty
if (this[head] !== null) {
// special case: only one node
if (this[head].next === this[head]) {
yield this[head].data;
} else {
/*
* The `current` variable is used to iterate over the list nodes.
* It starts out pointing to the head and is overwritten inside
* of the loop below.
*/
let current = this[head];
/*
* Because the list is circular, we need to stop when `current` is
* equal to the first node, otherwise this will be an infinite loop.
* And because `current` starts out equal to the first node, we need
* to use a post-test loop because we know the loop should execute
* at least once.
*/
do {
yield current.data;
current = current.next;
} while (current !== this[head]);
}
}
}
/**
* Create an iterator that returns each node in the list and repeats
* each node if it continues to be called. This is designed to be used
* to manually iterate through the list, outside of using syntax such
* as `for-of` (which will result in an infinite loop with this iterator).
* @returns {Iterator} A circular iterator on the list.
*/
*circularValues() {
// special case: list is empty
if (this[head] !== null) {
/*
* The `current` variable is used to iterate over the list nodes.
* It starts out pointing to the head and is overwritten inside
* of the loop below.
*/
let current = this[head];
/*
* This is an infinite loop if you remove the `yield` call. The `yield`
* allows execution to stop and not pick up again until the iterator's
* `next()` method is called again.
*
* It's not possible for this loop to exit. Even removing all nodes
* from the list using `remove()` or `clear()` will not cause the
* loop to stop yield values. That's because `current.next` always
* has a value, even if it just points back to `current`.
*/
do {
yield current.data;
current = current.next;
} while (true);
}
}
/**
* Create an iterator that returns each item in the list in reverse order.
* @returns {Iterator} An iterator on the list.
*/
*reverse(){
// special case: list is empty
if (this[head] !== null) {
/*
* The `current` variable is used to iterate over the list items.
* It starts out pointing to the head and is overwritten inside
* of the loop below.
*/
let current = this[head].previous;
/*
* As long as `current` is not `head`, there is a piece of data
* to yield.
*/
do {
yield current.data;
current = current.previous;
} while (current !== this[head].previous);
}
}
/**
* Converts the list into a string representation.
* @returns {String} A string representation of the list.
*/
toString(){
return [...this].toString();
}
}
exports.CircularDoublyLinkedList = CircularDoublyLinkedList;
================================================
FILE: src/data-structures/circular-doubly-linked-list/package.json
================================================
{
"name": "@humanwhocodes/circular-doubly-linked-list",
"version": "2.0.2",
"description": "A circular doubly linked list implementation in JavaScript",
"main": "circular-doubly-linked-list.js",
"scripts": {
"test": "npx mocha ../../../tests/data-structures/circular-doubly-linked-list/circular-doubly-linked-list.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/humanwhocodes/computer-science-in-javascript.git"
},
"keywords": [
"circular linked list",
"cicular doubly linked list",
"linked list",
"doubly linked list",
"data structure",
"iterable"
],
"author": "Nicholas C. Zakas",
"license": "MIT",
"bugs": {
"url": "https://github.com/humanwhocodes/computer-science-in-javascript/issues"
},
"homepage": "https://github.com/humanwhocodes/computer-science-in-javascript#readme",
"engines": {
"node": ">=8.0.0"
}
}
================================================
FILE: src/data-structures/circular-linked-list/README.md
================================================
# JavaScript Circular Linked List Class
by [Nicholas C. Zakas](https://humanwhocodes.com)
If you find this useful, please consider supporting my work with a [donation](https://humanwhocodes.com/donate).
## Overview
A JavaScript implementation of a linked list. This class uses the conventions of built-in JavaScript collection objects, such as:
1. There is a `[Symbol.iterator]` method so each instance is iterable.
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.
1. Defining a `values()` generator method.
1. Returning `undefined` from `get()` when no such index exists.
## Usage
Use CommonJS to get access to the `CircularLinkedList` constructor:
```js
const { CircularLinkedList } = require("@humanwhocodes/circular-linked-list");
```
Each instance of `CircularLinkedList` has the following properties and methods:
```js
const list = new CircularLinkedList();
// add an item to the end
list.add("foo");
// insert an item
list.insertBefore("bar", 0);
list.insertAfter("baz", 1);
// get the value at an index
let value = list.get(0);
// get the number of items
let count = list.size;
// get the index of a value
let index = list.indexOf("foo");
// convert to an array using iterators
let array1 = [...list.values()];
let array2 = [...list];
// create a circular iterator to keep iterating over values
const iterator = list.circularValues();
// remove an item at the given index and return the data that was removed
let data = list.remove(0);
// remove all items
list.clear();
```
## Note on Code Style
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.
## Issues and Pull Requests
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.
## License
MIT
================================================
FILE: src/data-structures/circular-linked-list/circular-linked-list.js
================================================
/**
* @fileoverview Circular Linked List implementation in JavaScript
*/
/*
* These symbols are used to represent properties that should not be part of
* the public interface. You could also use ES2019 private fields, but those
* are not yet widely available as of the time of my writing.
*/
const tail = Symbol("tail");
/**
* Represents a single node in a LinkedList.
* @class CircularLinkedListNode
*/
class CircularLinkedListNode {
/**
* Creates a new instance of CircularLinkedListNode.
* @param {*} data The data to store in the node.
*/
constructor(data) {
/**
* The data that this node stores.
* @property data
* @type *
*/
this.data = data;
/**
* A pointer to the next node in the LinkedList.
* @property next
* @type ?CircularLinkedListNode
*/
this.next = null;
}
}
/**
* A linked list implementation in JavaScript.
* @class CircularLinkedList
*/
class CircularLinkedList {
/**
* Creates a new instance of LinkedList
*/
constructor() {
/**
* Pointer to last node added to the list.
* @property tail
* @type ?CircularLinkedListNode
* @private
*/
this[tail] = null;
}
/**
* Appends some data to the end of the list. This method traverses
* the existing list and places the data at the end in a new node.
* @param {*} data The data to add to the list.
* @returns {void}
*/
add(data) {
/*
* Create a new list node object and store the data in it.
* This node will be added to the end of the existing list.
*/
const newNode = new CircularLinkedListNode(data);
//special case: no nodes in the list yet
if (this[tail] === null) {
/*
* Because there are no nodes in the list, set `node.next`
* equal to itself to complete the cycle.
*/
newNode.next = newNode;
} else {
/*
* We are inserting `newNode` in between `this[tail]` and
* `this[tail].next`, so update pointers.
*/
newNode.next = this[tail].next;
this[tail].next = newNode;
}
// update the pointer to the last inserted node
this[tail] = newNode;
}
/**
* Inserts some data into the middle of the list. This method traverses
* the existing list and places the data in a new node at a specific index.
* @param {*} data The data to add to the list.
* @param {int} index The zero-based index at which to insert the data.
* @returns {void}
* @throws {RangeError} If the index doesn't exist in the list.
*/
insertBefore(data, index) {
/*
* Create a new list node object and store the data in it.
* This node will be inserted into the existing list.
*/
const newNode = new CircularLinkedListNode(data);
// special case: no nodes in the list yet
if (this[tail] === null) {
throw new RangeError(`Index ${index} does not exist in the list.`);
}
/*
* Special case: if `index` is `0`, then no traversal is needed
* and we need to update `this[tail]` to point to `newNode`. First,
* set `node.next` to the current `this[tail]` so the previous
* head of the list is now the second node in the list. Then it's
* safe to update `this[tail]` to point to `newNode`.
*/
if (index === 0) {
newNode.next = this[tail].next;
this[tail].next = newNode;
} else {
/*
* The `current` variable is used to track the node that is being
* used inside of the loop below. It starts out pointing to
* `this[tail].next` and is overwritten inside of the loop.
*
* The `previous` variable tracks one step behind `current`, which
* is necessary because we need to adjust the node at `index`-1's
* `next` pointer to point to the new node.
*/
let current = this[tail].next,
previous = null;
/*
* The `i` variable is used to track how deep into the list we've
* gone. This important because it's the only way to know when
* we've hit the `index` to insert into.
*/
let i = 0;
/*
* Traverse and make sure to keep track of how many nodes have
* been visited and update the `previous` pointer in addition to
* `current`. When `i` is the same as `index`, it means we've
* found the location to insert the new data.
*/
while ((current.next !== this[tail].next) && (i < index)) {
previous = current;
current = current.next;
i++;
}
/*
* At this point, `current` is either the item to insert the new data
* before, or the last item in the list. The only way to tell is if
* `i` is still less than `index`, that means the index is out of range
* and an error should be thrown.
*/
if (i < index) {
throw new RangeError(`Index ${index} does not exist in the list.`);
}
/*
* If code continues to execute here, it means `current` is the node
* to insert new data before and `previous` is the node to insert
* new data after. So `previous.next` must point to `newNode` and
* `node.next` must point to `current`.
*/
previous.next = newNode;
newNode.next = current;
}
}
/**
* Inserts some data into the middle of the list. This method traverses
* the existing list and places the data in a new node after a specific index.
* @param {*} data The data to add to the list.
* @param {int} index The zero-based index after which to insert the data.
* @returns {void}
* @throws {RangeError} If the index doesn't exist in the list.
*/
insertAfter(data, index) {
/*
* Create a new list node object and store the data in it.
* This node will be inserted into the existing list.
*/
const newNode = new CircularLinkedListNode(data);
// special case: no nodes in the list yet
if (this[tail] === null) {
throw new RangeError(`Index ${index} does not exist in the list.`);
}
/*
* The `current` variable is used to track the node that is being
* used inside of the loop below. It starts out pointing to
* `this[tail].next` (the first node) and is overwritten inside of the loop.
*/
let current = this[tail].next;
// special case: insert after index 0 doesn't require a traversal
if (index > 0) {
/*
* The `i` variable is used to track how deep into the list we've
* gone. This important because it's the only way to know when
* we've hit the `index` to insert into.
*/
let i = 0;
/*
* Traverse and keep track of how many nodes have been visited and
* update the `current` pointer. When `i` is the same as `index`,
* it means we've found the location to insert the new data.
*/
do {
current = current.next;
i++;
} while ((current !== this[tail]) && (i < index));
/*
* At this point, `current` is either the node to insert the new data
* before, or the last node in the list. The only way to tell is if
* `i` is still less than `index`, that means the index is out of range
* and an error should be thrown.
*/
if (i < index) {
throw new RangeError(`Index ${index} does not exist in the list.`);
}
}
/*
* If code continues to execute here, it means `current` is the node
* to insert new data after. So `current.next` must point to
* `newNode` for the data to be in the correct spot, but before that,
* `newNode.next` must point to `current.next` to ensure the list
* remains intact.
*/
newNode.next = current.next;
current.next = newNode;
if (current === this[tail]) {
this[tail] = newNode;
}
}
/**
* Retrieves the data in the given position in the list.
* @param {int} index The zero-based index of the node whose data
* should be returned.
* @returns {*} The data in the "data" portion of the given node
* or undefined if the node doesn't exist.
*/
get(index) {
// ensure `index` is a positive value and the list isn't empty
if ((index > -1) && (this[tail] !== null)) {
/*
* The `current` variable is used to track the node that is being
* used inside of the loop below. It starts out pointing to
* `this[tail].next` (the first node) and is overwritten inside of the loop.
*/
let current = this[tail].next;
/*
* The `i` variable is used to track how deep into the list we've
* gone. This is important because it's the only way to know when
* we've hit the `index` to insert into.
*/
let i = 0;
/*
* Traverse the nodes in the list and keep track of how deep we are
* into the list. If we've reached the first node in the list
* (`this[head].next`) or we've gone past the end of the list
* (`i > index`), then exit the loop.
*/
do {
/*
* If the current position matches, the index being requested,
* then return the data in the current node and exit immediately.
*/
if (i === index) {
return current.data;
}
// otherwise, go on to the next node
current = current.next;
// and increment the index
i++;
} while ((current !== this[tail].next) && (i <= index));
/*
* If we've made it here, it means that that the index is past the
* end of the list. Execution now falls through the last `return`
* statement in this method, returning `undefined` to indicate no
* data was found.
*/
}
return undefined;
}
/**
* Retrieves the index of the data in the list.
* @param {*} data The data to search for.
* @returns {int} The index of the first instance of the data in the list
* or -1 if not found.
*/
indexOf(data) {
// special case: the list is empty so there's nothing to search
if (this[tail] === null) {
return -1;
}
/*
* The `current` variable is used to iterate over the list nodes.
* It starts out pointing to the head and is overwritten inside
* of the loop below.
*/
let current = this[tail].next;
/*
* The `index` variable is used to track how deep into the list we've
* gone. This is important because this is the value that is returned
* from this method.
*/
let index = 0;
/*
* This loop checks each node in the list to see if it matches `data`.
* If a match is found, it returns `index` immediately, exiting the
* loop because there's no reason to keep searching. The search
* continues until there are no more nodes to search because `current`
* is once again pointing the first node in the list.
*/
do {
/*
* If the data in the current node matches the data we are looking
* for, then return `index`. This exits the loop immediately.
*/
if (current.data === data) {
return index;
}
// otherwise, go on to the next node
current = current.next;
// and increment the index
index++;
} while (current !== this[tail].next);
/*
* If execution gets to this point, it means we reached the end of the
* list and didn't find `data`. Just return -1 as the "not found" value.
*/
return -1;
}
/**
* Removes the node from the given location in the list.
* @param {int} index The zero-based index of the node to remove.
* @returns {*} The data in the given position in the list.
* @throws {RangeError} If index is out of range.
*/
remove(index) {
// special case: no nodes in the list
if (this[tail] === null) {
throw new RangeError(`Index ${index} does not exist in the list.`);
}
// special case: `index` is an invalid value
if (index < 0) {
throw new RangeError(`Index ${index} does not exist in the list.`);
}
/*
* The `current` variable is used to iterate over the list nodes.
* It starts out pointing to the head and is overwritten inside
* of the loop below.
*/
let current = this[tail].next;
// special case: removing the first node
if (index === 0) {
// if there's only one node, null out `this[tail]`
if (current.next === this[tail]) {
this[tail] = null;
} else {
/*
* The tail doesn't change when there is more than one item
* in the list. Just skip over `current` by setting
* `this[tail].next` to `current.next`.
*/
this[tail].next = current.next;
}
// return the data at the previous head of the list
return current.data;
}
/*
* The `previous` variable keeps track of the node just before
* `current` in the loop below. This is necessary because removing
* an node means updating the previous node's `next` pointer.
*/
let previous = null;
/*
* The `i` variable is used to track how deep into the list we've
* gone. This is important because it's the only way to know when
* we've hit the `index` to remove.
*/
let i = 0;
/*
* Traverse the list, keeping track of the previous position so
* that we can remove the node once it's found. The loop is exited
* when either the start of the list is encountered or `i` is no
* longer less than `index` (meaning we have found the node to remove).
*/
do {
// save the value of current
previous = current;
// traverse to the next node
current = current.next;
// increment the count
i++;
} while ((current !== this[tail].next) && (i < index));
/*
* If `current` isn't `this[tail].next`, then that means we've found the node
* to remove.
*/
if (current !== this[tail].next) {
// skip over the node to remove
previous.next = current.next;
// return the value that was just removed from the list
return current.data;
}
/*
* If we've made it this far, it means `index` is a value that
* doesn't exist in the list, so throw an error.
*/
throw new RangeError(`Index ${index} does not exist in the list.`);
}
/**
* Removes all nodes from the list.
* @returns {void}
*/
clear() {
this[tail] = null;
}
/**
* Returns the number of nodes in the list.
* @returns {int} The number of nodes in the list.
*/
get size() {
// special case: the list is empty
if (this[tail] === null) {
return 0;
}
/*
* The `current` variable is used to iterate over the list nodes.
* It starts out pointing to the head and is overwritten inside
* of the loop below. `this[tail].next` points to the first node
* in the list because the last node always points to the first node.
*/
let current = this[tail].next;
/*
* The `count` variable is used to keep track of how many nodes have
* been visited inside the loop below. This is important because this
* is the value to return from this method.
*/
let count = 0;
/*
* Because the list is circular, we need to stop when `current` is
* equal to `this[tail].next`, otherwise this will be an infinite loop.
*/
do {
count++;
current = current.next;
} while (current !== this[tail].next);
/*
* The loop is exited and the value of `count`
* is the number of nodes that were counted in the loop.
*/
return count;
}
/**
* The default iterator for the class.
* @returns {Iterator} An iterator for the class.
*/
[Symbol.iterator]() {
return this.values();
}
/**
* Create an iterator that returns each node in the list.
* @returns {Iterator} An iterator on the list.
*/
*values(){
// special case: list is empty
if (this[tail] !== null) {
// special case: only one node
if (this[tail].next === this[tail]) {
yield this[tail].data;
} else {
/*
* The `current` variable is used to iterate over the list nodes.
* It starts out pointing to the head and is overwritten inside
* of the loop below. `this[tail].next] points to the first node
* in the list because the last node always points to the first node.
*/
let current = this[tail].next;
/*
* Because the list is circular, we need to stop when `current` is
* equal to the first node, otherwise this will be an infinite loop.
* And because `current` starts out equal to the first node, we need
* to use a post-test loop because we know the loop should execute
* at least once.
*/
do {
yield current.data;
current = current.next;
} while (current !== this[tail].next);
}
}
}
/**
* Create an iterator that returns each node in the list and repeats
* each node if it continues to be called. This is designed to be used
* to manually iterate through the list, outside of using syntax such
* as `for-of` (which will result in an infinite loop with this iterator).
* @returns {Iterator} A circular iterator on the list.
*/
*circularValues(){
// special case: list is empty
if (this[tail] !== null) {
/*
* The `current` variable is used to iterate over the list nodes.
* It starts out pointing to the head and is overwritten inside
* of the loop below. `this[tail].next] points to the first node
* in the list because the last node always points to the first node.
*/
let current = this[tail].next;
/*
* This is an infinite loop if you remove the `yield` call. The `yield`
* allows execution to stop and not pick up again until the iterator's
* `next()` method is called again.
*
* It's possible for this loop to exit if the list is emptied
* in between calls to the iterator's `next()` method. That will
* cause `current` to be `null` and the iterator will close.
*/
do {
yield current.data;
current = current.next;
} while (current !== null);
}
}
/**
* Converts the list into a string representation.
* @returns {String} A string representation of the list.
*/
toString(){
return [...this].toString();
}
}
exports.CircularLinkedList = CircularLinkedList;
================================================
FILE: src/data-structures/circular-linked-list/package.json
================================================
{
"name": "@humanwhocodes/circular-linked-list",
"version": "2.0.0",
"description": "A circular linked list implementation in JavaScript",
"main": "circular-linked-list.js",
"scripts": {
"test": "npx mocha ../../../tests/data-structures/circular-linked-list/circular-linked-list.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/humanwhocodes/computer-science-in-javascript.git"
},
"keywords": [
"circular linked list",
"linked list",
"data structure",
"iterable"
],
"author": "Nicholas C. Zakas",
"license": "MIT",
"bugs": {
"url": "https://github.com/humanwhocodes/computer-science-in-javascript/issues"
},
"homepage": "https://github.com/humanwhocodes/computer-science-in-javascript#readme",
"engines": {
"node": ">=8.0.0"
}
}
================================================
FILE: src/data-structures/doubly-linked-list/README.md
================================================
# JavaScript Doubly Linked List Class
by [Nicholas C. Zakas](https://humanwhocodes.com)
If you find this useful, please consider supporting my work with a [donation](https://humanwhocodes.com/donate).
## Overview
A JavaScript implementation of a doubly linked list. This class uses the conventions of built-in JavaScript collection objects, such as:
1. There is a `[Symbol.iterator]` method so each instance is iterable.
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.
1. Defining a `values()` generator method.
1. Defining a `find()` method for searching the list to return data.
1. Defining a `findIndex()` method for searching the list to return an index.
1. Returning `undefined` from `get()` when no such index exists.
Read the [blog post](https://humanwhocodes.com/blog/2019/02/computer-science-in-javascript-doubly-linked-lists/) about the design of this class.
## Usage
Use CommonJS to get access to the `DoublyLinkedList` constructor:
```js
const { DoublyLinkedList } = require("@humanwhocodes/doubly-linked-list");
```
Each instance of `DoublyLinkedList` has the following properties and methods:
```js
const list = new DoublyLinkedList();
// add an item to the end
list.add("foo");
// insert an item
list.insertBefore("bar", 0);
list.insertAfter("baz", 1);
// get the value at an index
let value = list.get(0);
// get the number of items
let count = list.size;
// get the index of a value
let index = list.indexOf("foo");
// search for a value
let result = list.find(value => value.length > 3);
let foundIndex = list.findIndex(value => value.length > 3);
// convert to an array using iterators
let array1 = [...list.values()];
let array2 = [...list];
// convert to an array in reverse order using an iterator
let array3 = [...list.reverse()];
// remove an item at the given index and return the data that was removed
let data = list.remove(0);
// remove all items
list.clear();
```
## Note on Code Style
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.
## Issues and Pull Requests
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.
## License
MIT
================================================
FILE: src/data-structures/doubly-linked-list/doubly-linked-list.js
================================================
/**
* @fileoverview Doubly linked list implementation in JavaScript
*/
/*
* These symbols are used to represent properties that should not be part of
* the public interface. You could also use ES2019 private fields, but those
* are not yet widely available as of the time of my writing.
*/
const head = Symbol("head");
const tail = Symbol("tail");
/**
* Represents a single node in a DoublyLinkedList.
* @class DoublyLinkedListNode
*/
class DoublyLinkedListNode {
/**
* Creates a new instance of DoublyLinkedListNode.
* @param {*} data The data to store in the node.
*/
constructor(data) {
/**
* The data that this node stores.
* @property data
* @type *
*/
this.data = data;
/**
* A pointer to the next node in the DoublyLinkedList.
* @property next
* @type ?DoublyLinkedListNode
*/
this.next = null;
/**
* A pointer to the previous node in the DoublyLinkedList.
* @property previous
* @type ?DoublyLinkedListNode
*/
this.previous = null;
}
}
/**
* A doubly linked list implementation in JavaScript.
* @class DoublyLinkedList
*/
class DoublyLinkedList {
/**
* Creates a new instance of DoublyLinkedList
*/
constructor() {
/**
* Pointer to first node in the list.
* @property head
* @type ?DoublyLinkedListNode
* @private
*/
this[head] = null;
/**
* Pointer to last node in the list.
* @property tail
* @type ?DoublyLinkedListNode
* @private
*/
this[tail] = null;
}
/**
* Appends some data to the end of the list.
* @param {*} data The data to add to the list.
* @returns {void}
*/
add(data) {
/*
* Create a new list node object and store the data in it.
* This node will be added to the end of the existing list.
*/
const newNode = new DoublyLinkedListNode(data);
// special case: no nodes in the list yet
if (this[head] === null) {
/*
* Because there are no nodes in the list, just set the
* `this[head]` pointer to the new node.
*/
this[head] = newNode;
} else {
/*
* Unlike in a singly linked list, we have a direct reference to
* the last node in the list. Set the `next` pointer of the
* current last node to `newNode` in order to append the new data
* to the end of the list. Then, set `newNode.previous` to the current
* tail to ensure backwards tracking work.
*/
this[tail].next = newNode;
newNode.previous = this[tail];
}
/*
* Last, reset `this[tail]` to `newNode` to ensure we are still
* tracking the last node correctly.
*/
this[tail] = newNode;
}
/**
* Inserts some data into the middle of the list. This method traverses
* the existing list and places the data in a new node at a specific index.
* @param {*} data The data to add to the list.
* @param {int} index The zero-based index at which to insert the data.
* @returns {void}
* @throws {RangeError} If the index doesn't exist in the list.
*/
insertBefore(data, index) {
/*
* Create a new list node object and store the data in it.
* This node will be inserted into the existing list.
*/
const newNode = new DoublyLinkedListNode(data);
// special case: no nodes in the list yet
if (this[head] === null) {
throw new RangeError(`Index ${index} does not exist in the list.`);
}
/*
* Special case: if `index` is `0`, then no traversal is needed
* and we need to update `this[head]` to point to `newNode`.
*/
if (index === 0) {
/*
* Ensure the new node's `next` property is pointed to the current
* head.
*/
newNode.next = this[head];
/*
* The current head's `previous` property needs to point to the new
* node to ensure the list is traversable backwards.
*/
this[head].previous = newNode;
/*
* Now it's safe to set `this[head]` to the new node, effectively
* making the new node the first node in the list.
*/
this[head] = newNode;
} else {
/*
* The `current` variable is used to track the node that is being
* used inside of the loop below. It starts out pointing to
* `this[head]` and is overwritten inside of the loop.
*/
let current = this[head];
/*
* The `i` variable is used to track how deep into the list we've
* gone. This important because it's the only way to know when
* we've hit the `index` to insert into.
*/
let i = 0;
/*
* Traverse the list nodes using `next` pointers, and make
* sure to keep track of how many nodes have been visited. When
* `i` is the same as `index`, it means we've found the location to
* insert the new data.
*/
while ((current.next !== null) && (i < index)) {
current = current.next;
i++;
}
/*
* At this point, `current` is either the node to insert the new data
* before, or the last node in the list. The only way to tell is if
* `i` is still less than `index`, that means the index is out of range
* and an error should be thrown.
*/
if (i < index) {
throw new RangeError(`Index ${index} does not exist in the list.`);
}
/*
* If code continues to execute here, it means `current` is the node
* to insert new data before.
*
* First, insert `newNode` after `current.previous` by updating
* `current.previous.next` and `newNode.previous`.
*/
current.previous.next = newNode;
newNode.previous = current.previous;
/*
* Next, insert `current` after `newNode` by updating `newNode.next` and
* `current.previous`.
*/
newNode.next = current;
current.previous = newNode;
}
}
/**
* Inserts some data into the middle of the list. This method traverses
* the existing list and places the data in a new node after a specific index.
* @param {*} data The data to add to the list.
* @param {int} index The zero-based index after which to insert the data.
* @returns {void}
* @throws {RangeError} If the index doesn't exist in the list.
*/
insertAfter(data, index) {
/*
* Create a new list node object and store the data in it.
* This node will be inserted into the existing list.
*/
const newNode = new DoublyLinkedListNode(data);
// special case: no nodes in the list yet
if (this[head] === null) {
throw new RangeError(`Index ${index} does not exist in the list.`);
}
/*
* The `current` variable is used to track the node that is being
* used inside of the loop below. It starts out pointing to
* `this[head]` and is overwritten inside of the loop.
*/
let current = this[head];
/*
* The `i` variable is used to track how deep into the list we've
* gone. This important because it's the only way to know when
* we've hit the `index` to insert into.
*/
let i = 0;
/*
* Traverse the list nodes similar to the `add()` method, but make
* sure to keep track of how many nodes have been visited and update
* the `previous` pointer in addition to `current`. When
* `i` is the same as `index`, it means we've found the location to
* insert the new data.
*/
while ((current !== null) && (i < index)) {
current = current.next;
i++;
}
/*
* At this point, `current` is either the node to insert the new data
* before, or the last node in the list. The only way to tell is if
* `i` is still less than `index`, that means the index is out of range
* and an error should be thrown.
*/
if (i < index) {
throw new RangeError(`Index ${index} does not exist in the list.`);
}
/*
* If code continues to execute here, it means `current` is the node
* to insert new data after.
*/
// special case: `current` is the tail, so reset `this[tail]`
if (this[tail] === current) {
this[tail] = newNode;
} else {
/*
* Otherwise, insert `newNode` before `current.next` by updating
* `current.next.previous` and `newNode.node`.
*/
current.next.previous = newNode;
newNode.next = current.next;
}
/*
* Next, insert `newNode` after `current` by updating `newNode.previous` and
* `current.next`.
*/
newNode.previous = current;
current.next = newNode;
}
/**
* Retrieves the data in the given position in the list.
* @param {int} index The zero-based index of the node whose data
* should be returned.
* @returns {*} The data in the "data" portion of the given node
* or undefined if the node doesn't exist.
*/
get(index) {
// ensure `index` is a positive value
if (index > -1) {
/*
* The `current` variable is used to track the node that is being
* used inside of the loop below. It starts out pointing to
* `this[head]` and is overwritten inside of the loop.
*/
let current = this[head];
/*
* The `i` variable is used to track how deep into the list we've
* gone. This is important because it's the only way to know when
* we've hit the `index` to insert into.
*/
let i = 0;
/*
* Traverse the list nodes, but make sure to keep track of how many
* nodes have been visited and update the `previous` pointer in
* addition to `current`. When `i` is the same as `index`, it means
* we've found the location to insert the new data.
*/
while ((current !== null) && (i < index)) {
current = current.next;
i++;
}
/*
* At this point, `current` might be null if we've gone past the
* end of the list. In that case, we return `undefined` to indicate
* that the node at `index` was not found. If `current` is not
* `null`, then it's safe to return `current.data`.
*/
return current !== null ? current.data : undefined;
} else {
return undefined;
}
}
/**
* Retrieves the index of the data in the list.
* @param {*} data The data to search for.
* @returns {int} The index of the first instance of the data in the list
* or -1 if not found.
*/
indexOf(data) {
/*
* The `current` variable is used to iterate over the list nodes.
* It starts out pointing to the head and is overwritten inside
* of the loop below.
*/
let current = this[head];
/*
* The `index` variable is used to track how deep into the list we've
* gone. This is important because this is the value that is returned
* from this method.
*/
let index = 0;
/*
* This loop checks each node in the list to see if it matches `data`.
* If a match is found, it returns `index` immediately, exiting the
* loop because there's no reason to keep searching. The search
* continues until there are no more nodes to search (when `current` is `null`).
*/
while (current !== null) {
if (current.data === data) {
return index;
}
// traverse to the next node in the list
current = current.next;
// keep track of where we are
index++;
}
/*
* If execution gets to this point, it means we reached the end of the
* list and didn't find `data`. Just return -1 as the "not found" value.
*/
return -1;
}
/**
* Returns the first item that matches a given function.
* @param {Function} matcher A function returning true when an item matches
* and false when an item doesn't match.
* @returns {*} The first item that returns true from the matcher, undefined
* if no items match.
*/
find(matcher) {
/*
* The `current` variable is used to iterate over the list nodes.
* It starts out pointing to the head and is overwritten inside
* of the loop below.
*/
let current = this[head];
/*
* This loop checks each node in the list to see if it matches.
* If a match is found, it returns the data immediately, exiting the
* loop because there's no reason to keep searching. The search
* continues until there are no more nodes to search (when `current` is `null`).
*/
while (current !== null) {
if (matcher(current.data)) {
return current.data;
}
// traverse to the next node in the list
current = current.next;
}
/*
* If execution gets to this point, it means we reached the end of the
* list and didn't find `data`. Just return `undefined` as the
* "not found" value.
*/
return undefined;
}
/**
* Returns the index of the first item that matches a given function.
* @param {Function} matcher A function returning true when an item matches
* and false when an item doesn't match.
* @returns {int} The index of the first item that matches a given function
* or -1 if there are no matching items.
*/
findIndex(matcher) {
/*
* The `current` variable is used to iterate over the list nodes.
* It starts out pointing to the head and is overwritten inside
* of the loop below.
*/
let current = this[head];
/*
* The `index` variable is used to track how deep into the list we've
* gone. This is important because this is the value that is returned
* from this method.
*/
let index = 0;
/*
* This loop checks each node in the list to see if it matches.
* If a match is found, it returns the index immediately, exiting the
* loop because there's no reason to keep searching. The search
* continues until there are no more nodes to search (when `current` is `null`).
*/
while (current !== null) {
if (matcher(current.data)) {
return index;
}
// traverse to the next node in the list
current = current.next;
// keep track of where we are
index++;
}
/*
* If execution gets to this point, it means we reached the end of the
* list and didn't find `data`. Just return -1 as the
* "not found" value.
*/
return -1;
}
/**
* Removes the node from the given location in the list.
* @param {int} index The zero-based index of the node to remove.
* @returns {*} The data in the given position in the list.
* @throws {RangeError} If index is out of range.
*/
remove(index) {
// special cases: no nodes in the list or `index` is negative
if ((this[head] === null) || (index < 0)) {
throw new RangeError(`Index ${index} does not exist in the list.`);
}
// special case: removing the first node
if (index === 0) {
// store the data from the current head
const data = this[head].data;
// just replace the head with the next node in the list
this[head] = this[head].next;
// special case: there was only one node, so also reset `this[tail]`
if (this[head] === null) {
this[tail] = null;
} else {
this[head].previous = null;
}
// return the data at the previous head of the list
return data;
}
/*
* The `current` variable is used to iterate over the list nodes.
* It starts out pointing to the head and is overwritten inside
* of the loop below.
*/
let current = this[head];
/*
* The `i` variable is used to track how deep into the list we've
* gone. This is important because it's the only way to know when
* we've hit the `index` to remove.
*/
let i = 0;
/*
* Traverse the list nodes similar to the `get()` method, but make
* sure to keep track of how many nodes have been visited. When
* `i` is the same as `index`, it means we've found the location to
* remove.
*/
while ((current !== null) && (i < index)) {
// traverse to the next node
current = current.next;
// increment the count
i++;
}
/*
* If `current` isn't `null`, then that means we've found the node
* to remove.
*/
if (current !== null) {
// skip over the node to remove
current.previous.next = current.next;
/*
* If we are at the end of the list, then update `this[tail]`.
*
* If we are not at the end of the list, then update the backwards
* pointer for `current.next` to preserve reverse traversal.
*/
if (this[tail] === current) {
this[tail] = current.previous;
} else {
current.next.previous = current.previous;
}
// return the value that was just removed from the list
return current.data;
}
/*
* If we've made it this far, it means `index` is a value that
* doesn't exist in the list, so throw an error.
*/
throw new RangeError(`Index ${index} does not exist in the list.`);
}
/**
* Removes all nodes from the list.
* @returns {void}
*/
clear() {
// just reset both the head and tail pointer to null
this[head] = null;
this[tail] = null;
}
/**
* Returns the number of nodes in the list.
* @returns {int} The number of nodes in the list.
*/
get size() {
// special case: the list is empty
if (this[head] === null) {
return 0;
}
/*
* The `current` variable is used to iterate over the list nodes.
* It starts out pointing to the head and is overwritten inside
* of the loop below.
*/
let current = this[head];
/*
* The `count` variable is used to keep track of how many nodes have
* been visited inside the loop below. This is important because this
* is the value to return from this method.
*/
let count = 0;
/*
* As long as `current` is not `null`, that means we're not yet at the
* end of the list, so adding 1 to `count` and traverse to the next node.
*/
while (current !== null) {
count++;
current = current.next;
}
/*
* When `current` is `null`, the loop is exited at the value of `count`
* is the number of nodes that were counted in the loop.
*/
return count;
}
/**
* The default iterator for the class.
* @returns {Iterator} An iterator for the class.
*/
[Symbol.iterator]() {
return this.values();
}
/**
* Create an iterator that returns each node in the list.
* @returns {Iterator} An iterator on the list.
*/
*values(){
/*
* The `current` variable is used to iterate over the list nodes.
* It starts out pointing to the head and is overwritten inside
* of the loop below.
*/
let current = this[head];
/*
* As long as `current` is not `null`, there is a piece of data
* to yield.
*/
while (current !== null) {
yield current.data;
current = current.next;
}
}
/**
* Create an iterator that returns each node in the list in reverse order.
* @returns {Iterator} An iterator on the list.
*/
*reverse(){
/*
* The `current` variable is used to iterate over the list nodes.
* It starts out pointing to the tail and is overwritten inside
* of the loop below.
*/
let current = this[tail];
/*
* As long as `current` is not `null`, there is a piece of data
* to yield.
*/
while (current !== null) {
yield current.data;
current = current.previous;
}
}
/**
* Converts the list into a string representation.
* @returns {String} A string representation of the list.
*/
toString(){
return [...this].toString();
}
}
exports.DoublyLinkedList = DoublyLinkedList;
================================================
FILE: src/data-structures/doubly-linked-list/package.json
================================================
{
"name": "@humanwhocodes/doubly-linked-list",
"version": "2.2.0",
"description": "A doubly linked list implementation in JavaScript",
"main": "doubly-linked-list.js",
"scripts": {
"test": "npx mocha ../../../tests/data-structures/doubly-linked-list/doubly-linked-list.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/humanwhocodes/computer-science-in-javascript.git"
},
"keywords": [
"linked list",
"doubly linked list",
"data structure",
"iterable"
],
"author": "Nicholas C. Zakas",
"license": "MIT",
"bugs": {
"url": "https://github.com/humanwhocodes/computer-science-in-javascript/issues"
},
"homepage": "https://github.com/humanwhocodes/computer-science-in-javascript#readme",
"engines": {
"node": ">=8.0.0"
}
}
================================================
FILE: src/data-structures/hash-map/README.md
================================================
# JavaScript Hash Map Class
by [Nicholas C. Zakas](https://humanwhocodes.com)
If you find this useful, please consider supporting my work with a [donation](https://humanwhocodes.com/donate).
## Overview
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:
1. There is a `[Symbol.iterator]` method so each instance is iterable.
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.
1. Defining `entries()`, `keys()`, and `values()` generator methods.
## Usage
Use CommonJS to get access to the `HashMap` constructor:
```js
const { HashMap } = require("@humanwhocodes/hash-map");
```
Each instance of `HashMap` has the following properties and methods:
```js
const map = new HashMap();
// add an item
map.set("foo", 1);
// get the value of an item
let value = map.get("foo");
// get the number of items
let count = map.size;
// does the key exist in the map?
let found = map.has("foo");
// remove a key
map.delete("foo");
// get all key-value pairs
let entries1 = [...map.entries()];
let entries2 = [...map];
// get all keys
let keys = [...map.keys()];
// get all values
let values = [...map.values()];
// remove all items
map.clear();
```
## Note on Code Style
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.
## Note on Usage
This module is intended for educational purposes. For production purposes, you should use the native JavaScript `Map` class.
## Issues and Pull Requests
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.
## License
MIT
================================================
FILE: src/data-structures/hash-map/hash-map.js
================================================
/**
* @fileoverview Hash Map implementation in JavaScript
*/
"use strict";
//-----------------------------------------------------------------------------
// Requirements
//-----------------------------------------------------------------------------
const { DoublyLinkedList } = require("@humanwhocodes/doubly-linked-list");
//-----------------------------------------------------------------------------
// Private
//-----------------------------------------------------------------------------
const ARRAY_SIZE = 16;
/**
* Adds up all of the code points in a string to create a numeric hash.
* @param {string} key The text key to hash.
* @returns {int} A numeric hash of text.
* @private
*/
function hashCodePoints(key) {
let result = 0;
// Iterate over each character (not each byte) in the string
for (const character of key) {
/*
* The `codePointAt()` method has support for multi-byte characters,
* so that's better than `charCodeAt()` for this purpose.
*
* `character` is a single-character string, so we only want the code
* point at position 0.
*/
result += character.codePointAt(0);
}
return result;
}
/**
* Determines the correct array index for the given hash code.
* @param {int} hashCode The hash code to compute an index for.
* @returns {int} An index between 0 and `ARRAY_SIZE - 1`.
*/
function getArrayIndexFromHashCode(hashCode) {
return hashCode % ARRAY_SIZE;
}
/**
* Creates an array to use as the basis for a hash map.
* @returns {void}
* @private
*/
function createArray() {
/*
* FYI: It's not necessary to use an instance of `Array` for the
* purpose of creating a hash map. You could just as easily use a
* regular object. This implementation uses an array strictly because
* it's the most direct equivalent to how other languages implement
* hash maps.
*/
// Object.seal() ensures we don't accidentally add more items
return Object.seal(
/*
* Creates a new array with a predefined length of 16. This doesn't
* actually create each of the properties in the array, so the
* `fill()` method is used to do so. This is necessary because a
* sealed array cannot have new properties defined, and since the
* array starts out without any numeric properties defined, it
* would prevent us from even assigning a value to array[0].
*/
new Array(ARRAY_SIZE).fill(undefined)
);
}
/**
* Checks that a key is a non-empty string.
* @param {string} key The key to validate.
* @returns {void}
* @throws {TypeError} When the key is either not a string or is an empty
* string.
*/
function assertNonEmptyString(key) {
if (typeof key !== "string" || key.length === 0) {
throw new TypeError("Key must be a non-empty string.");
}
}
//-----------------------------------------------------------------------------
// HashMap Class
//-----------------------------------------------------------------------------
/*
* These symbols are used to represent properties that should not be part of
* the public interface. You could also use ES2019 private fields, but those
* are not yet widely available as of the time of my writing.
*/
const array = Symbol("array");
/**
* A binary heap implementation in JavaScript.
* @class HashMap
*/
class HashMap {
/**
* Creates a new instance of HashMap
*/
constructor() {
/**
* Array used to manage the hash map. The array is sealed to ensure
* no new items accidentally get added.
* @property array
* @type Array
* @private
*/
this[array] = createArray();
}
/**
* Adds a key-value pair into the hash map.
* @param {string} key The key to add.
* @param {*} value The value to add.
* @returns {void}
* @throws {TypeError} If the key isn't a non-empty string.
*/
set(key, value) {
// first, ensure the key is valid
assertNonEmptyString(key);
/*
* Next, calculate the hash code from the string key. The hash code
* is then used to determine the index into the array where the data
* should be stored.
*/
const hashCode = hashCodePoints(key);
const index = getArrayIndexFromHashCode(hashCode);
/*
* Special case: If the calculated index in the array hasn't yet
* been initialized, then create a new linked list in that
* location before continuing.
*
* Note: It's not necessary to use a linked list. Because JavaScript
* has an excellent `Array` class, you could also just use an array
* here. This implementation sticks with a linked list because that's
* the most common implementation in other languages.
*/
if (this[array][index] === undefined) {
this[array][index] = new DoublyLinkedList();
}
/*
* Check to see if the exact key is already present
* in the hash map. If so, then just update the value.
*/
const result = this[array][index].find((value) => {
return value.key === key;
});
// If the key doesn't already exist, then add it
if (result === undefined) {
/*
* Add everything we know about this key-value pair, including the key,
* the value, and the hash code. We will need all of these later.
*/
this[array][index].add({
key,
value
});
} else {
// The key already exists in the hash map, so just update the value.
result.value = value;
}
}
/**
* Retrieves a key-value pair from the hash map.
* @param {string} key The key whose value should be retrieved.
* @returns {*} The value associated with the key or `undefined` if the
* key doesn't exist in the hash map.
* @throws {TypeError} If the key isn't a non-empty string.
*/
get(key) {
// first, ensure the key is valid
assertNonEmptyString(key);
/*
* Next, calculate the hash code from the string key. The hash code
* is then used to determine the index into the array where the data
* should be stored.
*/
const hashCode = hashCodePoints(key);
const index = getArrayIndexFromHashCode(hashCode);
/*
* Special case: If the calculated index in the array hasn't yet
* been initialized, then the key doesn't exist. Return
* `undefined` to indicate the key doesn't exist.
*/
if (this[array][index] === undefined) {
return undefined;
}
/*
* If we've made it to here, then there is a linked list in the
* array at this location, so try to find the key that matches.
*/
const result = this[array][index].find((value) => {
return value.key === key;
});
/*
* If an item with the given hash code and key was not found, then
* there is no value to return. Just return undefined.
*/
if (result === undefined) {
return undefined;
}
/*
* If we've made it to here, it means that the hash code and key were
* found, so return the value.
*/
return result.value;
}
/**
* Determines if a given key is present in the hash map.
* @param {string} key The key to check.
* @returns {boolean} True if the key exists in the hash map, false if not.
* @throws {TypeError} If the key isn't a non-empty string.
*/
has(key) {
// first, ensure the key is valid
assertNonEmptyString(key);
/*
* Next, calculate the hash code from the string key. The hash code
* is then used to determine the index into the array where the data
* should be stored.
*/
const hashCode = hashCodePoints(key);
const index = getArrayIndexFromHashCode(hashCode);
/*
* Special case: If the calculated index in the array hasn't yet
* been initialized, then the key doesn't exist. Return
* `false` to indicate the key doesn't exist.
*/
if (this[array][index] === undefined) {
return false;
}
/*
* If we've made it to here, then there is a linked list in the
* array at this location, so try to find the key that matches.
*/
const resultIndex = this[array][index].findIndex((value) => {
return value.key === key;
});
/*
* Any value greater than -1 indicates that the key was found in the
* hash map and therefore this method should return `true`.
*/
return resultIndex > -1;
}
/**
* Deletes the given key from the hash map.
* @param {string} key The key to delete.
* @returns {boolean} True if the key exists and was deleted, false if the
* key didn't exist.
* @throws {TypeError} If the key isn't a non-empty string.
*/
delete(key) {
// first, ensure the key is valid
assertNonEmptyString(key);
/*
* Next, calculate the hash code from the string key. The hash code
* is then used to determine the index into the array where the data
* should be stored.
*/
const hashCode = hashCodePoints(key);
const index = getArrayIndexFromHashCode(hashCode);
/*
* Special case: If the calculated index in the array hasn't yet
* been initialized, then the key doesn't exist. Return
* `false` to indicate the key doesn't exist.
*/
if (this[array][index] === undefined) {
return false;
}
/*
* If we've made it to here, then there is a linked list in the
* array at this location, so try to find the key that matches.
*/
const resultIndex = this[array][index].findIndex((value) => {
return value.key === key;
});
/*
* Special case: If `resultIndex` is -1, meaning the value wasn't
* found, just return -1.
*/
if (resultIndex === -1) {
return -1;
}
/*
* If we've made it to here, then `resultIndex` is greater than -1
* and we need to remove the given key from the hash map.
*/
this[array][index].remove(resultIndex);
/*
* Because we actually performed the removal, we need to return `true`
* to give feedback that this happened.
*/
return true;
}
/**
* Returns the number of key-value pairs in the hash map.
* @returns {int} The number of key-value pairs in the hash map.
*/
get size() {
/*
* Because the `entries()` generator already implements the correct
* traversal algorithm, use that here instead of duplicating the
* algorithm.
*
* The first step is to create a new iterator by calling `entries()`.
*/
const iterator = this.entries();
/*
* The `count` variable stores the number of entries we see and is
* incremented inside of a loop later on.
*/
let count = 0;
/*
* Get the first entry from the iterator. Each entry has two properties:
* `value`, containing the value from the hash map, and `done`, a
* a Boolean value that is `true` when there are no more entries.
*/
let entry = iterator.next();
// Continue the loop as long as there are more entries
while (!entry.done) {
// Increment the count to reflect the last entry
count++;
// Get the entry from the iterator and repeat the loop
entry = iterator.next();
}
/*
* Once the loop exits, the `count` variable contains the number of
* entries in the iterator so return that value.
*/
return count;
}
/**
* Removes all values from the heap.
* @returns {void}
*/
clear() {
/*
* The simplest way to clear all values is just to overwrite the array
* we started with.
*/
this[array] = createArray();
}
/**
* The default iterator for the class.
* @returns {Iterator} An iterator for the class.
*/
[Symbol.iterator]() {
return this.entries();
}
/**
* Create an iterator that returns each entry in the hash map.
* @returns {Iterator} An iterator on the hash map.
*/
*entries() {
// For each item in the array
for (const list of this[array]) {
// If there is no linked list then no need to go any further
if (list !== undefined) {
// If there is a linked list then yield each key-value pair
for (const item of list) {
yield [item.key, item.value];
}
}
}
}
/**
* Create an iterator that returns each key in the hash map.
* @returns {Iterator} An iterator on the hash map keys.
*/
*keys() {
/*
* Because this iterator just returns a subset of the information
* returned by the `entries()` iterator, we just use `entries()`
* and take the information we care about.
*/
for (const [key] of this.entries()) {
yield key;
}
}
/**
* Create an iterator that returns each value in the hash map.
* @returns {Iterator} An iterator on the hash map value.
*/
*values() {
/*
* Because this iterator just returns a subset of the information
* returned by the `entries()` iterator, we just use `entries()`
* and take the information we care about.
*/
for (const [,value] of this.entries()) {
yield value;
}
}
/**
* Converts the heap into a string representation.
* @returns {String} A string representation of the heap.
*/
toString(){
// TODO
return [...this[array]].toString();
}
}
exports.HashMap = HashMap;
================================================
FILE: src/data-structures/hash-map/package.json
================================================
{
"name": "@humanwhocodes/hash-map",
"version": "2.0.0",
"description": "A hash map implementation in JavaScript",
"main": "hash-map.js",
"scripts": {
"test": "npx mocha ../../../tests/data-structures/hash-map/hash-map.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/humanwhocodes/computer-science-in-javascript.git"
},
"keywords": [
"hash",
"hash table",
"hash map",
"data structure",
"iterable"
],
"author": "Nicholas C. Zakas",
"license": "MIT",
"bugs": {
"url": "https://github.com/humanwhocodes/computer-science-in-javascript/issues"
},
"homepage": "https://github.com/humanwhocodes/computer-science-in-javascript#readme",
"engines": {
"node": ">=8.0.0"
},
"dependencies": {
"@humanwhocodes/doubly-linked-list": "^2.2.0"
}
}
================================================
FILE: src/data-structures/linked-list/README.md
================================================
# JavaScript Linked List Class
by [Nicholas C. Zakas](https://humanwhocodes.com)
If you find this useful, please consider supporting my work with a [donation](https://humanwhocodes.com/donate).
## Overview
A JavaScript implementation of a linked list. This class uses the conventions of built-in JavaScript collection objects, such as:
1. There is a `[Symbol.iterator]` method so each instance is iterable.
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.
1. Defining a `values()` generator method.
1. Returning `undefined` from `get()` when no such index exists.
Read the [blog post](https://humanwhocodes.com/blog/2019/01/computer-science-in-javascript-linked-list/) about the design of this class.
## Usage
Use CommonJS to get access to the `LinkedList` constructor:
```js
const { LinkedList } = require("@humanwhocodes/linked-list");
```
Each instance of `LinkedList` has the following properties and methods:
```js
const list = new LinkedList();
// add an item to the end
list.add("foo");
// insert an item
list.insertBefore("bar", 0);
list.insertAfter("baz", 1);
// get the value at an index
let value = list.get(0);
// get the number of items
let count = list.size;
// get the index of a value
let index = list.indexOf("foo");
// convert to an array using iterators
let array1 = [...list.values()];
let array2 = [...list];
// remove an item at the given index and return the data that was removed
let data = list.remove(0);
// remove all items
list.clear();
```
## Note on Code Style
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.
## Issues and Pull Requests
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.
## License
MIT
================================================
FILE: src/data-structures/linked-list/linked-list.js
================================================
/**
* @fileoverview Linked List implementation in JavaScript
*/
/*
* These symbols are used to represent properties that should not be part of
* the public interface. You could also use ES2019 private fields, but those
* are not yet widely available as of the time of my writing.
*/
const head = Symbol("head");
/**
* Represents a single node in a LinkedList.
* @class LinkedListNode
*/
class LinkedListNode {
/**
* Creates a new instance of LinkedListNode.
* @param {*} data The data to store in the node.
*/
constructor(data) {
/**
* The data that this node stores.
* @property data
* @type *
*/
this.data = data;
/**
* A pointer to the next node in the LinkedList.
* @property next
* @type ?LinkedListNode
*/
this.next = null;
}
}
/**
* A linked list implementation in JavaScript.
* @class LinkedList
*/
class LinkedList {
/**
* Creates a new instance of LinkedList
*/
constructor() {
/**
* Pointer to first node in the list.
* @property head
* @type ?LinkedListNode
* @private
*/
this[head] = null;
}
/**
* Appends some data to the end of the list. This method traverses
* the existing list and places the data at the end in a new node.
* @param {*} data The data to add to the list.
* @returns {void}
*/
add(data) {
/*
* Create a new list node object and store the data in it.
* This node will be added to the end of the existing list.
*/
const newNode = new LinkedListNode(data);
//special case: no nodes in the list yet
if (this[head] === null) {
/*
* Because there are no nodes in the list, just set the
* `this[head]` pointer to the new node.
*/
this[head] = newNode;
} else {
/*
* The `current` variable is used to track the node that is being
* used inside of the loop below. It starts out pointing to
* `this[head]` and is overwritten inside of the loop.
*/
let current = this[head];
/*
* Follow each `next` pointer until the end. The last node in the
* list has `next` equal to `null`, so when we reach that node,
* we know we're at the end.
*/
while (current.next !== null) {
current = current.next;
}
/*
* At this point, `current` is equal to the last node in the list.
* Setting its `current.next` equal to node means adding a new node
* at the end of the list.
*/
current.next = newNode;
}
}
/**
* Inserts some data into the middle of the list. This method traverses
* the existing list and places the data in a new node at a specific index.
* @param {*} data The data to add to the list.
* @param {int} index The zero-based index at which to insert the data.
* @returns {void}
* @throws {RangeError} If the index doesn't exist in the list.
*/
insertBefore(data, index) {
/*
* Create a new list node object and store the data in it.
* This node will be inserted into the existing list.
*/
const newNode = new LinkedListNode(data);
// special case: no nodes in the list yet
if (this[head] === null) {
throw new RangeError(`Index ${index} does not exist in the list.`);
}
/*
* Special case: if `index` is `0`, then no traversal is needed
* and we need to update `this[head]` to point to `node`. First,
* set `node.next` to the current `this[head]` so the previous
* head of the list is now the second node in the list. Then it's
* safe to update `this[head]` to point to `node`.
*/
if (index === 0) {
newNode.next = this[head];
this[head] = newNode;
} else {
/*
* The `current` variable is used to track the node that is being
* used inside of the loop below. It starts out pointing to
* `this[head]` and is overwritten inside of the loop.
*
* The `previous` variable tracks one step behind `current`, which
* is necessary because we need to adjust the node at `index`-1's
* `next` pointer to point to the new node.
*/
let current = this[head],
previous = null;
/*
* The `i` variable is used to track how deep into the list we've
* gone. This important because it's the only way to know when
* we've hit the `index` to insert into.
*/
let i = 0;
/*
* Traverse the list nodes similar to the `add()` method, but make
* sure to keep track of how many nodes have been visited and update
* the `previous` pointer in addition to `current`. When
* `i` is the same as `index`, it means we've found the location to
* insert the new data.
*/
while ((current.next !== null) && (i < index)) {
previous = current;
current = current.next;
i++;
}
/*
* At this point, `current` is either the node to insert the new data
* before, or the last node in the list. The only way to tell is if
* `i` is still less than `index`, that means the index is out of range
* and an error should be thrown.
*/
if (i < index) {
throw new RangeError(`Index ${index} does not exist in the list.`);
}
/*
* If code continues to execute here, it means `current` is the node
* to insert new data before and `previous` is the node to insert
* new data after. So `previous.next` must point to `node` and
* `node.next` must point to `current`.
*/
previous.next = newNode;
newNode.next = current;
}
}
/**
* Inserts some data into the middle of the list. This method traverses
* the existing list and places the data in a new node after a specific index.
* @param {*} data The data to add to the list.
* @param {int} index The zero-based index after which to insert the data.
* @returns {void}
* @throws {RangeError} If the index doesn't exist in the list.
*/
insertAfter(data, index) {
/*
* Create a new list node object and store the data in it.
* This node will be inserted into the existing list.
*/
const newNode = new LinkedListNode(data);
// special case: no nodes in the list yet
if (this[head] === null) {
throw new RangeError(`Index ${index} does not exist in the list.`);
}
/*
* The `current` variable is used to track the node that is being
* used inside of the loop below. It starts out pointing to
* `this[head]` and is overwritten inside of the loop.
*/
let current = this[head];
/*
* The `i` variable is used to track how deep into the list we've
* gone. This important because it's the only way to know when
* we've hit the `index` to insert into.
*/
let i = 0;
/*
* Traverse the list nodes similar to the `add()` method, but make
* sure to keep track of how many nodes have been visited and update
* the `previous` pointer in addition to `current`. When
* `i` is the same as `index`, it means we've found the location to
* insert the new data.
*/
while ((current !== null) && (i < index)) {
current = current.next;
i++;
}
/*
* At this point, `current` is either the node to insert the new data
* before, or the last node in the list. The only way to tell is if
* `i` is still less than `index`, that means the index is out of range
* and an error should be thrown.
*/
if (i < index) {
throw new RangeError(`Index ${index} does not exist in the list.`);
}
/*
* If code continues to execute here, it means `current` is the node
* to insert new data after. So `current.next` must point to
* `node` for the data to be in the correct spot, but before that,
* `node.next` must point to `current.next` to ensure the list
* remains intact.
*/
newNode.next = current.next;
current.next = newNode;
}
/**
* Retrieves the data in the given position in the list.
* @param {int} index The zero-based index of the node whose data
* should be returned.
* @returns {*} The data in the "data" portion of the given node
* or undefined if the node doesn't exist.
*/
get(index) {
// ensure `index` is a positive value
if (index > -1) {
/*
* The `current` variable is used to track the node that is being
* used inside of the loop below. It starts out pointing to
* `this[head]` and is overwritten inside of the loop.
*/
let current = this[head];
/*
* The `i` variable is used to track how deep into the list we've
* gone. This is important because it's the only way to know when
* we've hit the `index` to insert into.
*/
let i = 0;
/*
* Traverse the list nodes similar to the `add()` method, but make
* sure to keep track of how many nodes have been visited and update
* the `previous` pointer in addition to `current`. When
* `i` is the same as `index`, it means we've found the location to
* insert the new data.
*/
while ((current !== null) && (i < index)) {
current = current.next;
i++;
}
/*
* At this point, `current` might be null if we've gone past the
* end of the list. In that case, we return `undefined` to indicate
* that the node at `index` was not found. If `current` is not
* `null`, then it's safe to return `current.data`.
*/
return current !== null ? current.data : undefined;
} else {
return undefined;
}
}
/**
* Retrieves the index of the data in the list.
* @param {*} data The data to search for.
* @returns {int} The index of the first instance of the data in the list
* or -1 if not found.
*/
indexOf(data) {
/*
* The `current` variable is used to iterate over the list nodes.
* It starts out pointing to the head and is overwritten inside
* of the loop below.
*/
let current = this[head];
/*
* The `index` variable is used to track how deep into the list we've
* gone. This is important because this is the value that is returned
* from this method.
*/
let index = 0;
/*
* This loop checks each node in the list to see if it matches `data`.
* If a match is found, it returns `index` immediately, exiting the
* loop because there's no reason to keep searching. The search
* continues until there are no more nodes to search (when `current` is `null`).
*/
while (current !== null) {
if (current.data === data) {
return index;
}
// traverse to the next node in the list
current = current.next;
// keep track of where we are
index++;
}
/*
* If execution gets to this point, it means we reached the end of the
* list and didn't find `data`. Just return -1 as the "not found" value.
*/
return -1;
}
/**
* Removes the node from the given location in the list.
* @param {int} index The zero-based index of the node to remove.
* @returns {*} The data in the given position in the list.
* @throws {RangeError} If index is out of range.
*/
remove(index) {
// special cases: empty list or invalid `index`
if ((this[head] === null) || (index < 0)) {
throw new RangeError(`Index ${index} does not exist in the list.`);
}
// special case: removing the first node
if (index === 0) {
// temporarily store the data from the node
const data = this[head].data;
// just replace the head with the next node in the list
this[head] = this[head].next;
// return the data at the previous head of the list
return data;
}
/*
* The `current` variable is used to iterate over the list nodes.
* It starts out pointing to the head and is overwritten inside
* of the loop below.
*/
let current = this[head];
/*
* The `previous` variable keeps track of the node just before
* `current` in the loop below. This is necessary because removing
* an node means updating the previous node's `next` pointer.
*/
let previous = null;
/*
* The `i` variable is used to track how deep into the list we've
* gone. This is important because it's the only way to know when
* we've hit the `index` to remove.
*/
let i = 0;
/*
* Traverse the list nodes similar to the `add()` method, but make
* sure to keep track of how many nodes have been visited and update
* the `previous` pointer in addition to `current`. When
* `i` is the same as `index`, it means we've found the location to
* remove.
*/
while ((current !== null) && (i < index)) {
// save the value of current
previous = current;
// traverse to the next node
current = current.next;
// increment the count
i++;
}
/*
* If `current` isn't `null`, then that means we've found the node
* to remove.
*/
if (current !== null) {
// skip over the node to remove
previous.next = current.next;
// return the value that was just removed from the list
return current.data;
}
/*
* If we've made it this far, it means `index` is a value that
* doesn't exist in the list, so throw an error.
*/
throw new RangeError(`Index ${index} does not exist in the list.`);
}
/**
* Removes all nodes from the list.
* @returns {void}
*/
clear() {
this[head] = null;
}
/**
* Returns the number of nodes in the list.
* @returns {int} The number of nodes in the list.
*/
get size() {
// special case: the list is empty
if (this[head] === null) {
return 0;
}
/*
* The `current` variable is used to iterate over the list nodes.
* It starts out pointing to the head and is overwritten inside
* of the loop below.
*/
let current = this[head];
/*
* The `count` variable is used to keep track of how many nodes have
* been visited inside the loop below. This is important because this
* is the value to return from this method.
*/
let count = 0;
/*
* As long as `current` is not `null`, that means we're not yet at the
* end of the list, so adding 1 to `count` and traverse to the next node.
*/
while (current !== null) {
count++;
current = current.next;
}
/*
* When `current` is `null`, the loop is exited at the value of `count`
* is the number of nodes that were counted in the loop.
*/
return count;
}
/**
* The default iterator for the class.
* @returns {Iterator} An iterator for the class.
*/
[Symbol.iterator]() {
return this.values();
}
/**
* Create an iterator that returns each node in the list.
* @returns {Iterator} An iterator on the list.
*/
*values(){
/*
* The `current` variable is used to iterate over the list nodes.
* It starts out pointing to the head and is overwritten inside
* of the loop below.
*/
let current = this[head];
/*
* As long as `current` is not `null`, there is a piece of data
* to yield.
*/
while (current !== null) {
yield current.data;
current = current.next;
}
}
/**
* Converts the list into a string representation.
* @returns {String} A string representation of the list.
*/
toString(){
return [...this].toString();
}
}
exports.LinkedList = LinkedList;
================================================
FILE: src/data-structures/linked-list/package.json
================================================
{
"name": "@humanwhocodes/linked-list",
"version": "2.0.2",
"description": "A LinkedList implementation in JavaScript",
"main": "linked-list.js",
"scripts": {
"test": "npx mocha ../../../tests/data-structures/linked-list/linked-list.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/humanwhocodes/computer-science-in-javascript.git"
},
"keywords": [
"linked list",
"data structure",
"iterable"
],
"author": "Nicholas C. Zakas",
"license": "MIT",
"bugs": {
"url": "https://github.com/humanwhocodes/computer-science-in-javascript/issues"
},
"homepage": "https://github.com/humanwhocodes/computer-science-in-javascript#readme",
"engines": {
"node": ">=8.0.0"
}
}
================================================
FILE: tests/algorithms/sorting/bubble-sort/bubble-sort.js
================================================
/**
* @fileoverview Bubble Sort tests
*/
/* global it, describe */
"use strict";
//-----------------------------------------------------------------------------
// Requirements
//-----------------------------------------------------------------------------
const assert = require("chai").assert;
const { bubbleSort } = require("../../../../src/algorithms/sorting/bubble-sort");
//-----------------------------------------------------------------------------
// Tests
//-----------------------------------------------------------------------------
describe("bubbleSort()", () => {
[
[],
[1],
[2, 1],
[2, 1, 3],
[32,4,5,7,9,4,1],
[3,1,1,5,9,4,2, 5, 12, 45]
].forEach(items => {
it("should sort an array when the array has " + items.length + " item(s)", () => {
const result = [...items].sort((a, b) => a - b);
bubbleSort(items);
assert.deepStrictEqual(items, result);
});
});
});
================================================
FILE: tests/algorithms/sorting/quicksort/quicksort.js
================================================
/**
* @fileoverview Quick Sort tests
*/
/* global it, describe */
"use strict";
//-----------------------------------------------------------------------------
// Requirements
//-----------------------------------------------------------------------------
const assert = require("chai").assert;
const fs = require("fs");
const path = require("path");
// Load and evaluate the quickSort code in global scope
const quickSortPath = path.join(__dirname, "../../../../algorithms/sorting/quicksort/quicksort.js");
const quickSortCode = fs.readFileSync(quickSortPath, "utf8");
// Use Function constructor to create functions in the parent scope
const createFunctions = new Function(quickSortCode + "\nreturn { swap: swap, partition: partition, quickSort: quickSort };");
const { quickSort } = createFunctions();
//-----------------------------------------------------------------------------
// Tests
//-----------------------------------------------------------------------------
describe("quickSort()", () => {
[
[],
[1],
[2, 1],
[2, 1, 3],
[32,4,5,7,9,4,1],
[3,1,1,5,9,4,2, 5, 12, 45]
].forEach(items => {
it("should sort an array when the array has " + items.length + " item(s)", () => {
const result = [...items].sort((a, b) => a - b);
quickSort(items);
assert.deepStrictEqual(items, result);
});
});
// Test cases specifically for pivot edge cases
it("should sort when pivot is minimum value", () => {
const items = [5, 5, 5, 1, 5];
const expected = [1, 5, 5, 5, 5];
quickSort(items);
assert.deepStrictEqual(items, expected);
});
it("should sort when pivot is maximum value", () => {
const items = [1, 1, 1, 9, 1];
const expected = [1, 1, 1, 1, 9];
quickSort(items);
assert.deepStrictEqual(items, expected);
});
it("should sort an array with all same values", () => {
const items = [3, 3, 3, 3, 3];
const expected = [3, 3, 3, 3, 3];
quickSort(items);
assert.deepStrictEqual(items, expected);
});
it("should sort an already sorted array", () => {
const items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const expected = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
quickSort(items);
assert.deepStrictEqual(items, expected);
});
it("should sort a reverse sorted array", () => {
const items = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1];
const expected = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
quickSort(items);
assert.deepStrictEqual(items, expected);
});
it("should sort an array with negative numbers", () => {
const items = [-5, 3, -1, 7, -10, 15];
const expected = [-10, -5, -1, 3, 7, 15];
quickSort(items);
assert.deepStrictEqual(items, expected);
});
it("should sort an array with duplicates", () => {
const items = [5, 2, 8, 2, 9, 1, 5, 5];
const expected = [1, 2, 2, 5, 5, 5, 8, 9];
quickSort(items);
assert.deepStrictEqual(items, expected);
});
// Edge case: array where subarray partitions could access out-of-bounds elements
it("should handle edge case with specific value distribution", () => {
const items = [100, 1, 2, 3, 4, 5];
const expected = [1, 2, 3, 4, 5, 100];
quickSort(items);
assert.deepStrictEqual(items, expected);
});
});
================================================
FILE: tests/data-structures/binary-heap/binary-heap.js
================================================
/**
* @fileoverview Doubly Linked List tests
*/
/* global it, describe, beforeEach */
"use strict";
//-----------------------------------------------------------------------------
// Requirements
//-----------------------------------------------------------------------------
const assert = require("chai").assert;
const { BinaryHeap } = require("../../../src/data-structures/binary-heap/binary-heap");
//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
/**
* Check that the contents of the heap match the values of the array.
* @param {BinaryHeap} heap The list to check
* @param {Array} values An array of values that should match.
* @throws {AssertionError} If the values in the list don't match the
* values in the array.
*/
function assertHeapValues(heap, values) {
const heapValues = [...heap.values()];
assert.deepStrictEqual(heapValues, values);
}
//-----------------------------------------------------------------------------
// Tests
//-----------------------------------------------------------------------------
describe("BinaryHeap", () => {
let heap;
beforeEach(() => {
heap = new BinaryHeap();
});
describe("add()", () => {
it("should store an item when one item is added", () => {
heap.add(1);
assertHeapValues(heap, [1]);
});
it("should store two items when multiple items are added", () => {
heap.add(2);
heap.add(1);
assertHeapValues(heap, [1,2]);
});
it("should store two items when multiple items are added", () => {
heap.add(2);
heap.add(3);
assertHeapValues(heap, [2,3]);
});
it("should store three items when multiple items are added", () => {
heap.add(2);
heap.add(3);
heap.add(1);
assertHeapValues(heap, [1,3,2]);
});
it("should store four items when multiple items are added", () => {
heap.add(2);
heap.add(3);
heap.add(1);
heap.add(0);
assertHeapValues(heap, [0,1,2,3]);
});
});
describe("size", () =
gitextract_sd20h0oq/
├── .eslintrc.js
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── algorithms/
│ ├── checksums/
│ │ └── luhn-algorithm/
│ │ └── luhn-algorithm.js
│ ├── searching/
│ │ └── binary-search/
│ │ └── binary-search.js
│ └── sorting/
│ ├── insertion-sort/
│ │ └── insertion-sort.js
│ ├── merge-sort-iterative/
│ │ └── merge-sort-iterative.js
│ ├── merge-sort-recursive/
│ │ ├── merge-sort-inplace.js
│ │ └── merge-sort-recursive.js
│ ├── quicksort/
│ │ └── quicksort.js
│ └── selection-sort/
│ └── selection-sort.js
├── encodings/
│ └── base64/
│ ├── base64.htm
│ └── base64.js
├── package.json
├── src/
│ ├── algorithms/
│ │ └── sorting/
│ │ └── bubble-sort/
│ │ ├── README.md
│ │ ├── bubble-sort.js
│ │ └── package.json
│ └── data-structures/
│ ├── binary-heap/
│ │ ├── README.md
│ │ ├── binary-heap.js
│ │ └── package.json
│ ├── binary-search-tree/
│ │ ├── README.md
│ │ ├── binary-search-tree.js
│ │ └── package.json
│ ├── circular-doubly-linked-list/
│ │ ├── README.md
│ │ ├── circular-doubly-linked-list.js
│ │ └── package.json
│ ├── circular-linked-list/
│ │ ├── README.md
│ │ ├── circular-linked-list.js
│ │ └── package.json
│ ├── doubly-linked-list/
│ │ ├── README.md
│ │ ├── doubly-linked-list.js
│ │ └── package.json
│ ├── hash-map/
│ │ ├── README.md
│ │ ├── hash-map.js
│ │ └── package.json
│ └── linked-list/
│ ├── README.md
│ ├── linked-list.js
│ └── package.json
└── tests/
├── algorithms/
│ └── sorting/
│ ├── bubble-sort/
│ │ └── bubble-sort.js
│ └── quicksort/
│ └── quicksort.js
└── data-structures/
├── binary-heap/
│ └── binary-heap.js
├── binary-search-tree/
│ └── binary-search-tree.js
├── circular-doubly-linked-list/
│ └── circular-doubly-linked-list.js
├── circular-linked-list/
│ └── circular-linked-list.js
├── doubly-linked-list/
│ └── doubly-linked-list.js
├── hash-map/
│ └── hash-map.js
└── linked-list/
└── linked-list.js
SYMBOL INDEX (140 symbols across 24 files)
FILE: algorithms/checksums/luhn-algorithm/luhn-algorithm.js
function isValidIdentifier (line 30) | function isValidIdentifier(identifier) {
FILE: algorithms/searching/binary-search/binary-search.js
function binarySearch (line 31) | function binarySearch(items, value){
FILE: algorithms/sorting/insertion-sort/insertion-sort.js
function insertionSort (line 30) | function insertionSort(items) {
FILE: algorithms/sorting/merge-sort-iterative/merge-sort-iterative.js
function merge (line 31) | function merge(left, right){
function mergeSort (line 57) | function mergeSort(items){
FILE: algorithms/sorting/merge-sort-recursive/merge-sort-inplace.js
function merge (line 31) | function merge(left, right){
function mergeSort (line 53) | function mergeSort(items){
FILE: algorithms/sorting/merge-sort-recursive/merge-sort-recursive.js
function merge (line 31) | function merge(left, right){
function mergeSort (line 53) | function mergeSort(items){
FILE: algorithms/sorting/quicksort/quicksort.js
function swap (line 31) | function swap(items, firstIndex, secondIndex){
function partition (line 37) | function partition(items, left, right) {
function quickSort (line 77) | function quickSort(items, left, right) {
FILE: algorithms/sorting/selection-sort/selection-sort.js
function swap (line 32) | function swap(items, firstIndex, secondIndex){
function selectionSort (line 44) | function selectionSort(items){
FILE: encodings/base64/base64.js
function base64Encode (line 29) | function base64Encode(text){
function base64Decode (line 80) | function base64Decode(text){
FILE: src/algorithms/sorting/bubble-sort/bubble-sort.js
function swap (line 12) | function swap(items, firstIndex, secondIndex){
FILE: src/data-structures/binary-heap/binary-heap.js
function getParentIndex (line 16) | function getParentIndex(index) {
function getLeftChildIndex (line 26) | function getLeftChildIndex(index) {
function getRightChildIndex (line 36) | function getRightChildIndex(index) {
function hasLeftChild (line 47) | function hasLeftChild(array, index) {
function hasRightChild (line 58) | function hasRightChild(array, index) {
function swap (line 70) | function swap(array, index1, index2) {
function heapifyUp (line 84) | function heapifyUp(array, compare) {
function heapifyDown (line 137) | function heapifyDown(array, compare) {
class BinaryHeap (line 218) | class BinaryHeap {
method constructor (line 224) | constructor(comparator = (a, b) => a - b) {
method add (line 248) | add(data) {
method isEmpty (line 257) | isEmpty() {
method peek (line 266) | peek() {
method poll (line 278) | poll() {
method size (line 319) | get size() {
method includes (line 328) | includes(value) {
method clear (line 336) | clear() {
method values (line 352) | values() {
method toString (line 360) | toString(){
method [Symbol.iterator] (line 344) | [Symbol.iterator]() {
FILE: src/data-structures/binary-search-tree/binary-search-tree.js
class BinarySearchTreeNode (line 16) | class BinarySearchTreeNode {
method constructor (line 22) | constructor(value) {
class BinarySearchTree (line 52) | class BinarySearchTree {
method constructor (line 57) | constructor() {
method add (line 74) | add(value) {
method has (line 136) | has(value) {
method delete (line 190) | delete(value) {
method clear (line 349) | clear() {
method size (line 357) | get size() {
method values (line 419) | *values(){
method toString (line 459) | toString(){
method [Symbol.iterator] (line 411) | [Symbol.iterator]() {
FILE: src/data-structures/circular-doubly-linked-list/circular-doubly-linked-list.js
class CircularDoublyLinkedListNode (line 16) | class CircularDoublyLinkedListNode {
method constructor (line 22) | constructor(data) {
class CircularDoublyLinkedList (line 51) | class CircularDoublyLinkedList {
method constructor (line 56) | constructor() {
method add (line 73) | add(data) {
method insertBefore (line 125) | insertBefore(data, index) {
method insertAfter (line 222) | insertAfter(data, index) {
method get (line 295) | get(index) {
method indexOf (line 355) | indexOf(data) {
method remove (line 414) | remove(index) {
method clear (line 502) | clear() {
method size (line 512) | get size() {
method values (line 563) | *values() {
method circularValues (line 603) | *circularValues() {
method reverse (line 637) | *reverse(){
method toString (line 664) | toString(){
method [Symbol.iterator] (line 555) | [Symbol.iterator]() {
FILE: src/data-structures/circular-linked-list/circular-linked-list.js
class CircularLinkedListNode (line 16) | class CircularLinkedListNode {
method constructor (line 22) | constructor(data) {
class CircularLinkedList (line 45) | class CircularLinkedList {
method constructor (line 50) | constructor() {
method add (line 67) | add(data) {
method insertBefore (line 105) | insertBefore(data, index) {
method insertAfter (line 190) | insertAfter(data, index) {
method get (line 264) | get(index) {
method indexOf (line 324) | indexOf(data) {
method remove (line 383) | remove(index) {
method clear (line 478) | clear() {
method size (line 486) | get size() {
method values (line 536) | *values(){
method circularValues (line 577) | *circularValues(){
method toString (line 611) | toString(){
method [Symbol.iterator] (line 528) | [Symbol.iterator]() {
FILE: src/data-structures/doubly-linked-list/doubly-linked-list.js
class DoublyLinkedListNode (line 17) | class DoublyLinkedListNode {
method constructor (line 23) | constructor(data) {
class DoublyLinkedList (line 52) | class DoublyLinkedList {
method constructor (line 57) | constructor() {
method add (line 81) | add(data) {
method insertBefore (line 125) | insertBefore(data, index) {
method insertAfter (line 225) | insertAfter(data, index) {
method get (line 307) | get(index) {
method indexOf (line 355) | indexOf(data) {
method find (line 403) | find(matcher) {
method findIndex (line 442) | findIndex(matcher) {
method remove (line 490) | remove(index) {
method clear (line 583) | clear() {
method size (line 594) | get size() {
method values (line 643) | *values(){
method reverse (line 666) | *reverse(){
method toString (line 689) | toString(){
method [Symbol.iterator] (line 635) | [Symbol.iterator]() {
FILE: src/data-structures/hash-map/hash-map.js
constant ARRAY_SIZE (line 18) | const ARRAY_SIZE = 16;
function hashCodePoints (line 26) | function hashCodePoints(key) {
function getArrayIndexFromHashCode (line 51) | function getArrayIndexFromHashCode(hashCode) {
function createArray (line 60) | function createArray() {
function assertNonEmptyString (line 92) | function assertNonEmptyString(key) {
class HashMap (line 113) | class HashMap {
method constructor (line 118) | constructor() {
method set (line 137) | set(key, value) {
method get (line 198) | get(key) {
method has (line 249) | has(key) {
method delete (line 293) | delete(key) {
method size (line 349) | get size() {
method clear (line 394) | clear() {
method entries (line 415) | *entries() {
method keys (line 435) | *keys() {
method values (line 451) | *values() {
method toString (line 467) | toString(){
method [Symbol.iterator] (line 407) | [Symbol.iterator]() {
FILE: src/data-structures/linked-list/linked-list.js
class LinkedListNode (line 16) | class LinkedListNode {
method constructor (line 22) | constructor(data) {
class LinkedList (line 45) | class LinkedList {
method constructor (line 50) | constructor() {
method add (line 67) | add(data) {
method insertBefore (line 118) | insertBefore(data, index) {
method insertAfter (line 204) | insertAfter(data, index) {
method get (line 271) | get(index) {
method indexOf (line 320) | indexOf(data) {
method remove (line 367) | remove(index) {
method clear (line 451) | clear() {
method size (line 459) | get size() {
method values (line 508) | *values(){
method toString (line 531) | toString(){
method [Symbol.iterator] (line 500) | [Symbol.iterator]() {
FILE: tests/data-structures/binary-heap/binary-heap.js
function assertHeapValues (line 25) | function assertHeapValues(heap, values) {
FILE: tests/data-structures/binary-search-tree/binary-search-tree.js
function assertTreeValues (line 26) | function assertTreeValues(tree, values) {
FILE: tests/data-structures/circular-doubly-linked-list/circular-doubly-linked-list.js
function assertListValues (line 25) | function assertListValues(list, values) {
FILE: tests/data-structures/circular-linked-list/circular-linked-list.js
function assertListValues (line 26) | function assertListValues(list, values) {
FILE: tests/data-structures/doubly-linked-list/doubly-linked-list.js
function assertListValues (line 25) | function assertListValues(list, values) {
FILE: tests/data-structures/hash-map/hash-map.js
function assertHashMapValues (line 25) | function assertHashMapValues(map, values) {
FILE: tests/data-structures/linked-list/linked-list.js
function assertListValues (line 26) | function assertListValues(list, values) {
Condensed preview — 49 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (282K chars).
[
{
"path": ".eslintrc.js",
"chars": 514,
"preview": "module.exports = {\n \"env\": {\n \"commonjs\": true,\n \"es6\": true,\n \"node\": true\n },\n \"extends\""
},
{
"path": ".gitignore",
"chars": 23,
"preview": "node_modules/\nholding/\n"
},
{
"path": ".travis.yml",
"chars": 162,
"preview": "language: node_js\nnode_js:\n - \"8\"\n - \"9\"\n - \"10\"\n - \"11\"\nsudo: false\nbranches:\n only:\n - master\n\n# Run"
},
{
"path": "LICENSE",
"chars": 1083,
"preview": "Copyright (c) 2009 Nicholas C. Zakas. All rights reserved.\n\nPermission is hereby granted, free of charge, to any person "
},
{
"path": "README.md",
"chars": 3967,
"preview": "# Computer Science in JavaScript\n\nby [Nicholas C. Zakas](https://humanwhocodes.com)\n\nIf you find this useful, please con"
},
{
"path": "algorithms/checksums/luhn-algorithm/luhn-algorithm.js",
"chars": 2137,
"preview": "/*\n * Luhn algorithm implementation in JavaScript\n * Copyright (c) 2009 Nicholas C. Zakas\n * \n * Permission is hereby gr"
},
{
"path": "algorithms/searching/binary-search/binary-search.js",
"chars": 2087,
"preview": "/*\n * Binary search implementation in JavaScript\n * Copyright (c) 2009 Nicholas C. Zakas\n * \n * Permission is hereby gra"
},
{
"path": "algorithms/sorting/insertion-sort/insertion-sort.js",
"chars": 2207,
"preview": "/*\n * Insertion sort implementation in JavaScript\n * Copyright (c) 2012 Nicholas C. Zakas\n * \n * Permission is hereby gr"
},
{
"path": "algorithms/sorting/merge-sort-iterative/merge-sort-iterative.js",
"chars": 2608,
"preview": "/*\n * Iterative merge sort implementation in JavaScript\n * Copyright (c) 2009-2011 Nicholas C. Zakas\n * \n * Permission i"
},
{
"path": "algorithms/sorting/merge-sort-recursive/merge-sort-inplace.js",
"chars": 2361,
"preview": "/*\n * Recursive merge sort implementation in JavaScript\n * Copyright (c) 2012 Nicholas C. Zakas\n * \n * Permission is her"
},
{
"path": "algorithms/sorting/merge-sort-recursive/merge-sort-recursive.js",
"chars": 2175,
"preview": "/*\n * Recursive merge sort implementation in JavaScript\n * Copyright (c) 2009 Nicholas C. Zakas\n * \n * Permission is her"
},
{
"path": "algorithms/sorting/quicksort/quicksort.js",
"chars": 3317,
"preview": "/*\n * Quick sort implementation in JavaScript\n * Copyright (c) 2012 Nicholas C. Zakas\n *\n * Permission is hereby granted"
},
{
"path": "algorithms/sorting/selection-sort/selection-sort.js",
"chars": 2278,
"preview": "/*\n * Selection sort implementation in JavaScript\n * Copyright (c) 2009 Nicholas C. Zakas\n * \n * Permission is hereby gr"
},
{
"path": "encodings/base64/base64.htm",
"chars": 6532,
"preview": "<html>\n<head>\n<title>Base64 Tests</title>\n<!-- Combo-handled YUI CSS files: -->\n<link rel=\"stylesheet\" type=\"text/css\" h"
},
{
"path": "encodings/base64/base64.js",
"chars": 4057,
"preview": "/*\n * Base 64 implementation in JavaScript\n * Copyright (c) 2009 Nicholas C. Zakas. All rights reserved.\n * \n * Permissi"
},
{
"path": "package.json",
"chars": 948,
"preview": "{\n \"name\": \"@humanwhocodes/computer-science-in-javascript\",\n \"version\": \"2.0.0\",\n \"private\": true,\n \"description\": \""
},
{
"path": "src/algorithms/sorting/bubble-sort/README.md",
"chars": 1364,
"preview": "# Bubble Sort in JavaScript\n\nby [Nicholas C. Zakas](https://humanwhocodes.com)\n\nIf you find this useful, please consider"
},
{
"path": "src/algorithms/sorting/bubble-sort/bubble-sort.js",
"chars": 1745,
"preview": "/**\n * @fileoverview Bubble sort implementation in JavaScript\n */\n\n/**\n * Swaps two values in an array.\n * @param {Array"
},
{
"path": "src/algorithms/sorting/bubble-sort/package.json",
"chars": 746,
"preview": "{\n \"name\": \"@humanwhocodes/bubble-sort\",\n \"version\": \"2.0.0\",\n \"description\": \"A bubble sort implementation in JavaSc"
},
{
"path": "src/data-structures/binary-heap/README.md",
"chars": 2555,
"preview": "# JavaScript Binary Heap Class\n\nby [Nicholas C. Zakas](https://humanwhocodes.com)\n\nIf you find this useful, please consi"
},
{
"path": "src/data-structures/binary-heap/binary-heap.js",
"chars": 11360,
"preview": "\n/**\n * @fileoverview Binary Heap implementation in JavaScript\n */\n\n//--------------------------------------------------"
},
{
"path": "src/data-structures/binary-heap/package.json",
"chars": 742,
"preview": "{\n \"name\": \"@humanwhocodes/binary-heap\",\n \"version\": \"2.0.1\",\n \"description\": \"A binary heap implementation in JavaSc"
},
{
"path": "src/data-structures/binary-search-tree/README.md",
"chars": 2143,
"preview": "# JavaScript Binary Search Tree Class\n\nby [Nicholas C. Zakas](https://humanwhocodes.com)\n\nIf you find this useful, pleas"
},
{
"path": "src/data-structures/binary-search-tree/binary-search-tree.js",
"chars": 14550,
"preview": "/**\n * @fileoverview Binary Search Tree implementation in JavaScript\n */\n\n/*\n * These symbols are used to represent prop"
},
{
"path": "src/data-structures/binary-search-tree/package.json",
"chars": 807,
"preview": "{\n \"name\": \"@humanwhocodes/binary-search-tree\",\n \"version\": \"2.0.0\",\n \"description\": \"A binary search tree implementa"
},
{
"path": "src/data-structures/circular-doubly-linked-list/README.md",
"chars": 2506,
"preview": "# JavaScript Circular Doubly Linked List Class\n\nby [Nicholas C. Zakas](https://humanwhocodes.com)\n\nIf you find this usef"
},
{
"path": "src/data-structures/circular-doubly-linked-list/circular-doubly-linked-list.js",
"chars": 22378,
"preview": "/**\n * @fileoverview Circular Doubly linked list implementation in JavaScript\n */\n\n/*\n * These symbols are used to repre"
},
{
"path": "src/data-structures/circular-doubly-linked-list/package.json",
"chars": 914,
"preview": "{\n \"name\": \"@humanwhocodes/circular-doubly-linked-list\",\n \"version\": \"2.0.2\",\n \"description\": \"A circular doubly link"
},
{
"path": "src/data-structures/circular-linked-list/README.md",
"chars": 2204,
"preview": "# JavaScript Circular Linked List Class\n\nby [Nicholas C. Zakas](https://humanwhocodes.com)\n\nIf you find this useful, ple"
},
{
"path": "src/data-structures/circular-linked-list/circular-linked-list.js",
"chars": 21096,
"preview": "/**\n * @fileoverview Circular Linked List implementation in JavaScript\n */\n\n/*\n * These symbols are used to represent pr"
},
{
"path": "src/data-structures/circular-linked-list/package.json",
"chars": 819,
"preview": "{\n \"name\": \"@humanwhocodes/circular-linked-list\",\n \"version\": \"2.0.0\",\n \"description\": \"A circular linked list implem"
},
{
"path": "src/data-structures/doubly-linked-list/README.md",
"chars": 2618,
"preview": "# JavaScript Doubly Linked List Class\n\nby [Nicholas C. Zakas](https://humanwhocodes.com)\n\nIf you find this useful, pleas"
},
{
"path": "src/data-structures/doubly-linked-list/doubly-linked-list.js",
"chars": 22562,
"preview": "/**\n * @fileoverview Doubly linked list implementation in JavaScript\n */\n\n/*\n * These symbols are used to represent prop"
},
{
"path": "src/data-structures/doubly-linked-list/package.json",
"chars": 807,
"preview": "{\n \"name\": \"@humanwhocodes/doubly-linked-list\",\n \"version\": \"2.2.0\",\n \"description\": \"A doubly linked list implementa"
},
{
"path": "src/data-structures/hash-map/README.md",
"chars": 2104,
"preview": "# JavaScript Hash Map Class\n\nby [Nicholas C. Zakas](https://humanwhocodes.com)\n\nIf you find this useful, please consider"
},
{
"path": "src/data-structures/hash-map/hash-map.js",
"chars": 14572,
"preview": "\n/**\n * @fileoverview Hash Map implementation in JavaScript\n */\n\n\"use strict\";\n\n//--------------------------------------"
},
{
"path": "src/data-structures/hash-map/package.json",
"chars": 833,
"preview": "{\n \"name\": \"@humanwhocodes/hash-map\",\n \"version\": \"2.0.0\",\n \"description\": \"A hash map implementation in JavaScript\","
},
{
"path": "src/data-structures/linked-list/README.md",
"chars": 2191,
"preview": "# JavaScript Linked List Class\n\nby [Nicholas C. Zakas](https://humanwhocodes.com)\n\nIf you find this useful, please consi"
},
{
"path": "src/data-structures/linked-list/linked-list.js",
"chars": 17835,
"preview": "/**\n * @fileoverview Linked List implementation in JavaScript\n */\n\n/*\n * These symbols are used to represent properties "
},
{
"path": "src/data-structures/linked-list/package.json",
"chars": 745,
"preview": "{\n \"name\": \"@humanwhocodes/linked-list\",\n \"version\": \"2.0.2\",\n \"description\": \"A LinkedList implementation in JavaScr"
},
{
"path": "tests/algorithms/sorting/bubble-sort/bubble-sort.js",
"chars": 1001,
"preview": "/**\n * @fileoverview Bubble Sort tests\n */\n/* global it, describe */\n\n\"use strict\";\n\n//---------------------------------"
},
{
"path": "tests/algorithms/sorting/quicksort/quicksort.js",
"chars": 3541,
"preview": "/**\n * @fileoverview Quick Sort tests\n */\n/* global it, describe */\n\n\"use strict\";\n\n//----------------------------------"
},
{
"path": "tests/data-structures/binary-heap/binary-heap.js",
"chars": 10471,
"preview": "/**\n * @fileoverview Doubly Linked List tests\n */\n/* global it, describe, beforeEach */\n\"use strict\";\n\n//---------------"
},
{
"path": "tests/data-structures/binary-search-tree/binary-search-tree.js",
"chars": 9115,
"preview": "/**\n * @fileoverview BinarySearchTree tests\n */\n/* global it, describe, beforeEach */\n\n\"use strict\";\n\n//----------------"
},
{
"path": "tests/data-structures/circular-doubly-linked-list/circular-doubly-linked-list.js",
"chars": 12596,
"preview": "/**\n * @fileoverview Circular Doubly Linked List tests\n */\n/* global it, describe, beforeEach */\n\"use strict\";\n\n//------"
},
{
"path": "tests/data-structures/circular-linked-list/circular-linked-list.js",
"chars": 12003,
"preview": "/**\n * @fileoverview Linked List tests\n */\n/* global it, describe, beforeEach */\n\n\"use strict\";\n\n//---------------------"
},
{
"path": "tests/data-structures/doubly-linked-list/doubly-linked-list.js",
"chars": 13389,
"preview": "/**\n * @fileoverview Doubly Linked List tests\n */\n/* global it, describe, beforeEach */\n\"use strict\";\n\n//---------------"
},
{
"path": "tests/data-structures/hash-map/hash-map.js",
"chars": 7889,
"preview": "/**\n * @fileoverview Hash Map tests\n */\n/* global it, describe, beforeEach */\n\"use strict\";\n\n//-------------------------"
},
{
"path": "tests/data-structures/linked-list/linked-list.js",
"chars": 11008,
"preview": "/**\n * @fileoverview Linked List tests\n */\n/* global it, describe, beforeEach */\n\n\"use strict\";\n\n//---------------------"
}
]
About this extraction
This page contains the full source code of the humanwhocodes/computer-science-in-javascript GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 49 files (261.4 KB), approximately 62.2k tokens, and a symbol index with 140 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.