├── .prettierignore ├── .travis.yml ├── .prettierrc ├── search ├── linearSearch.js ├── README.md └── binary-search-tree.js ├── fast-fourier-transforms ├── README.md └── fft.js ├── stack ├── stack.js └── README.md ├── graph ├── tree.js ├── graphNode.js ├── gridGraph.js └── README.md ├── memoization ├── README.md └── memoize.js ├── sorting ├── bubble-recursive.js ├── selectionsort.js ├── quick-recursive.js ├── bubblesort.js ├── insertionsort.js ├── mergesort.js ├── quicksort.js └── README.md ├── graph-traversing ├── depth-first-search-imperative.js ├── depth-first-search-recursive.js ├── breadth-first-search.js └── README.md ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── complex-array ├── README.md └── complex-array.js ├── queue ├── priority-queue.js ├── queue.js └── README.md ├── rabin-karp ├── README.md └── rk.js ├── list ├── README.md └── list.js ├── test ├── binary-heap.spec.js ├── rabin-karp.spec.js ├── stack.spec.js ├── shortest-path.spec.js ├── hash-table.spec.js ├── trie.spec.js ├── fft-test-helper.js ├── sorting.spec.js ├── complex-array.spec.js ├── fft.spec.js ├── graph.spec.js ├── graph-traversing.spec.js ├── queue.spec.js ├── bitwise.spec.js ├── list.spec.js └── linked-list.spec.js ├── bitwise ├── bitwise-rgb-hex-binary.js ├── bitwise-basics.js └── README.md ├── LICENSE ├── hash-table ├── README.md └── hash-table.js ├── package.json ├── trie ├── README.md └── trie.js ├── binary-heap ├── README.md └── binaryHeap.js ├── linked-list ├── README.md └── linkedList.js ├── .gitignore ├── CONTRIBUTING.md ├── shortest-path ├── dijkstra.js ├── README.md └── aStar.js ├── CODE_OF_CONDUCT.md ├── .eslintrc.js └── README.md /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .nyc_output/ 3 | coverage/ 4 | .git/ 5 | *.log 6 | package-lock.json 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "18" 4 | - "20" 5 | notifications: 6 | email: false 7 | script: npm test 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "es5", 4 | "singleQuote": true, 5 | "printWidth": 80, 6 | "tabWidth": 2, 7 | "useTabs": false, 8 | "bracketSpacing": true, 9 | "arrowParens": "avoid", 10 | "endOfLine": "lf" 11 | } 12 | -------------------------------------------------------------------------------- /search/linearSearch.js: -------------------------------------------------------------------------------- 1 | class LinearSearch { 2 | constructor(arr) { 3 | this.arr = arr; 4 | } 5 | 6 | search(element) { 7 | for ( let i = 0; i < this.arr.length; i++) { 8 | if (element === this.arr[i]) { 9 | return i; 10 | } 11 | } 12 | return -1; 13 | } 14 | } 15 | 16 | module.exports = LinearSearch; 17 | -------------------------------------------------------------------------------- /fast-fourier-transforms/README.md: -------------------------------------------------------------------------------- 1 | # Fast Fourier Transforms (FFT) 2 | 3 | ### Music Search using Fast Fourier Transforms (FFT) 4 | 5 | Music recognition is done by converting it into frequency domain using FFT. FFT has implementations in number of languages. See this article for a great start: Shazam It! Music Recognition Algorithms, Fingerprinting, and Processing. -------------------------------------------------------------------------------- /stack/stack.js: -------------------------------------------------------------------------------- 1 | class Stack { 2 | constructor() { 3 | this.top = null; 4 | } 5 | 6 | push( value ) { 7 | const newNode = { 8 | value, 9 | next: this.top, 10 | }; 11 | this.top = newNode; 12 | } 13 | 14 | pop() { 15 | if ( this.top !== null ) { 16 | const { value } = this.top; 17 | this.top = this.top.next; 18 | return value; 19 | } 20 | return null; 21 | } 22 | } 23 | 24 | module.exports = Stack; 25 | -------------------------------------------------------------------------------- /graph/tree.js: -------------------------------------------------------------------------------- 1 | class Node { 2 | constructor( value ) { 3 | this.value = value; 4 | this.children = []; 5 | } 6 | 7 | getChildren() { 8 | return this.children; 9 | } 10 | 11 | addChild( child ) { 12 | this.children.push( child ); 13 | } 14 | 15 | hasChildren() { 16 | return this.children.length > 0; 17 | } 18 | 19 | getValue() { 20 | return this.value; 21 | } 22 | 23 | toString() { 24 | return this.name; 25 | } 26 | } 27 | 28 | module.exports = Node; 29 | -------------------------------------------------------------------------------- /memoization/README.md: -------------------------------------------------------------------------------- 1 | # Memoization 2 | 3 | In computing, memoization is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again. 4 | 5 | The term "memoization" was coined by Donald Michie in 1968 and is derived from the Latin word "memorandum" ("to be remembered"), usually truncated as "memo" in the English language, and thus carries the meaning of "turning [the results of] a function into something to be remembered." 6 | -------------------------------------------------------------------------------- /sorting/bubble-recursive.js: -------------------------------------------------------------------------------- 1 | const swap = ( arr, i1, i2 ) => { 2 | const tmp = arr[i1]; 3 | arr[i1] = arr[i2]; 4 | arr[i2] = tmp; 5 | return arr; 6 | }; 7 | 8 | // Recursive implementation of Bubble Sort 9 | const bubbleSort = ( arr ) => { 10 | let swapped = false; 11 | 12 | for ( let i = 0; i < arr.length; i++ ) { 13 | if ( arr[i] > arr[i + 1] ) { 14 | swap( arr, i, i + 1 ); 15 | swapped = true; 16 | } 17 | } 18 | 19 | if ( swapped === true ) { 20 | return bubbleSort( arr ); 21 | } 22 | return arr; 23 | }; 24 | 25 | module.exports = bubbleSort; 26 | -------------------------------------------------------------------------------- /graph-traversing/depth-first-search-imperative.js: -------------------------------------------------------------------------------- 1 | const DFS = (start, searchFor) => { 2 | const stack = [start]; 3 | const visited = []; 4 | let currNode = start; 5 | 6 | while ( stack.length !== 0) { 7 | currNode = stack.pop(); 8 | if (currNode.value === searchFor) { 9 | return currNode; 10 | } 11 | if (visited.indexOf(currNode) === -1) { 12 | visited.push(currNode); 13 | currNode.neighbors.forEach((w) => { 14 | stack.push(w); 15 | }); 16 | } 17 | } 18 | 19 | if (stack.length === 0) { 20 | return false; 21 | } 22 | }; 23 | 24 | module.exports = DFS; 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /complex-array/README.md: -------------------------------------------------------------------------------- 1 | # Complex Arrays 2 | 3 | A complex variable or value is usually represented as a pair of floating point numbers. Languages that support a complex data type usually provide special syntax for building such values, and extend the basic arithmetic operations ('+', '−', '×', '÷') to act on them. These operations are usually translated by the compiler into a sequence of floating-point machine instructions or into library calls. Those languages may also provide support for other operations, such as formatting, equality testing, etc. As in mathematics, those languages often interpret a floating-point value as equivalent to a complex value with a zero imaginary part. 4 | 5 | -------------------------------------------------------------------------------- /queue/priority-queue.js: -------------------------------------------------------------------------------- 1 | /** 2 | Basic priority queue implementation. 3 | If a better priority queue is wanted/needed, 4 | */ 5 | 6 | class PriorityQueue { 7 | constructor() { 8 | this._nodes = []; 9 | } 10 | 11 | enqueue(priority, key) { 12 | this._nodes.push({ 13 | key, 14 | priority, 15 | }); 16 | this.sort(); 17 | } 18 | 19 | dequeue() { 20 | if (this._nodes.length <= 0) { 21 | return null; 22 | } 23 | return this._nodes.shift().key; 24 | } 25 | 26 | sort() { 27 | this._nodes.sort((a, b) => a.priority - b.priority); 28 | } 29 | 30 | isEmpty() { 31 | return !this._nodes.length; 32 | } 33 | } 34 | 35 | module.exports = PriorityQueue; 36 | -------------------------------------------------------------------------------- /graph/graphNode.js: -------------------------------------------------------------------------------- 1 | class Node { 2 | constructor(name, value) { 3 | this.name = name; 4 | this.value = value; 5 | this.neighbors = []; 6 | } 7 | 8 | addNeighbors(arr) { 9 | if (this.neighbors.length !== 0 && arr !== undefined) { 10 | this.neighbors = this.neighbors.concat(arr); 11 | } else if (this.neighbors.length === 0 && arr !== undefined) { 12 | this.neighbors = arr; 13 | return this.neighbors; 14 | } 15 | return this.neighbors; 16 | } 17 | 18 | getNeighbors() { 19 | return this.neighbors; 20 | } 21 | 22 | getValue() { 23 | return this.value; 24 | } 25 | 26 | toString() { 27 | return this.name; 28 | } 29 | } 30 | 31 | module.exports = Node; 32 | -------------------------------------------------------------------------------- /rabin-karp/README.md: -------------------------------------------------------------------------------- 1 | # Rabin- Karp 2 | 3 | ## String Searching Algorithm 4 | 5 | String matching algorithms are pervasive in software. One particularly fun one, is Rabin Karp, which is used in Plagiarism detection. As a student in CS (or in any major), plagiarism detection should be of interest ;-) 6 | 7 | ![rabin-karp-basic-principles](https://cloud.githubusercontent.com/assets/4650739/25076866/79d441d6-22c0-11e7-8cf1-6af40e7cecff.png) 8 | 9 | Rabin Karp is relatively easy to implement. See this: 10 | 11 | * [Rabin–Karp algorithm - Wikipedia](https://en.wikipedia.org/wiki/Rabin%E2%80%93Karp_algorithm) 12 | 13 | Rabin Karp has also inspired a string matching routine in Zlib (one of the most popular un/zip libraries ever). See this, directly into the source code. -------------------------------------------------------------------------------- /list/README.md: -------------------------------------------------------------------------------- 1 | # Array 2 | 3 | - implement arrays methods 4 | - size() - number of items 5 | - capacity() - number of items it can hold 6 | - is_empty() 7 | - at(index) - returns item at given index, blows up if index out of bounds 8 | - push(item) 9 | - insert(index, item) - inserts item at index, shifts that index's value and trailing elements to the right 10 | - prepend(item) - can use insert above at index 0 11 | - pop() - remove from end, return value 12 | - delete(index) - delete item at index, shifting all trailing elements left 13 | - remove(item) - looks for value and removes index holding it (even if in multiple places) 14 | - find(item) - looks for value and returns first index with that value, -1 if not found 15 | - resize(new_capacity) // private function -------------------------------------------------------------------------------- /queue/queue.js: -------------------------------------------------------------------------------- 1 | class Queue { 2 | constructor() { 3 | this.front = null; 4 | this.back = null; 5 | } 6 | 7 | enqueue( value ) { 8 | const newNode = { 9 | value, 10 | next: null, 11 | }; 12 | if ( !this.front ) { 13 | this.back = newNode; 14 | this.front = this.back; 15 | } else { 16 | this.back.next = newNode; 17 | this.back = newNode; 18 | } 19 | } 20 | 21 | dequeue() { 22 | if ( this.front ) { 23 | const { value } = this.front; 24 | this.front = this.front.next; 25 | return value; 26 | } 27 | return null; 28 | } 29 | 30 | isEmpty() { 31 | if ( this.front === null ) { 32 | return true; 33 | } 34 | return false; 35 | } 36 | } 37 | 38 | module.exports = Queue; 39 | -------------------------------------------------------------------------------- /stack/README.md: -------------------------------------------------------------------------------- 1 | # Stack (abstract data type) 2 | 3 | A stack is an abstract data type that serves as a collection of elements, with two principal operations: push, which adds an element to the collection, and pop, which removes the most recently added element that was not yet removed. The order in which elements come off a stack gives rise to its alternative name, LIFO (for last in, first out). Additionally, a peek operation may give access to the top without modifying the stack. 4 | 5 | The name "stack" for this type of structure comes from the analogy to a set of physical items stacked on top of each other, which makes it easy to take an item off the top of the stack, while getting to an item deeper in the stack may require taking off multiple other items first. 6 | 7 | ![Simple representation of a stack runtime with push and pop operations.](https://upload.wikimedia.org/wikipedia/commons/thumb/b/b4/Lifo_stack.png/350px-Lifo_stack.png) -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Desktop (please complete the following information):** 24 | - OS: [e.g. iOS] 25 | - Browser [e.g. chrome, safari] 26 | - Version [e.g. 22] 27 | 28 | **Smartphone (please complete the following information):** 29 | - Device: [e.g. iPhone6] 30 | - OS: [e.g. iOS8.1] 31 | - Browser [e.g. stock browser, safari] 32 | - Version [e.g. 22] 33 | 34 | **Additional context** 35 | Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /sorting/selectionsort.js: -------------------------------------------------------------------------------- 1 | const selectionModule = (() => { 2 | // swap method because its used multiple times 3 | const swap = (array, index1, index2) => { 4 | // store a tmp variable at pos index2 5 | const tmp = array[index2]; 6 | 7 | // set value of index2 to our value at index 8 | array[index2] = array[index1]; 9 | 10 | // set our value of index1 to our stored variable 11 | array[index1] = tmp; 12 | }; 13 | 14 | // Everything after the return statement is public 15 | return { 16 | selectionSort: ( array ) => { 17 | for ( let i = 0; i < array.length - 1; i++ ) { 18 | let min = i; 19 | for ( let j = i + 1; j < array.length; j++ ) { 20 | if ( array[j] < array[min] ) { 21 | min = j; 22 | } 23 | } 24 | if (min !== i) { 25 | swap(array, i, min); 26 | } 27 | } 28 | return array; 29 | }, 30 | }; 31 | }); 32 | 33 | module.exports = selectionModule; 34 | -------------------------------------------------------------------------------- /memoization/memoize.js: -------------------------------------------------------------------------------- 1 | /* 2 | Memoization is a useful optimization technique for caching the results of function calls such that 3 | lengthy lookups or expensive recursive computations can be minimized where possible. 4 | */ 5 | 6 | Function.prototype.memoize = () => { 7 | const cache = {}; 8 | 9 | return (arg) => { 10 | if (arg in cache) { 11 | // Cache Hit 12 | return cache[arg]; 13 | } 14 | // Chache Miss 15 | cache[arg] = this(arg); 16 | return cache[arg]; 17 | }; 18 | }; 19 | 20 | // Test 21 | /** 22 | * Test function for memoization 23 | * @param {*} x - Input value 24 | * @returns {*} The input value 25 | */ 26 | function fooBar(x) { 27 | return x; 28 | } 29 | 30 | const memoFooBar = fooBar.memoize(); 31 | console.log('memoFooBar(1): ', memoFooBar(1)); 32 | memoFooBar(1); // Cache miss 33 | memoFooBar(1); // Cache hit :D 34 | memoFooBar(2); // Cache miss 35 | 36 | // Source: https://addyosmani.com/blog/faster-javascript-memoization/ 37 | -------------------------------------------------------------------------------- /sorting/quick-recursive.js: -------------------------------------------------------------------------------- 1 | const quickSort = ( arr ) => { 2 | // Base case 3 | if ( arr.length <= 1 ) { 4 | return arr; 5 | } 6 | 7 | // 1) Pick a pivot 8 | const pivot = arr[0]; 9 | 10 | // 2) Partition 11 | const { left, right } = partition( arr, pivot ); 12 | 13 | // 3) Call quick sort recursively 14 | const leftArr = quickSort( left ); 15 | const rightArr = quickSort( right ); 16 | 17 | // 4) Concat after calling quicksort recursively 18 | return leftArr.concat( pivot, rightArr ); 19 | }; 20 | 21 | const partition = ( arr, pivot ) => { 22 | const left = []; 23 | const right = []; 24 | 25 | // Loop through the array and split it into left and right arrays 26 | for ( let i = 1; i < arr.length; i++ ) { 27 | // If value is less than the pivot - push into the left array, else push it into the right 28 | if ( arr[i] < pivot ) { 29 | left.push( arr[i] ); 30 | } else { 31 | right.push( arr[i] ); 32 | } 33 | } 34 | 35 | return { 36 | left, 37 | right, 38 | }; 39 | }; 40 | 41 | module.exports = quickSort; 42 | -------------------------------------------------------------------------------- /test/binary-heap.spec.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const BinaryHeap = require('../binary-heap/binaryHeap'); 3 | 4 | const { expect } = chai; 5 | 6 | describe( 'BinaryHeap', () => { 7 | let heap; 8 | 9 | beforeEach(() => { 10 | heap = new BinaryHeap((x) => x); 11 | const data = [10, 3, 4, 8, 2, 9, 7, 1, 2, 6, 5]; 12 | 13 | data.forEach((el) => { 14 | heap.push(el); 15 | }); 16 | }); 17 | 18 | // describe( 'Constructor', () => { 19 | // it( 'should return a new empty binary heap', () => { 20 | // const fn = ((x) => { 21 | // return x; 22 | // }); 23 | // const newHeap = new BinaryHeap(fn); 24 | 25 | // console.log(newHeap); 26 | // expect(newHeap).to.deep.equal({ 27 | // content: [], 28 | // scoreFunction: fn, 29 | // }); 30 | 31 | // }); 32 | // }); 33 | 34 | describe( 'push', () => { 35 | it( 'should return a new empty binary heap', () => { 36 | // console.log(heap.pop()) 37 | // expect.(heap.content).to.deep.equal([1, 2, 4, 2, 5, 9, 7, 10, 3, 8, 6]) 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /graph-traversing/depth-first-search-recursive.js: -------------------------------------------------------------------------------- 1 | /* 2 | 1 procedure DFS(G,v): 3 | 2 label v as discovered 4 | 3 for all edges from v to w in G.adjacentEdges(v) do 5 | 4 if vertex w is not labeled as discovered then 6 | 5 recursively call DFS(G,w) 7 | */ 8 | const dfs = (start, searchFor) => { 9 | if (!searchFor || !start) { 10 | throw new Error('Invalid input'); 11 | } 12 | 13 | // If the node we are searching for 14 | if (searchFor === start.getValue()) { 15 | return start; 16 | } 17 | let child; 18 | let found; 19 | const neighbors = start.getNeighbors(); 20 | 21 | // iterate through all of the starting nodes neighbors 22 | for (let i = 0; i < neighbors.length; i++) { 23 | child = neighbors[i]; 24 | 25 | // Recursviely call the child nodes until we find 26 | found = dfs(child, searchFor); 27 | 28 | // If we find the item we are searching for - return the node 29 | if (found) { 30 | return found; 31 | } 32 | } 33 | // If we cannot find the node - return false; 34 | return false; 35 | }; 36 | 37 | module.exports = dfs; 38 | -------------------------------------------------------------------------------- /bitwise/bitwise-rgb-hex-binary.js: -------------------------------------------------------------------------------- 1 | // convert 0..255 R,G,B values to binary string 2 | const RGBToBin = ( r, g, b ) => { 3 | const bin = r << 16 | g << 8 | b; 4 | return ((h) => new Array(25 - h.length).join( '0' ) + h)(bin.toString(2)); 5 | }; 6 | 7 | // convert 0..255 R,G,B values to a hexidecimal color string 8 | const RGBToHex = ( r, g, b ) => { 9 | const bin = r << 16 | g << 8 | b; 10 | return (( h ) => new Array(7 - h.length).join( '0' ) + h)( bin.toString( 16 ).toUpperCase() ); 11 | }; 12 | 13 | // convert a 24 bit binary color to 0..255 R,G,B 14 | const binToRGB = ( bin ) => { 15 | const pbin = parseInt( bin, 2 ); 16 | const r = pbin >> 16; 17 | const g = pbin >> 8 & 0xFF; 18 | const b = pbin & 0xFF; 19 | return [r, g, b]; 20 | }; 21 | 22 | // convert a hexidecimal color string to 0..255 R,G,B 23 | const hexToRGB = ( hex ) => { 24 | const r = hex >> 16; 25 | const g = hex >> 8 & 0xFF; 26 | const b = hex & 0xFF; 27 | return [r, g, b]; 28 | }; 29 | 30 | module.exports = { 31 | RGBToBin, 32 | RGBToHex, 33 | binToRGB, 34 | hexToRGB, 35 | }; 36 | 37 | // source: https://gist.github.com/lrvick/2080648 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2017 Joe Karlsson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /bitwise/bitwise-basics.js: -------------------------------------------------------------------------------- 1 | const bitwiseAND = (a, b) => (a & b).toString(2); 2 | 3 | const bitwiseOR = (a, b) => (a | b).toString(2); 4 | 5 | const bitwiseXOR = (a, b) => (a ^ b).toString(2); 6 | 7 | const bitwiseNOT = (a) => (~a >>> 0).toString(2); 8 | 9 | const bitwiseLeftShift = (a, bits) => (a << bits).toString(2); 10 | 11 | const bitwiseSignPropagatingRightShift = (a, bits) => (a >> bits).toString(2); 12 | 13 | const bitwiseZeroFillRightShift = (a, bits) => (a >>> bits).toString(2); 14 | 15 | const isEven = (n) => !(n & 1); 16 | 17 | const isOdd = (n) => ((n & 1) === 1); 18 | 19 | const dec2bin = (dec) => (dec >>> 0).toString(2); 20 | // Source: http://stackoverflow.com/questions/9939760/how-do-i-convert-an-integer-to-binary-in-javascript 21 | 22 | const avgInt = (a, b) => // a + b / 2 23 | (a + b) >> 1; 24 | 25 | const plusOneInt = (n) => // slower than ++ 26 | -~n; 27 | module.exports = { 28 | bitwiseAND, 29 | bitwiseOR, 30 | bitwiseXOR, 31 | bitwiseNOT, 32 | bitwiseLeftShift, 33 | bitwiseSignPropagatingRightShift, 34 | bitwiseZeroFillRightShift, 35 | isEven, 36 | isOdd, 37 | dec2bin, 38 | avgInt, 39 | plusOneInt, 40 | }; 41 | -------------------------------------------------------------------------------- /hash-table/README.md: -------------------------------------------------------------------------------- 1 | # Hash Tables 2 | 3 | Hash tables optimize storage for key-value pairs. In best case scenarios hash table insertion, retrieval and deletion are constant time. Hash tables are used to store large amounts of quickly accessible information like passwords. 4 | 5 | Anatomy of a Hash Table: 6 | 7 | This is a basic Javascript hash table implementation. A hash table can be conceptualized as an array holding a series of tuples stored in sub-arrays inside of an object: 8 | 9 | ``` 10 | { [[ [‘a’, 9], [‘b’, 88] ],[ [‘e’, 7], [‘q’, 8] ],[ [‘j’, 7], [‘l’, 8] ]] }; 11 | ``` 12 | 13 | The outer array holds a number of buckets (sub-arrays) equal to the max length of the array. Inside the buckets, tuples or two element arrays hold key-value pairs. 14 | 15 | When key-value pairs are inserted into the hash table, the key is hashed with a hashing function. The key and the maximum length of the array are passed to the hashing function which returns an index used to identify the bucket. 16 | 17 | - hash table 18 | - hash(k, m) - m is size of hash table 19 | - add(key, value) - if key already exists, update value 20 | - exists(key) 21 | - get(key) 22 | - remove(key) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "data-structure", 3 | "version": "1.0.0", 4 | "description": "Various data structures and algorithms implemented in JavaScript.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "nyc mocha", 8 | "test:watch": "mocha --watch", 9 | "test:coverage": "nyc report --reporter=text-lcov", 10 | "test:coverage:html": "nyc report --reporter=html", 11 | "lint": "eslint . --ext .js", 12 | "lint:fix": "eslint . --ext .js --fix", 13 | "format": "prettier --write .", 14 | "format:check": "prettier --check .", 15 | "dev": "npm run test:watch", 16 | "build": "echo 'No build step needed for this project'", 17 | "clean": "rm -rf .nyc_output coverage", 18 | "prepare": "husky install" 19 | }, 20 | "author": "Joe Karlsson", 21 | "license": "ISC", 22 | "devDependencies": { 23 | "chai": "^4.5.0", 24 | "eslint": "^8.57.0", 25 | "eslint-config-airbnb-base": "^15.0.0", 26 | "eslint-config-prettier": "^9.1.0", 27 | "eslint-plugin-import": "^2.29.1", 28 | "eslint-plugin-jsdoc": "^48.6.0", 29 | "husky": "^9.0.11", 30 | "lint-staged": "^15.2.2", 31 | "mocha": "^10.4.0", 32 | "mocha-lcov-reporter": "^1.3.0", 33 | "nyc": "^15.1.0", 34 | "prettier": "^3.2.5" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/rabin-karp.spec.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const searchRabinKarp = require('../rabin-karp/rk'); 3 | 4 | const { expect } = chai; 5 | 6 | describe( 'Rabin-Karp', () => { 7 | describe( '`searchRabinKarp` method', () => { 8 | it( 'str.length < text.length and match', () => { 9 | expect(searchRabinKarp('abcdefgh', 'cde')).to.deep.equal([2]); 10 | }); 11 | it( 'str.length < text.length and no match', () => { 12 | expect(searchRabinKarp('abcdefgh', 'klm')).to.deep.equal([]); 13 | }); 14 | it( 'str.length < text.length and several matches', () => { 15 | expect(searchRabinKarp('abcdefabcdefabcdef', 'cd')).to.deep.equal([2, 8, 14]); 16 | }); 17 | it( 'str.length == text.length and match', () => { 18 | expect(searchRabinKarp('abc', 'abc')).to.deep.equal([0]); 19 | }); 20 | it( 'str.length == text.length and no match', () => { 21 | expect(searchRabinKarp('abc', 'def')).to.deep.equal([]); 22 | }); 23 | it( 'str.length > text.length', () => { 24 | expect(searchRabinKarp('abc', 'abcd')).to.deep.equal([]); 25 | }); 26 | it( 'long string', () => { 27 | expect(searchRabinKarp('abcdabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc', 'cd')).to.deep.equal([2]); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /trie/README.md: -------------------------------------------------------------------------------- 1 | # Trie 2 | 3 | > pronouced "trie" as in `reTRIEve` 4 | 5 | Trie, a.k.a. a digital tree, is a search tree in which we can store data in a tree-like structure. In the case of this exercise, nodes (letters) will chain upon each other to build out a word. The last node of a word is called a leaf, signifying the end of a valid word. 6 | 7 | For example, the word, `cat`, `catch`, and `car`. 8 | 9 | ``` 10 | root 11 | | 12 | └── c 13 | | 14 | └── a 15 | | 16 | ├─- t (leaf) 17 | | | 18 | | └── c 19 | | | 20 | | └── h (leaf) 21 | | 22 | └── r (leaf) 23 | ``` 24 | 25 | ## Complexity 26 | 27 | Access Search Insertion Deletion 28 | O(k) O(k) O(k) O(k) 29 | 30 | where k is the word length. 31 | 32 | ### Usage 33 | 34 | Running node on the `index.js` file located in the root of the repository will be your interactive test (in addition to the tests spec'd out in the `tests` directory). 35 | 36 | ```javascript 37 | let trie = new Trie(); 38 | trie.add('cat'); 39 | trie.add('dog'); 40 | 41 | trie.exists('cat'); // returns true 42 | trie.exists('dog'); // returns true 43 | trie.exists('mouse'); //returns false 44 | ``` 45 | 46 | #### References 47 | 48 | [Wikipedia: Trie](https://en.wikipedia.org/wiki/Trie) 49 | -------------------------------------------------------------------------------- /test/stack.spec.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | 3 | const { expect } = chai; 4 | chai.should(); 5 | 6 | const Stack = require('../stack/stack'); 7 | 8 | describe( 'Stack', () => { 9 | describe( '"push" and "pop" methods', () => { 10 | let stack; 11 | 12 | beforeEach(() => { 13 | stack = new Stack(); 14 | }); 15 | 16 | it( 'should return `null` if the stack is empty', () => { 17 | expect(stack.pop()).to.equal(null); 18 | expect(stack.pop()).to.equal(null); 19 | expect(stack.pop()).to.equal(null); 20 | }); 21 | it( 'should return the last value pushed onto the stack', () => { 22 | stack.push('turtle'); 23 | stack.push('dog'); 24 | stack.push('cat'); 25 | expect(stack.pop()).to.equal('cat'); 26 | }); 27 | it('should remove the last element from the stack when popped', () => { 28 | stack.push('turtle'); 29 | stack.push('dog'); 30 | stack.push('cat'); 31 | stack.pop(); 32 | expect(stack.pop()).to.equal('dog'); 33 | }); 34 | it('should rereturn null when all of the nodes have been popped off the stack', () => { 35 | stack.push('turtle'); 36 | stack.push('dog'); 37 | stack.push('cat'); 38 | stack.pop(); 39 | stack.pop(); 40 | stack.pop(); 41 | expect(stack.pop()).to.equal(null); 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /trie/trie.js: -------------------------------------------------------------------------------- 1 | class TrieNode { 2 | constructor(value) { 3 | this.isWord = false; 4 | this.value = value; 5 | this.children = {}; 6 | } 7 | } 8 | 9 | const isString = ( word ) => { 10 | if ( typeof word !== 'string' ) { 11 | throw new TypeError('Input must be of type string'); 12 | } 13 | }; 14 | 15 | class Trie { 16 | constructor() { 17 | this.words = 0; 18 | this.root = new TrieNode(''); 19 | } 20 | 21 | add(word) { 22 | isString(word); 23 | let currNode = this.root; 24 | 25 | for (let i = 0; i < word.length; i++) { 26 | const letter = word[i]; 27 | let nextNode = currNode.children[letter]; 28 | 29 | if (nextNode === undefined) { 30 | nextNode = new TrieNode(letter); 31 | } 32 | currNode.children[letter] = nextNode; 33 | currNode = nextNode; 34 | 35 | } 36 | if (currNode.isWord === false) { 37 | this.words++; 38 | currNode.isWord = true; 39 | } 40 | } 41 | 42 | exists(word) { 43 | isString(word); 44 | let currNode = this.root; 45 | 46 | for (let i = 0; i < word.length; i++) { 47 | const letter = word[i]; 48 | if (!currNode.children[letter]) { 49 | return false; 50 | } 51 | currNode = currNode.children[letter]; 52 | } 53 | return !!currNode.isWord; 54 | } 55 | } 56 | 57 | module.exports = Trie; 58 | -------------------------------------------------------------------------------- /bitwise/README.md: -------------------------------------------------------------------------------- 1 | # BitWise 2 | 3 | Bitwise AND a & b Returns a one in each bit position for which the corresponding bits of both operands are ones. 4 | Bitwise OR a | b Returns a one in each bit position for which the corresponding bits of either or both operands are ones. 5 | Bitwise XOR a ^ b Returns a one in each bit position for which the corresponding bits of either but not both operands are ones. 6 | Bitwise NOT ~ a Inverts the bits of its operand. 7 | Left shift a << b Shifts a in binary representation b (< 32) bits to the left, shifting in zeroes from the right. 8 | Sign-propagating right shift a >> b Shifts a in binary representation b (< 32) bits to the right, discarding bits shifted off. 9 | Zero-fill right shift a >>> b Shifts a in binary representation b (< 32) bits to the right, discarding bits shifted off, and shifting in zeroes from the left. 10 | 11 | - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#>>>_(Zero-fill_right_shift) 12 | - http://michalbe.blogspot.com.br/2013/03/javascript-less-known-parts-bitwise.html 13 | - http://jsperf.com/bitwise-vs-math-object 14 | - http://united-coders.com/christian-harms/results-for-game-for-forfeits-and-the-winner-is/ 15 | - https://mudcu.be/journal/2011/11/bitwise-gems-and-other-optimizations/ 16 | - https://dreaminginjavascript.wordpress.com/2009/02/09/bitwise-byte-foolish/ 17 | - http://jsperf.com/math-min-max-vs-ternary-vs-if/24 18 | -------------------------------------------------------------------------------- /sorting/bubblesort.js: -------------------------------------------------------------------------------- 1 | const bubbleModule = (() => { 2 | const swap = ( arr, i1, i2) => { 3 | const tmp = arr[i1]; 4 | arr[i1] = arr[i2]; 5 | arr[i2] = tmp; 6 | return arr; 7 | }; 8 | 9 | /* 10 | * Bubble sort works in a nature similar to its name, 11 | * the lesser - or lighter - values 12 | * will 'bubble' to the beginning of the array, 13 | * and the heavier values will 'sink' 14 | * to the bottom. 15 | */ 16 | return { 17 | bubbleSort: ( array ) => { 18 | // create variables for swapping and our while loop condition 19 | let swapped = true; 20 | 21 | // Continue making passes until we have a clean pass with no swaps. 22 | while ( swapped ) { 23 | // init swapped to false at the top of the while loop 24 | swapped = false; 25 | 26 | // loop through our array 27 | for ( let i = 0; i < array.length; i++ ) { 28 | // at each position, compare this element with the previous 29 | // if this one is greater than our previous one swap it and 30 | // flag our conditional to loop through our array again 31 | if ( array[i - 1] > array[i] ) { 32 | // swap the two numbers 33 | swap( array, i - 1, i ); 34 | 35 | // flag our conditional to continue looping 36 | swapped = true; 37 | } 38 | } 39 | } 40 | // return our sorted array 41 | return array; 42 | }, 43 | }; 44 | }); 45 | 46 | module.exports = bubbleModule; 47 | -------------------------------------------------------------------------------- /sorting/insertionsort.js: -------------------------------------------------------------------------------- 1 | const insertionModule = (() => { 2 | // swap method because its used multiple times 3 | const swap = (array, index1, index2) => { 4 | // store a tmp variable at pos index2 5 | const tmp = array[index2]; 6 | 7 | // set value of index2 to our value at index 8 | array[index2] = array[index1]; 9 | 10 | // set our value of index1 to our stored variable 11 | array[index1] = tmp; 12 | }; 13 | 14 | return { 15 | /** 16 | * Over each iteration insertion sort removes one element 17 | * from the input array, finds the location it belongs to 18 | * and inserts it at this point. 19 | * 20 | * @param {Array} a - Unsorted array that will be sorted 21 | * @returns {Array} Sorted array 22 | */ 23 | insertionSort: (a) => { 24 | // Iterate over each element in the array 25 | // for each element we will be finding the 26 | // correct place to put this element 27 | for (let i = 1; i < a.length; i++) { 28 | // init j to i 29 | let j = i; 30 | // while our previous number is greater than 0, 31 | // and the number we're comparing is less than 32 | // our previous number enter our loop 33 | while (j > 0 && (a[j - 1] > a[j])) { 34 | // shift the number down the array and give us a space to insert our current value 35 | swap(a, j, j - 1); 36 | // decrement j to go through our entire array 37 | j--; 38 | } 39 | } 40 | return a; 41 | }, 42 | }; 43 | }); 44 | 45 | module.exports = insertionModule; 46 | -------------------------------------------------------------------------------- /graph-traversing/breadth-first-search.js: -------------------------------------------------------------------------------- 1 | /* 2 | In a breadth first search you will start at the root node. 3 | You will then search all their children nodes moving from left to right. 4 | Once all the children nodes have been searched, the process is repeated 5 | on the level below the root node. 6 | 7 | This process is repeated on each level until you reach the end of the 8 | tree or you reach the node that you were searching for initially. 9 | The image below shows you the order that you will search a tree in a breadth first search. 10 | */ 11 | 12 | const bfs = (start) => { 13 | // initailize the open to be the nodes we are currently exploring 14 | const open = []; 15 | open.push(start); 16 | 17 | // Keep track of the nodes we have already visited, so we don't repeat nodes 18 | const visitedNode = [start]; 19 | const searchPath = []; 20 | 21 | // Keep checking nodes until our open array is empty 22 | while (open.length > 0) { 23 | 24 | // Pull the first item off of our queue 25 | const current = open.shift(); 26 | 27 | // Add the current node to our search path stack 28 | searchPath.push(current.name); 29 | 30 | // Iterate through all of the neighbors of the current node 31 | current.neighbors.forEach((next) => { 32 | // If we haven't already visisted a node, add it to our 33 | // visisted stack, and add it to our open queue 34 | if (visitedNode.indexOf(next) < 0) { 35 | visitedNode.push(next); 36 | open.push(next); 37 | } 38 | }); 39 | } 40 | 41 | // Once we have traversed the whole graph - return the search path 42 | return searchPath; 43 | }; 44 | 45 | module.exports = bfs; 46 | -------------------------------------------------------------------------------- /binary-heap/README.md: -------------------------------------------------------------------------------- 1 | http://eloquentjavascript.net/1st_edition/appendix2.html 2 | # Binary Heap 3 | 4 | A binary heap is a heap data structure that takes the form of a binary tree. Binary heaps are a common way of implementing priority queues. 5 | 6 | A binary heap is defined as a binary tree with two additional constraints. 7 | 8 | Shape property: a binary heap is a complete binary tree; that is, all levels of the tree, except possibly the last one (deepest) are fully filled, and, if the last level of the tree is not complete, the nodes of that level are filled from left to right. 9 | 10 | Heap property: the key stored in each node is either greater than or equal to or less than or equal to the keys in the node's children, according to some total order. 11 | 12 | |____ |Average |Worst Case | 13 | |----------|------------|------------| 14 | |Space |`O(n)` |`O(n)` | 15 | |Search |`O(n)` |`O(n)` | 16 | |Insert |`O(1)` |`O(log n)` | 17 | |Delete |`O(log n)` |`O(log n)` | 18 | |Peek |`O(1)` |`O(1)` | 19 | 20 | ### Pseudocode 21 | 22 | ``` 23 | Max-Heapify (A, i): 24 | left ← 2*i // ← means "assignment" 25 | right ← 2*i + 1 26 | largest ← i 27 | 28 | if left ≤ heap_length[A] and A[left] > A[largest] then: 29 | largest ← left 30 | if right ≤ heap_length[A] and A[right] > A[largest] then: 31 | largest ← right 32 | 33 | if largest ≠ i then: 34 | swap A[i] and A[largest] 35 | Max-Heapify(A, largest) 36 | ``` 37 | 38 | # Additional Resources 39 | 40 | #### Binary Heap - Wikipedia 41 | - Link: [Binary heap - Wikipedia](https://en.wikipedia.org/wiki/Binary_heap) 42 | 43 | #### Binary Heap - Eloquent JavaScript 44 | - Link: [Binary heap - Eloquent JavaScript](http://eloquentjavascript.net/1st_edition/appendix2.html) -------------------------------------------------------------------------------- /linked-list/README.md: -------------------------------------------------------------------------------- 1 | # Linked List - An Abstract Data Type 2 | 3 | ## Linked List Example 4 | 5 | { 6 | value: 'Ready Player One', 7 | next: { 8 | value: '1982', 9 | next: { 10 | value: 'Neuromancer', 11 | next: { 12 | value: 'Snow Crash', 13 | next: null 14 | } 15 | } 16 | } 17 | } 18 | 19 | ## Methods 20 | 21 | ### getHead() 22 | Returns the value of the first node of the list 23 | 24 | linkedListExample.getHead(); // returns a node object... 25 | { 26 | value: 'Ready Player One' 27 | next: { ... } 28 | } 29 | 30 | ### getTail() 31 | Returns the value of the last node of a list. 32 | 33 | linkedListExample.getTail(); // returns a node object... 34 | { 35 | value: 'Snow Crash', 36 | next: null 37 | } 38 | 39 | ### add(Value) 40 | Takes in any data value and adds a new node to the end of a list. Returns the new node that was created. 41 | 42 | linkedListExample.add('The Stranger'); // returns the newly created and appended node... 43 | { 44 | value: 'The Stranger', 45 | next: null 46 | } 47 | 48 | ### get(Number) 49 | Takes in a Number value and searches for the **Nth node** in a list and returns that node 50 | 51 | linkedListExample.get(2); // returns a node object... 52 | { 53 | value: 'Neuromancer', 54 | next: { ... } 55 | } 56 | 57 | ### remove(Number) 58 | Takes in a Number value and searches for the Nth node removes it from the list. Should return `false` if the the position is outside the length of the list. 59 | 60 | linkedListExample.remove(3); 61 | 62 | ### insert(Value, Number) 63 | Inserts the specified element at the specified position in this list. Shifts the element currently at that position (if any) and any subsequent elements to the right. Cannot be used to append a node to the end of a list, if attempted, should return `false`. 64 | -------------------------------------------------------------------------------- /hash-table/hash-table.js: -------------------------------------------------------------------------------- 1 | class Hashtable { 2 | constructor() { 3 | this._storage = []; 4 | this._storageLimit = 8; 5 | } 6 | 7 | insert(key, value) { 8 | const hash = getHash(key, this._storageLimit); 9 | 10 | // check if the hash index exists in HashTables 11 | if ( this._storage[hash] ) { 12 | this._storage[hash] = this._storage[hash]; 13 | } else { 14 | this._storage[hash] = []; 15 | } 16 | 17 | for (let i = 0; i < this._storage[hash].length; i++) { 18 | const tupple = this._storage[hash][i]; 19 | if (tupple[0] === key) { 20 | return 'key already exists; keys must be unique'; 21 | } 22 | } 23 | this._storage[hash].push([key, value]); 24 | return 'inserted'; 25 | } 26 | 27 | retrieve(key) { 28 | const hash = getHash(key, this._storageLimit); 29 | if (!this._storage[hash]) { 30 | return 'key does not exist'; 31 | } 32 | for (let i = 0; i < this._storage[hash].length; i++) { 33 | const tupple = this._storage[hash][i]; 34 | if (tupple[0] === key) { 35 | return tupple; 36 | } 37 | } 38 | } 39 | 40 | remove(key) { 41 | const hash = getHash(key, this._storageLimit); 42 | if (!this._storage[hash]) { 43 | return 'key does not exist'; 44 | } 45 | for (let i = 0; i < this._storage[hash].length; i++) { 46 | if (this._storage[hash][i][0] === key) { 47 | this._storage[hash][i].splice(i, 2); 48 | return `${key} removed`; 49 | } 50 | } 51 | } 52 | } 53 | 54 | // helper function that generates hash 55 | const getHash = (str, max) => { 56 | let hash = 0; 57 | for (let i = 0; i < str.length; i++) { 58 | hash = ( hash << 5 ) + hash + str.charCodeAt(i); 59 | hash &= hash; // 60 | hash = Math.abs(hash); 61 | } 62 | return hash % max; 63 | }; 64 | 65 | module.exports = Hashtable; 66 | // Source: https://medium.com/@jenwong/hash-tables-a-simple-javascript-example-237f92d36459#.khe8iijr8 67 | -------------------------------------------------------------------------------- /complex-array/complex-array.js: -------------------------------------------------------------------------------- 1 | class ComplexArray { 2 | constructor(other, arrayType = Float32Array) { 3 | if (other instanceof ComplexArray) { 4 | // Copy constuctor. 5 | this.ArrayType = other.ArrayType; 6 | this.real = new this.ArrayType(other.real); 7 | this.imag = new this.ArrayType(other.imag); 8 | } else { 9 | this.ArrayType = arrayType; 10 | // other can be either an array or a number. 11 | this.real = new this.ArrayType(other); 12 | this.imag = new this.ArrayType(this.real.length); 13 | } 14 | 15 | this.length = this.real.length; 16 | } 17 | 18 | toString() { 19 | const components = []; 20 | 21 | this.forEach((value, i) => { 22 | components.push( 23 | `(${value.real.toFixed(2)}, ${value.imag.toFixed(2)})`, 24 | ); 25 | }); 26 | 27 | return `[${components.join(', ')}]`; 28 | } 29 | 30 | forEach(iterator) { 31 | const n = this.length; 32 | // For gc efficiency, re-use a single object in the iterator. 33 | const value = Object.seal(Object.defineProperties({}, { 34 | real: { writable: true }, imag: { writable: true }, 35 | })); 36 | 37 | for (let i = 0; i < n; i++) { 38 | value.real = this.real[i]; 39 | value.imag = this.imag[i]; 40 | iterator(value, i, n); 41 | } 42 | } 43 | 44 | // In-place mapper. 45 | map(mapper) { 46 | this.forEach((value, i, n) => { 47 | mapper(value, i, n); 48 | this.real[i] = value.real; 49 | this.imag[i] = value.imag; 50 | }); 51 | 52 | return this; 53 | } 54 | 55 | conjugate() { 56 | return new ComplexArray(this).map((value) => { 57 | value.imag *= -1; 58 | }); 59 | } 60 | 61 | magnitude() { 62 | const mags = new this.ArrayType(this.length); 63 | 64 | this.forEach((value, i) => { 65 | mags[i] = Math.sqrt(value.real * value.real + value.imag * value.imag); 66 | }); 67 | 68 | return mags; 69 | } 70 | } 71 | 72 | module.exports = ComplexArray; 73 | -------------------------------------------------------------------------------- /sorting/mergesort.js: -------------------------------------------------------------------------------- 1 | const mergeModule = ( () => { 2 | // used to merge all of our pieces back together after recursively separating the array 3 | const merge = ( left, right ) => { 4 | // initialize array to return 5 | const result = []; 6 | 7 | // if both of our split arrays have items inside go through this while loop 8 | while ( left.length > 0 && right.length > 0 ) { 9 | // compare the first element of each array 10 | if ( left[0] <= right[0] ) { 11 | // if the left element is smaller, push it 12 | // to our return array 13 | result.push( left.shift() ); 14 | } else { 15 | // if the right element is smaller, push it 16 | // to our return array 17 | result.push( right.shift() ); 18 | } 19 | } 20 | 21 | // if only our left array has an element left, push that 22 | while ( left.length > 0 ) { 23 | result.push( left.shift() ); 24 | } 25 | 26 | // if only our right array has an element left, push that 27 | while ( right.length > 0 ) { 28 | result.push( right.shift() ); 29 | } 30 | 31 | // return the sorted array 32 | return result; 33 | }; 34 | 35 | return { 36 | mergeSort( arr ) { 37 | // Base Case - if the array is length 0 or 1, 38 | // then we can assume it is already sorted and return it 39 | if (arr.length < 2) { 40 | return arr; 41 | } 42 | 43 | // pick a pivot at our the middle of our arr 44 | const pivot = ( Math.floor(arr.length / 2) ); 45 | 46 | // separate the arr into two places, everything before it 47 | const pLeft = arr.slice( 0, pivot ); 48 | 49 | // and everything after 50 | const pRight = arr.slice( pivot, arr.length ); 51 | 52 | // call our mergeSort recursively on this array, 53 | // splitting it further and further until it hits 54 | // our base case and the array is split into lengths less than 2 55 | return merge( this.mergeSort( pLeft ), this.mergeSort( pRight )); 56 | }, 57 | }; 58 | }); 59 | 60 | module.exports = mergeModule; 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | npm-debug.log* 4 | yarn-debug.log* 5 | yarn-error.log* 6 | 7 | # Coverage directory used by tools like istanbul 8 | coverage/ 9 | .nyc_output/ 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # Bower dependency directory (https://bower.io/) 30 | bower_components 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (https://nodejs.org/api/addons.html) 36 | build/Release 37 | 38 | # Dependency directories 39 | jspm_packages/ 40 | 41 | # TypeScript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # Yarn Integrity file 57 | .yarn-integrity 58 | 59 | # dotenv environment variables file 60 | .env 61 | .env.test 62 | .env.local 63 | .env.production 64 | 65 | # parcel-bundler cache (https://parceljs.org/) 66 | .cache 67 | .parcel-cache 68 | 69 | # next.js build output 70 | .next 71 | 72 | # nuxt.js build output 73 | .nuxt 74 | 75 | # vuepress build output 76 | .vuepress/dist 77 | 78 | # Serverless directories 79 | .serverless 80 | 81 | # FuseBox cache 82 | .fusebox/ 83 | 84 | # DynamoDB Local files 85 | .dynamodb/ 86 | 87 | # TernJS port file 88 | .tern-port 89 | 90 | # Stores VSCode versions used for testing VSCode extensions 91 | .vscode-test 92 | 93 | # IDE files 94 | .vscode/ 95 | .idea/ 96 | *.swp 97 | *.swo 98 | *~ 99 | 100 | # OS generated files 101 | .DS_Store 102 | .DS_Store? 103 | ._* 104 | .Spotlight-V100 105 | .Trashes 106 | ehthumbs.db 107 | Thumbs.db 108 | 109 | # Logs 110 | logs 111 | *.log 112 | 113 | # Temporary folders 114 | tmp/ 115 | temp/ -------------------------------------------------------------------------------- /search/README.md: -------------------------------------------------------------------------------- 1 | # Search 2 | 3 | ## Binary Search Tree 4 | 5 | A binary search tree (BST), sometimes also called an ordered or sorted binary tree, is a node-based binary tree data structure where each node has a comparable key (and an associated value) and satisfies the restriction that the key in any node is larger than the keys in all nodes in that node's left subtree and smaller than the keys in all nodes in that node's right sub-tree. Each node has no more than two child nodes. Each child must either be a leaf node or the root of another binary search tree. The left sub-tree contains only nodes with keys less than the parent node; the right sub-tree contains only nodes with keys greater than the parent node. BSTs are also dynamic data structures, and the size of a BST is only limited by the amount of free memory in the operating system. The main advantage of binary search trees is that it remains ordered, which provides quicker search times than many other data structures. 6 | 7 | !(A binary search tree of size 9 and depth 3, with 8 at the root. The leaves are not drawn)[https://upload.wikimedia.org/wikipedia/commons/thumb/d/da/Binary_search_tree.svg/200px-Binary_search_tree.svg.png] 8 | 9 | ## Time Complexity 10 | 11 | | |Average |Worst case| 12 | |--------|----------|----------| 13 | |Space |Θ(n) |O(n) | 14 | |Search |Θ(log n) |O(n) | 15 | |Insert |Θ(log n) |O(n) | 16 | |Delete |Θ(log n) |O(n) | 17 | 18 | 19 | ## Linear Search 20 | 21 | Linear search or sequential search is a method for finding a target value within a list. It sequentially checks each element of the list for the target value until a match is found or until all the elements have been searched. 22 | 23 | Linear search runs in at worst linear time and makes at most n comparisons, where n is the length of the list. If each element is equally likely to be searched, then linear search has an average case of `n/2` comparisons, but the average case can be affected if the search probabilities for each element vary. Linear search is rarely practical because other search algorithms and schemes, such as the binary search algorithm and hash tables, allow significantly faster searching for all but short lists. -------------------------------------------------------------------------------- /rabin-karp/rk.js: -------------------------------------------------------------------------------- 1 | const primeBase = 101; // Any large prime number 2 | 3 | // Rolling Hash Function using The Rabin fingerprint method 4 | // I.E - The hash of the first substring, "abr", using 101 as a base is: 5 | // 6 | // ASCII a = 97, b = 98, r = 114. 7 | // hash("abr") = (97 × 1012) + (98 × 1011) + (114 × 1010) = 999,509 8 | const hashFromTo = (str, from, to) => { 9 | let hash = 0; // init hash 10 | for (let i = from; i < to && i < str.length; i++) { 11 | const charASCII = str.charCodeAt(i); 12 | hash = primeBase * hash + charASCII; 13 | } 14 | return hash; 15 | }; 16 | 17 | const matchesAtIndex = (index, text, str) => { 18 | let matches = true; 19 | 20 | for (let j = 0; j < str.length; j++) { 21 | if (text[index + j] !== str[j]) { 22 | matches = false; 23 | break; 24 | } 25 | } 26 | return matches; 27 | }; 28 | 29 | /** 30 | * Returns an array of indexes of the places where the str is found within text 31 | * @param {string} text - Text to search in 32 | * @param {string} str - String to search for 33 | * @returns {Array} Array of indexes where str is found 34 | */ 35 | const searchRabinKarp = (text, str) => { 36 | const matches = []; 37 | 38 | const hashStr = hashFromTo(str, 0, str.length); // hash the substring 39 | const primeToPower = primeBase ** str.length; 40 | const maxIndexForPotentialMatch = text.length - str.length; 41 | // init text has of the length of the substring 42 | let hashTextPart = hashFromTo(text, 0, str.length); 43 | 44 | for (let i = 0; i <= maxIndexForPotentialMatch; i++) { 45 | if (hashTextPart === hashStr) { 46 | // if there is a hash match - we need to verify this is 47 | // a valid match by manually checking it 48 | if (matchesAtIndex(i, text, str)) { 49 | matches.push(i); 50 | } 51 | } 52 | // Update rolling hash 53 | // s[i+1..i+m] = s[i..i+m-1] - s[i] + s[i+m] 54 | const charASCII = text.charCodeAt(i + str.length); 55 | hashTextPart = primeBase * hashTextPart - primeToPower * text.charCodeAt(i) + charASCII; 56 | } 57 | 58 | return matches; 59 | }; 60 | 61 | const str = 'abaacadabra'; 62 | const subStr = 'abaaa'; 63 | const hashStr = searchRabinKarp(str, subStr); 64 | console.log(hashStr); 65 | 66 | module.exports = searchRabinKarp; 67 | -------------------------------------------------------------------------------- /queue/README.md: -------------------------------------------------------------------------------- 1 | # Queues 2 | 3 | # Queue (abstract data type) 4 | 5 | A queue is a particular kind of abstract data type or collection in which the entities in the collection are kept in order and the principal (or only) operations on the collection are the addition of entities to the rear terminal position, known as enqueue, and removal of entities from the front terminal position, known as dequeue. This makes the queue a First-In-First-Out (FIFO) data structure. In a FIFO data structure, the first element added to the queue will be the first one to be removed. 6 | 7 | ![Representation of a FIFO (first in, first out) queue](https://upload.wikimedia.org/wikipedia/commons/5/52/Data_Queue.svg) 8 | 9 | ### A simple queue implemented in Ruby: 10 | 11 | ```ruby 12 | class Queue 13 | def initialize 14 | @list = Array.new 15 | end 16 | 17 | def enqueue(element) 18 | @list << element 19 | end 20 | 21 | def dequeue 22 | @list.shift 23 | end 24 | end 25 | ``` 26 | 27 | ## Priority Queue (abstract data type) 28 | 29 | In computer science, a priority queue is an abstract data type which is like a regular queue or stack data structure, but where additionally each element has a "priority" associated with it. In a priority queue, an element with high priority is served before an element with low priority. If two elements have the same priority, they are served according to their order in the queue. 30 | 31 | While priority queues are often implemented with heaps, they are conceptually distinct from heaps. A priority queue is an abstract concept like "a list" or "a map"; just as a list can be implemented with a linked list or an array, a priority queue can be implemented with a heap or a variety of other methods such as an unordered array. 32 | 33 | One can imagine a priority queue as a modified queue, but when one would get the next element off the queue, the highest-priority element is retrieved first. 34 | 35 | Stacks and queues may be modeled as particular kinds of priority queues. As a reminder, here is how stacks and queues behave: 36 | 37 | * stack – elements are pulled in last-in first-out-order (e.g., a stack of papers) 38 | * queue – elements are pulled in first-in first-out-order (e.g., a line in a cafeteria) 39 | 40 | In a stack, the priority of each inserted element is monotonically increasing; thus, the last element inserted is always the first retrieved. In a queue, the priority of each inserted element is monotonically decreasing; thus, the first element inserted is always the first retrieved. -------------------------------------------------------------------------------- /test/shortest-path.spec.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const Graph = require('../shortest-path/dijkstra'); 3 | const GridGraph = require('../graph/gridGraph'); 4 | const astar = require('../shortest-path/aStar'); 5 | 6 | const { expect } = chai; 7 | 8 | describe( 'Shortest Path', () => { 9 | describe( 'Dijkstra', () => { 10 | describe( '`shortestPath` method', () => { 11 | let g; 12 | 13 | beforeEach(() => { 14 | g = new Graph(); 15 | 16 | g.addVertex('A', { 17 | B: 7, 18 | C: 8, 19 | }); 20 | g.addVertex('B', { 21 | A: 7, 22 | F: 2, 23 | }); 24 | g.addVertex('C', { 25 | A: 8, 26 | F: 6, 27 | G: 4, 28 | }); 29 | g.addVertex('D', { 30 | F: 8, 31 | }); 32 | g.addVertex('E', { 33 | H: 1, 34 | }); 35 | g.addVertex('F', { 36 | B: 2, 37 | C: 6, 38 | D: 8, 39 | G: 9, 40 | H: 3, 41 | }); 42 | g.addVertex('G', { 43 | C: 4, 44 | F: 9, 45 | }); 46 | g.addVertex('H', { 47 | E: 1, 48 | F: 3, 49 | }); 50 | }); 51 | 52 | it( 'should return array of the shortest path', () => { 53 | const shortestPath = g.shortestPath('A', 'H') 54 | .concat(['A']) 55 | .reverse(); 56 | 57 | expect(shortestPath).to.deep.equal(['A', 'B', 'F', 'H']); 58 | }); 59 | }); 60 | }); 61 | describe( 'A Star', () => { 62 | const pathToString = (result) => result.map((node) => `(${node.x},${node.y})`).join(''); 63 | 64 | const runSearch = (gridGraph, start, end, options) => { 65 | if (!(gridGraph instanceof GridGraph)) { 66 | gridGraph = new Graph(gridGraph); 67 | } 68 | start = gridGraph.grid[start[0]][start[1]]; 69 | end = gridGraph.grid[end[0]][end[1]]; 70 | const sTime = new Date(); 71 | const result = astar.search(gridGraph, start, end, options); 72 | const eTime = new Date(); 73 | 74 | return { 75 | result, 76 | text: pathToString(result), 77 | time: (eTime - sTime), 78 | }; 79 | }; 80 | 81 | const gridGraph = new GridGraph([ 82 | [1, 1, 0, 1], 83 | [0, 1, 1, 0], 84 | [0, 0, 1, 1], 85 | ]); 86 | // '(0,1)(1,1)(1,2)(2,2)(2,3)' 87 | const result = runSearch(gridGraph, [0, 0], [2, 3]); 88 | 89 | it( 'should return array of the shortest path', () => { 90 | expect(result.text).to.equal('(0,1)(1,1)(1,2)(2,2)(2,3)'); 91 | }); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | ## Code of Conduct 4 | 5 | This project is intended to be a safe, welcoming space for collaboration. All contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct. Thank you for being kind to each other! 6 | 7 | ## Contributions welcome! 8 | 9 | **Before spending lots of time on something, ask for feedback on your idea first!** 10 | 11 | Please search [issues](../../issues/) and [pull requests](../../pulls/) before adding something new! This helps avoid duplicating efforts and conversations. 12 | 13 | This project welcomes any kind of contribution! Here are a few suggestions: 14 | 15 | - **Ideas**: participate in an issue thread or start your own to have your voice heard. 16 | - **Writing**: contribute your expertise in an area by helping expand the included content. 17 | - **Copy editing**: fix typos, clarify language, and generally improve the quality of the content. 18 | - **Formatting**: help keep content easy to read with consistent formatting. 19 | - **Code**: help maintain and improve the project codebase. 20 | 21 | ## Code Style 22 | 23 | [![standard][standard-image]][standard-url] 24 | 25 | This repository uses [`standard`][standard-url] to maintain code style and consistency, and to avoid style arguments. 26 | 27 | [standard-image]: https://cdn.rawgit.com/feross/standard/master/badge.svg 28 | [standard-url]: https://github.com/feross/standard 29 | [semistandard-image]: https://cdn.rawgit.com/flet/semistandard/master/badge.svg 30 | [semistandard-url]: https://github.com/Flet/semistandard 31 | 32 | ## Project Governance 33 | 34 | **This is an [OPEN Open Source Project](http://openopensource.org/).** 35 | 36 | Individuals making significant and valuable contributions are given commit access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project. 37 | 38 | ### Rules 39 | 40 | There are a few basic ground rules for collaborators: 41 | 42 | 1. **No `--force` pushes** or modifying the Git history in any way. 43 | 1. **Non-master branches** ought to be used for ongoing work. 44 | 1. **External API changes and significant modifications** ought to be subject to an **internal pull request** to solicit feedback from other contributors. 45 | 1. Internal pull requests to solicit feedback are *encouraged* for any other non-trivial contribution but left to the discretion of the contributor. 46 | 1. Contributors should attempt to adhere to the prevailing code style. 47 | 48 | ### Releases 49 | 50 | Declaring formal releases remains the prerogative of the project maintainer. 51 | 52 | ### Changes to this arrangement 53 | 54 | This is an experiment and feedback is welcome! This document may also be subject to pull requests or changes by contributors where you believe you have something valuable to add or change. -------------------------------------------------------------------------------- /shortest-path/dijkstra.js: -------------------------------------------------------------------------------- 1 | const PriorityQueue = require('../queue/priority-queue'); 2 | 3 | /** 4 | * Pathfinding starts here 5 | */ 6 | class Graph { 7 | constructor() { 8 | this.INFINITY = 1 / 0; 9 | this.vertices = {}; 10 | } 11 | 12 | addVertex(name, edges) { 13 | this.vertices[name] = edges; 14 | } 15 | 16 | shortestPath(start, finish) { 17 | const nodes = new PriorityQueue(); 18 | const distances = {}; 19 | const previous = {}; 20 | const path = []; 21 | let smallest; 22 | let vertex; 23 | let neighbor; 24 | let alt; 25 | 26 | // Assign to every node a tentative distance value: 27 | // set it to zero for our initial node and to infinity 28 | // for all other nodes. 29 | for (vertex in this.vertices) { 30 | if (vertex === start) { 31 | // Set the initial node as current. 32 | distances[vertex] = 0; 33 | nodes.enqueue(0, vertex); 34 | } else { 35 | // Mark all other nodes unvisited. 36 | // Create a set of all the unvisited nodes 37 | // called the unvisited set. 38 | distances[vertex] = this.INFINITY; 39 | nodes.enqueue(this.INFINITY, vertex); 40 | } 41 | 42 | previous[vertex] = null; 43 | } 44 | 45 | // Loop through all of the nodes until we run out of nodes, 46 | // or we find the node we were looking for 47 | while (!nodes.isEmpty()) { 48 | smallest = nodes.dequeue(); 49 | 50 | // If the current smallest node is the node we are looking for 51 | if (smallest === finish) { 52 | while (previous[smallest]) { 53 | path.push(smallest); 54 | smallest = previous[smallest]; 55 | } 56 | break; 57 | } 58 | 59 | if (!smallest || distances[smallest] === this.INFINITY) { 60 | continue; 61 | } 62 | 63 | // For the current node, consider all of its unvisited 64 | // neighbors and calculate their tentative distances. 65 | for (neighbor in this.vertices[smallest]) { 66 | 67 | // Compare the newly calculated tentative distance to the 68 | // current assigned value and assign the smaller one. 69 | // For example, if the current node A is marked with a 70 | // distance of 6, and the edge connecting it with a 71 | // neighbor B has length 2, 72 | // then the distance to B (through A) will be 6 + 2 = 8. 73 | // If B was previously marked with a distance greater than 8 then change it to 8. 74 | // Otherwise, keep the current value. 75 | alt = distances[smallest] + this.vertices[smallest][neighbor]; 76 | 77 | // If the newly calculated tentative distance is smaller 78 | // than the currently assigned value then assign the smaller one. 79 | if (alt < distances[neighbor]) { 80 | distances[neighbor] = alt; 81 | previous[neighbor] = smallest; 82 | nodes.enqueue(alt, neighbor); 83 | } 84 | } 85 | } 86 | 87 | return path; 88 | } 89 | } 90 | 91 | // Source: https://github.com/mburst/dijkstras-algorithm 92 | 93 | module.exports = Graph; 94 | -------------------------------------------------------------------------------- /test/hash-table.spec.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const HashTable = require('../hash-table/hash-table'); 3 | 4 | const { expect } = chai; 5 | 6 | describe( 'Hash Table', () => { 7 | let hashTable; 8 | 9 | beforeEach(() => { 10 | hashTable = new HashTable(); 11 | }); 12 | 13 | describe( '`insert` method', () => { 14 | it( 'should return `inserted` if successful', () => { 15 | expect(hashTable.insert('Cat', 'Elvis')).to.equal('inserted'); 16 | expect(hashTable.insert('Cat2', 'BMO')).to.equal('inserted'); 17 | expect(hashTable.insert('Cat4', 'Merrie')).to.equal('inserted'); 18 | expect(hashTable.insert('Dog', 'Rover')).to.equal('inserted'); 19 | }); 20 | it( 'should `key already exists; keys must be unique` if duplicate key', () => { 21 | hashTable.insert('Cat', 'Elvis'); 22 | hashTable.insert('Cat2', 'BMO'); 23 | hashTable.insert('Cat4', 'Merrie'); 24 | hashTable.insert('Dog', 'Rover'); 25 | expect(hashTable.insert('Cat', 'Elvis')).to.equal('key already exists; keys must be unique'); 26 | expect(hashTable.insert('Cat2', 'BMO')).to.equal('key already exists; keys must be unique'); 27 | expect(hashTable.insert('Cat4', 'Merrie')).to.equal('key already exists; keys must be unique'); 28 | expect(hashTable.insert('Dog', 'Rover')).to.equal('key already exists; keys must be unique'); 29 | }); 30 | }); 31 | 32 | describe( '`retrieve` method', () => { 33 | it( 'should return `key does not exist` if the key has not been added', () => { 34 | expect(hashTable.retrieve('Cat')).to.equal('key does not exist'); 35 | expect(hashTable.retrieve('Dog')).to.equal('key does not exist'); 36 | expect(hashTable.retrieve('Human')).to.equal('key does not exist'); 37 | }); 38 | it( 'should return the tupple if successful', () => { 39 | hashTable.insert('Cat', 'Elvis'); 40 | hashTable.insert('Cat2', 'BMO'); 41 | hashTable.insert('Cat4', 'Merrie'); 42 | hashTable.insert('Dog', 'Rover'); 43 | expect(hashTable.retrieve('Cat')).to.deep.equal(['Cat', 'Elvis']); 44 | expect(hashTable.retrieve('Cat2')).to.deep.equal(['Cat2', 'BMO']); 45 | expect(hashTable.retrieve('Cat4')).to.deep.equal(['Cat4', 'Merrie']); 46 | expect(hashTable.retrieve('Dog')).to.deep.equal(['Dog', 'Rover']); 47 | }); 48 | }); 49 | 50 | describe( '`remove` method', () => { 51 | it( 'should return `key does not exist` if the key does not exist', () => { 52 | expect(hashTable.remove('Cat')).to.equal('key does not exist'); 53 | expect(hashTable.remove('Dog')).to.equal('key does not exist'); 54 | expect(hashTable.remove('Human')).to.equal('key does not exist'); 55 | }); 56 | it( 'should return `[key name] removed` if successful', () => { 57 | hashTable.insert('Cat', 'Elvis'); 58 | hashTable.insert('Cat2', 'BMO'); 59 | hashTable.insert('Cat4', 'Merrie'); 60 | hashTable.insert('Dog', 'Rover'); 61 | expect(hashTable.remove('Cat')).to.equal('Cat removed'); 62 | expect(hashTable.remove('Dog')).to.equal('Dog removed'); 63 | expect(hashTable.remove('Cat2')).to.equal('Cat2 removed'); 64 | expect(hashTable.remove('Cat4')).to.equal('Cat4 removed'); 65 | }); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /sorting/quicksort.js: -------------------------------------------------------------------------------- 1 | const quickModule = (() => { 2 | // Private Methods and variables 3 | 4 | // swap method because its used multiple times 5 | const swap = ( array, index1, index2 ) => { 6 | 7 | // store a tmp variable at pos index2 8 | const tmp = array[index2]; 9 | 10 | // set value of index2 to our value at index 11 | array[index2] = array[index1]; 12 | 13 | // Let our value of index1 to our stored letiable 14 | array[index1] = tmp; 15 | }; 16 | 17 | // function for creating our partitions and swapping 18 | const partition = ( arr, pivot, lo, hi ) => { 19 | 20 | // the value of our pivot, where pivot is the index 21 | const pivotValue = arr[pivot]; 22 | 23 | // our new pivot to be, and our comparison 24 | let index = lo; 25 | 26 | // swap our pivot to the end, because we want it in the hi partition 27 | swap(arr, hi, pivot); 28 | 29 | // loop through our array, from our lo value, to our hi value 30 | for ( let i = lo; i < hi; i++ ) { 31 | 32 | // if the value at this position is less than our pivot value, then it needs to be sorted 33 | // to the left 34 | if ( arr[i] < pivotValue ) { 35 | 36 | // swap it and the index, and now that we know it should be sorted 37 | // increment the index because its been sorted 38 | swap( arr, i, index ); 39 | index++; 40 | } 41 | } 42 | 43 | // swap our hi value back with the index value, this is putting our pivot value 44 | // back where it rightfully belongs 45 | swap( arr, index, hi ); 46 | 47 | // return the index for a new pivot in recursively calling quickSort 48 | return index; 49 | }; 50 | 51 | // Public methods 52 | return { 53 | /* Known as partition-exchange sort, quicksort picks a pivot from a partition 54 | * (assuming the first partition is our array). Reorders our array into lower 55 | * higher value partitions, then recursively creates partitions until it cannot. 56 | * takes in the array and optionally low and high parameters for recursion 57 | */ 58 | quickSort( array, low, high ) { 59 | 60 | // reset our pivot for recursive use 61 | let pivot = null; 62 | 63 | // used for initialization, begin on the end 64 | if ( typeof low !== 'number' ) { 65 | low = 0; 66 | } 67 | 68 | // used for initialization, begin on the end 69 | if ( typeof high !== 'number' ) { 70 | high = array.length - 1; 71 | } 72 | 73 | // base case for recursion, if low is >= high, then its already sorted 74 | if ( low < high ) { 75 | 76 | // create a point between our low and high values 77 | pivot = low + ( Math.ceil( ( high - low ) * 0.5) ); 78 | 79 | // create the positions and partitions to be recursively sorted 80 | const nextPivot = partition( array, pivot, low, high ); 81 | 82 | // sort from low, to the pivot - 1, because nextPivot belongs where it is 83 | this.quickSort( array, low, nextPivot - 1 ); 84 | 85 | // sort from pivot + 1 to high 86 | this.quickSort( array, nextPivot + 1, high ); 87 | } 88 | 89 | // return the sorted array 90 | return array; 91 | }, 92 | }; 93 | }); 94 | 95 | module.exports = quickModule; 96 | -------------------------------------------------------------------------------- /test/trie.spec.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | 3 | const { expect } = chai; 4 | chai.should(); 5 | 6 | const Trie = require('../trie/trie'); 7 | 8 | describe( 'Trie', () => { 9 | 10 | describe( '"add" and "exists" behavior', () => { 11 | 12 | let trie; 13 | 14 | beforeEach(() => { 15 | trie = new Trie(); 16 | }); 17 | 18 | it( 'add should only accept strings', () => { 19 | const boolErrorTest = () => trie.add(true); 20 | const numberErrorTest = () => trie.add(10); 21 | const arrayErrorTest = () => trie.add(['bad', 'words']); 22 | const anonObjectErrorTest = () => trie.add({ bad: 'words' }); 23 | const nullErrorTest = () => trie.add(null); 24 | const undefinedErrorTest = () => trie.add(); 25 | expect(boolErrorTest).to.throw(TypeError); 26 | expect(numberErrorTest).to.throw(TypeError); 27 | expect(arrayErrorTest).to.throw(TypeError); 28 | expect(anonObjectErrorTest).to.throw(TypeError); 29 | expect(nullErrorTest).to.throw(TypeError); 30 | expect(undefinedErrorTest).to.throw(TypeError); 31 | }); 32 | 33 | it( 'exists should return "true" if the word has been added', () => { 34 | trie.add('cat'); 35 | trie.exists('cat').should.be.true; 36 | }); 37 | 38 | it( 'exists should return "false" if the word has not been added', () => { 39 | trie.add('cat'); 40 | trie.exists('dog').should.be.false; 41 | }); 42 | 43 | it( 'exists should return "false" if the word is in the trie and not an actual word', () => { 44 | trie.add('cat'); 45 | trie.add('catacombs'); 46 | trie.add('catatonic'); 47 | trie.add('catalyst'); 48 | trie.exists('cat').should.be.true; 49 | trie.exists('catacombs').should.be.true; 50 | trie.exists('catatonic').should.be.true; 51 | trie.exists('catalyst').should.be.true; 52 | trie.exists('c').should.be.false; 53 | trie.exists('ca').should.be.false; 54 | trie.exists('cata').should.be.false; 55 | trie.exists('catat').should.be.false; 56 | trie.exists('catac').should.be.false; 57 | trie.exists('catal').should.be.false; 58 | trie.exists('cataly').should.be.false; 59 | trie.exists('catalys').should.be.false; 60 | trie.exists('catalyts').should.be.false; 61 | }); 62 | 63 | it( 'should not add duplicate data to the trie', () => { 64 | trie.add('cat'); 65 | const snapshot1 = JSON.stringify(trie); 66 | 67 | for (let i = 0, len = 100; i < len; i++) { 68 | trie.add('cat'); 69 | } 70 | const snapshot2 = JSON.stringify(trie); 71 | 72 | snapshot1.should.be.equal(snapshot2); 73 | }); 74 | 75 | it( 'should handle spaces gracefully', () => { 76 | trie.add('cat'); 77 | trie.add('cat'); 78 | trie.add('d o g'); 79 | trie.add(' mouse'); 80 | trie.add(' '); 81 | trie.add(' '); 82 | 83 | trie.exists('cat').should.be.true; 84 | 85 | trie.exists('d o g').should.be.true; 86 | trie.exists('dog').should.be.false; 87 | 88 | trie.exists(' mouse').should.be.true; 89 | trie.exists('mouse').should.be.false; 90 | 91 | trie.exists(' ').should.be.true; 92 | trie.exists(' ').should.be.false; 93 | }); 94 | }); 95 | }); 96 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at joekarlsson1@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /graph/gridGraph.js: -------------------------------------------------------------------------------- 1 | const astar = require('../shortest-path/aStar'); 2 | /** 3 | * A graph memory structure 4 | * @param {Array} gridIn 2D array of input weights 5 | * @param {Object} [options] 6 | * @param {bool} [options.diagonal] Specifies whether diagonal moves are allowed 7 | */ 8 | class GridGraph { 9 | constructor(gridIn, options) { 10 | options = options || {}; 11 | this.nodes = []; 12 | this.diagonal = !!options.diagonal; 13 | this.grid = []; 14 | for (let x = 0; x < gridIn.length; x++) { 15 | this.grid[x] = []; 16 | for (let y = 0, row = gridIn[x]; y < row.length; y++) { 17 | const node = new GridNode(x, y, row[y]); 18 | this.grid[x][y] = node; 19 | this.nodes.push(node); 20 | } 21 | } 22 | this.init(); 23 | } 24 | 25 | init() { 26 | this.dirtyNodes = []; 27 | for (let i = 0; i < this.nodes.length; i++) { 28 | astar.cleanNode(this.nodes[i]); 29 | } 30 | } 31 | 32 | cleanDirty() { 33 | for (let i = 0; i < this.dirtyNodes.length; i++) { 34 | astar.cleanNode(this.dirtyNodes[i]); 35 | } 36 | this.dirtyNodes = []; 37 | } 38 | 39 | markDirty(node) { 40 | this.dirtyNodes.push(node); 41 | } 42 | 43 | neighbors(node) { 44 | const ret = []; 45 | const { x } = node; 46 | const { y } = node; 47 | const { grid } = this; 48 | 49 | // West 50 | if (grid[x - 1] && grid[x - 1][y]) { 51 | ret.push(grid[x - 1][y]); 52 | } 53 | 54 | // East 55 | if (grid[x + 1] && grid[x + 1][y]) { 56 | ret.push(grid[x + 1][y]); 57 | } 58 | 59 | // South 60 | if (grid[x] && grid[x][y - 1]) { 61 | ret.push(grid[x][y - 1]); 62 | } 63 | 64 | // North 65 | if (grid[x] && grid[x][y + 1]) { 66 | ret.push(grid[x][y + 1]); 67 | } 68 | 69 | if (this.diagonal) { 70 | // Southwest 71 | if (grid[x - 1] && grid[x - 1][y - 1]) { 72 | ret.push(grid[x - 1][y - 1]); 73 | } 74 | 75 | // Southeast 76 | if (grid[x + 1] && grid[x + 1][y - 1]) { 77 | ret.push(grid[x + 1][y - 1]); 78 | } 79 | 80 | // Northwest 81 | if (grid[x - 1] && grid[x - 1][y + 1]) { 82 | ret.push(grid[x - 1][y + 1]); 83 | } 84 | 85 | // Northeast 86 | if (grid[x + 1] && grid[x + 1][y + 1]) { 87 | ret.push(grid[x + 1][y + 1]); 88 | } 89 | } 90 | return ret; 91 | } 92 | 93 | toString() { 94 | const graphString = []; 95 | const nodes = this.grid; 96 | for (let x = 0; x < nodes.length; x++) { 97 | const rowDebug = []; 98 | const row = nodes[x]; 99 | for (let y = 0; y < row.length; y++) { 100 | rowDebug.push(row[y].weight); 101 | } 102 | graphString.push(rowDebug.join(' ')); 103 | } 104 | return graphString.join('\n'); 105 | } 106 | 107 | } 108 | 109 | class GridNode { 110 | constructor(x, y, weight) { 111 | this.x = x; 112 | this.y = y; 113 | this.weight = weight; 114 | } 115 | 116 | toString() { 117 | return `[${this.x} ${this.y}]`; 118 | } 119 | 120 | getCost(fromNeighbor) { 121 | // Take diagonal weight into consideration. 122 | if (fromNeighbor && fromNeighbor.x !== this.x && fromNeighbor.y !== this.y) { 123 | return this.weight * 1.41421; 124 | } 125 | return this.weight; 126 | } 127 | 128 | isWall() { 129 | return this.weight === 0; 130 | } 131 | } 132 | 133 | module.exports = GridGraph; 134 | -------------------------------------------------------------------------------- /test/fft-test-helper.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const fftMod = require('../fast-fourier-transforms/fft'); 3 | const ComplexArray = require('../complex-array/complex-array'); 4 | 5 | const { FFT, InvFFT } = fftMod; 6 | const { expect } = chai; 7 | 8 | const EPSILON = 1e-4; 9 | const { PI } = Math; 10 | 11 | /** 12 | * Asserts that two complex arrays are approximately equal 13 | * @param {ComplexArray} first - First complex array 14 | * @param {ComplexArray} second - Second complex array 15 | */ 16 | function assertComplexArraysAlmostEqual(first, second) { 17 | const message = `${second} != ${first}`; 18 | 19 | expect(first.length).to.equal(second.length, message); 20 | 21 | first.forEach((value, i) => { 22 | assertApproximatelyEqual(value.real, second.real[i], message); 23 | assertApproximatelyEqual(value.imag, second.imag[i], message); 24 | }); 25 | } 26 | 27 | /** 28 | * Asserts that FFT result matches expected output 29 | * @param {ComplexArray} original - Original input 30 | * @param {ComplexArray} expected - Expected FFT output 31 | */ 32 | function assertFFTMatches(original, expected) { 33 | if (!(expected instanceof ComplexArray)) { 34 | throw TypeError('expected match should be a ComplexArray'); 35 | } 36 | 37 | const copy = new ComplexArray(original); 38 | const transformed = FFT(original); 39 | assertComplexArraysAlmostEqual(expected, transformed); 40 | assertComplexArraysAlmostEqual(copy, InvFFT(transformed)); 41 | } 42 | 43 | /** 44 | * Asserts that FFT matches DFT for given input 45 | * @param {Array|ComplexArray} input - Input to test 46 | */ 47 | function assertFFTMatchesDFT(input) { 48 | input = new ComplexArray(input); 49 | 50 | assertComplexArraysAlmostEqual(DFT(input), FFT(input)); 51 | } 52 | 53 | /** 54 | * Discrete Fourier Transform implementation for testing 55 | * @param {ComplexArray} input - Input complex array 56 | * @returns {ComplexArray} DFT result 57 | */ 58 | function DFT(input) { 59 | const n = input.length; 60 | const amplitude = 1 / Math.sqrt(n); 61 | 62 | if (!(input instanceof ComplexArray)) { 63 | input = new ComplexArray(input); 64 | } 65 | const output = new ComplexArray(input); 66 | 67 | for (let i = 0; i < n; i++) { 68 | output.real[i] = 0, output.imag[i] = 0; 69 | const phase = { real: 1, imag: 0 }; 70 | const delta = { real: Math.cos(2 * PI * i / n), imag: Math.sin(2 * PI * i / n) }; 71 | 72 | for (let j = 0; j < n; j++) { 73 | output.real[i] += phase.real * input.real[j] - phase.imag * input.imag[j]; 74 | output.imag[i] += phase.real * input.imag[j] + phase.imag * input.real[j]; 75 | [phase.real, phase.imag] = [ 76 | phase.real * delta.real - phase.imag * delta.imag, 77 | phase.real * delta.imag + phase.imag * delta.real, 78 | ]; 79 | } 80 | output.real[i] *= amplitude; 81 | output.imag[i] *= amplitude; 82 | } 83 | 84 | return output; 85 | } 86 | 87 | /** 88 | * Asserts that two numbers are approximately equal within epsilon 89 | * @param {number} first - First number 90 | * @param {number} second - Second number 91 | * @param {string} message - Error message 92 | */ 93 | function assertApproximatelyEqual(first, second, message) { 94 | const delta = Math.abs(first - second); 95 | 96 | expect(delta < EPSILON).to.be.true; 97 | } 98 | 99 | module.exports = { 100 | assertComplexArraysAlmostEqual, 101 | assertFFTMatches, 102 | assertFFTMatchesDFT, 103 | DFT, 104 | }; 105 | -------------------------------------------------------------------------------- /test/sorting.spec.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | 3 | const { expect } = chai; 4 | chai.should(); 5 | 6 | const bubbleModule = require('../sorting/bubblesort.js'); 7 | const bubbleRecursive = require('../sorting/bubble-recursive.js'); 8 | const quickModule = require('../sorting/quicksort.js'); 9 | const quickRecursive = require('../sorting/quick-recursive.js'); 10 | const mergeModule = require('../sorting/mergesort.js'); 11 | const insertionModule = require('../sorting/insertionsort.js'); 12 | const selectionModule = require('../sorting/selectionsort.js'); 13 | 14 | describe('Sorting', () => { 15 | describe('Bubble Sort', () => { 16 | const bubble = bubbleModule(); 17 | const result = bubble.bubbleSort([3, 2, 1]); 18 | 19 | it('should be a function that exists', () => { 20 | expect(bubbleModule).to.be.a('function'); 21 | }); 22 | it('should be a module that exists', () => { 23 | expect(bubble).to.be.a('object'); 24 | }); 25 | it('should be return a sorted array', () => { 26 | expect( result ).to.deep.equal([1, 2, 3]); 27 | }); 28 | }); 29 | 30 | describe('Bubble Sort - Recursive', () => { 31 | const arr = [5, 1, 4, 2, 8, 7, 9, 9, 2, 4, 5, 6]; 32 | 33 | it('should be a function that exists', () => { 34 | expect(bubbleRecursive).to.be.a('function'); 35 | }); 36 | it('should be return a sorted array', () => { 37 | expect( bubbleRecursive( arr ) ).to.deep.equal([1, 2, 2, 4, 4, 5, 5, 6, 7, 8, 9, 9]); 38 | }); 39 | }); 40 | 41 | describe('Quick Sort - Recursive', () => { 42 | const arr = [5, 1, 4, 2, 8, 7, 9, 9, 2, 4, 5, 6]; 43 | 44 | it('should be a module that exists', () => { 45 | expect(quickRecursive).to.be.a('function'); 46 | }); 47 | it('should be return a sorted array', () => { 48 | expect( quickRecursive(arr) ).to.deep.equal([1, 2, 2, 4, 4, 5, 5, 6, 7, 8, 9, 9]); 49 | }); 50 | }); 51 | 52 | describe('Quick Sort', () => { 53 | const arr = [5, 1, 4, 2, 8, 7, 9, 9, 2, 4, 5, 6]; 54 | const quick = quickModule(); 55 | 56 | it('should be a module that exists', () => { 57 | expect(quickModule).to.be.a('function'); 58 | }); 59 | it('should be return a sorted array', () => { 60 | expect( quick.quickSort(arr) ).to.deep.equal([1, 2, 2, 4, 4, 5, 5, 6, 7, 8, 9, 9]); 61 | }); 62 | }); 63 | 64 | describe('Merge Sort', () => { 65 | const arr = [5, 1, 4, 2, 8, 7, 9, 9, 2, 4, 5, 6]; 66 | const merge = mergeModule(); 67 | 68 | it('should be a module that exists', () => { 69 | expect(mergeModule).to.be.a('function'); 70 | }); 71 | it('should be return a sorted array', () => { 72 | expect( merge.mergeSort(arr) ).to.deep.equal([1, 2, 2, 4, 4, 5, 5, 6, 7, 8, 9, 9]); 73 | }); 74 | }); 75 | 76 | describe('Insertion Sort', () => { 77 | const arr = [5, 1, 4, 2, 8, 7, 9, 9, 2, 4, 5, 6]; 78 | const insert = insertionModule(); 79 | 80 | it('should be a module that exists', () => { 81 | expect(insertionModule).to.be.a('function'); 82 | }); 83 | it('should be return a sorted array', () => { 84 | expect( insert.insertionSort( arr ) ).to.deep.equal([1, 2, 2, 4, 4, 5, 5, 6, 7, 8, 9, 9]); 85 | }); 86 | }); 87 | 88 | describe('Selection Sort', () => { 89 | const arr = [5, 1, 4, 2, 8, 7, 9, 9, 2, 4, 5, 6]; 90 | const select = selectionModule(); 91 | 92 | it('should be a module that exists', () => { 93 | expect(selectionModule).to.be.a('function'); 94 | }); 95 | it('should be return a sorted array', () => { 96 | expect( select.selectionSort( arr ) ).to.deep.equal([1, 2, 2, 4, 4, 5, 5, 6, 7, 8, 9, 9]); 97 | }); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /linked-list/linkedList.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @name linkedListGenerator 3 | * @description Main Module 4 | * @return {Object} an object exposing methods to be used to manipulate a linked list 5 | */ 6 | class LinkedList { 7 | constructor() { 8 | this.tail = null; 9 | this.head = null; 10 | } 11 | 12 | // points to our head 13 | getHead() { 14 | return this.head; 15 | } 16 | 17 | // points to our tail 18 | getTail() { 19 | return this.tail; 20 | } 21 | 22 | // Create a new node 23 | newNode(value) { 24 | return { 25 | value, 26 | next: null, 27 | }; 28 | } 29 | 30 | // Takes a new node and adds it to our linked list 31 | add(value) { 32 | const node = this.newNode(value); 33 | 34 | // init empty LL 35 | if (this.getHead() === null) { 36 | this.head = node; 37 | } else { // if it's not empty 38 | this.getTail().next = node; 39 | } 40 | // Happy Path 41 | this.tail = node; 42 | return node; 43 | } 44 | 45 | /** 46 | * Reads through our list and returns the node we are looking for 47 | * @param {number} index - Index of the node to retrieve 48 | * @returns {Object|false} The node at the given index or false if not found 49 | */ 50 | get(index) { 51 | let currNode = this.getHead(); 52 | let postion = 0; 53 | 54 | // If index is less than 0, return false 55 | if (index <= -1) { 56 | return false; 57 | } 58 | 59 | // Loop through all the nodes 60 | while (postion < index) { 61 | 62 | // Check if we hit the end of the LL 63 | if (currNode.next === null) { 64 | return false; 65 | } 66 | 67 | // If node exists go to next node 68 | currNode = currNode.next; 69 | postion++; 70 | } 71 | 72 | return currNode; 73 | } 74 | 75 | /** 76 | * reads through our list and removes desired node 77 | * @param {number} index - Index of the node to remove 78 | * @returns {Object|false} The removed node or false if not found 79 | */ 80 | remove(index) { 81 | const currNode = this.get(index); 82 | const prevNode = this.get(index - 1); 83 | 84 | // If index not in LL, return false 85 | if (currNode === false) { 86 | return false; 87 | } 88 | 89 | // If removing the head, reassign the head to the next node 90 | if (index === 0) { 91 | this.head = currNode.next; 92 | 93 | // If removing the tail, reassign the tail to the prevNode 94 | } else if (currNode.next === null) { 95 | this.tail = prevNode; 96 | prevNode.next = currNode.next; 97 | 98 | // Happy Path 99 | } else { 100 | prevNode.next = currNode.next; 101 | } 102 | } 103 | 104 | /** 105 | * Inserts a new node at the deisred index 106 | * @param {*} value - Value to insert 107 | * @param {number} index - Index where to insert 108 | * @returns {Object|false} The inserted node or false if index is invalid 109 | */ 110 | insert(value, index) { 111 | const currNode = this.get(index); 112 | const prevNode = this.get(index - 1); 113 | const node = this.newNode(value); 114 | 115 | // If the index is not in the LL, return false 116 | if (currNode === false) { 117 | return false; 118 | } 119 | // If inserting at the head, reassign the head to the new node 120 | if (index === 0) { 121 | this.head = node; 122 | node.next = currNode; 123 | } else { 124 | // If inserting at the tail, reassign the tail 125 | if (currNode.next === null) { 126 | this.tail = node; 127 | } 128 | node.next = currNode; 129 | prevNode.next = node; 130 | } 131 | return node; 132 | } 133 | } 134 | 135 | module.exports = LinkedList; 136 | -------------------------------------------------------------------------------- /test/complex-array.spec.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | 3 | const { expect } = chai; 4 | 5 | const ComplexArray = require('../complex-array/complex-array'); 6 | 7 | /** 8 | * Asserts that two arrays are equal 9 | * @param {Array} first - First array to compare 10 | * @param {Array} second - Second array to compare 11 | */ 12 | function assertArrayEquals(first, second) { 13 | const message = `${first} != ${second}`; 14 | 15 | first.forEach((item, i) => { 16 | expect(item).to.equal(second[i], message); 17 | }); 18 | } 19 | 20 | describe('Complex Array', () => { 21 | describe('Consructor', () => { 22 | it('should construct from a number', () => { 23 | const a = new ComplexArray(10); 24 | 25 | expect(a).to.exist; 26 | expect(a.real.length).to.equal(10); 27 | expect(a.imag.length).to.equal(10); 28 | expect(a.real[0]).to.equal(0); 29 | expect(a.imag[0]).to.equal(0); 30 | }); 31 | 32 | it('should construct from a number with a type', () => { 33 | const a = new ComplexArray(10, Int32Array); 34 | 35 | expect(a.ArrayType).to.equal(Int32Array); 36 | expect(a.real.length).to.equal(10); 37 | expect(a.imag.length).to.equal(10); 38 | expect(a.real[0]).to.equal(0); 39 | expect(a.imag[0]).to.equal(0); 40 | }); 41 | 42 | it('should contruct from a real array', () => { 43 | const a = new ComplexArray([1, 2]); 44 | 45 | assertArrayEquals([1, 2], a.real); 46 | assertArrayEquals([0, 0], a.imag); 47 | }); 48 | 49 | it('should contruct from a real array with a type', () => { 50 | const a = new ComplexArray([1, 2], Int32Array); 51 | 52 | expect(a.ArrayType).to.equal(Int32Array); 53 | assertArrayEquals([1, 2], a.real); 54 | assertArrayEquals([0, 0], a.imag); 55 | }); 56 | 57 | it('should contruct from another complex array', () => { 58 | const a = new ComplexArray(new ComplexArray([1, 2])); 59 | 60 | assertArrayEquals([1, 2], a.real); 61 | assertArrayEquals([0, 0], a.imag); 62 | }); 63 | }); 64 | 65 | describe('`map` method', () => { 66 | it('should alter all values', () => { 67 | const a = new ComplexArray([1, 2]).map((value, i) => { 68 | value.real *= 10; 69 | value.imag = i; 70 | }); 71 | 72 | assertArrayEquals([10, 20], a.real); 73 | assertArrayEquals([0, 1], a.imag); 74 | }); 75 | }); 76 | 77 | describe('`forEach` method', () => { 78 | it('should touch every value', () => { 79 | const a = new ComplexArray([1, 2]); 80 | a.imag[0] = 4; 81 | a.imag[1] = 8; 82 | 83 | let sum = 0; 84 | a.forEach((value, i) => { 85 | sum += value.real; 86 | sum += value.imag; 87 | }); 88 | 89 | expect(sum).to.equal(15); 90 | }); 91 | }); 92 | 93 | describe('`conjugate` method', () => { 94 | it('should multiply a number', () => { 95 | const a = new ComplexArray([1, 2]); 96 | a.imag[0] = 1; 97 | a.imag[1] = -2; 98 | 99 | const b = a.conjugate(); 100 | 101 | assertArrayEquals([1, 2], b.real); 102 | assertArrayEquals([-1, 2], b.imag); 103 | }); 104 | }); 105 | 106 | describe('`magnitude` method', () => { 107 | it('should give the an array of magnitudes', () => { 108 | const a = new ComplexArray([1, 3]); 109 | a.imag[0] = 0; 110 | a.imag[1] = 4; 111 | 112 | assertArrayEquals([1, 5], a.magnitude()); 113 | }); 114 | 115 | it('should return an iterable ArrayType object', () => { 116 | const a = new ComplexArray([1, 2]); 117 | 118 | let sum = 0; 119 | a.magnitude().forEach((value, i) => { 120 | sum += value; 121 | }); 122 | 123 | expect(sum).to.equal(3); 124 | }); 125 | }); 126 | 127 | }); 128 | -------------------------------------------------------------------------------- /graph-traversing/README.md: -------------------------------------------------------------------------------- 1 | # Graph Traversing/Searching Algorithms 2 | 3 | ## Breadth-first search (BFS) 4 | 5 | Breadth-first search (BFS) is an algorithm for traversing or searching tree or graph data structures. It starts at the tree root (or some arbitrary node of a graph, sometimes referred to as a 'search key'[1]) and explores the neighbor nodes first, before moving to the next level neighbors. 6 | 7 | |____ |____ | 8 | |-----------------------------|-----------------| 9 | |Class |Search algorithms| 10 | |Data structure |Graph | 11 | |Worst case performance |`O(|E|)=O(b^{d})`| 12 | |Worst case space complexity |`O(|V|)=O(b^{d})`| 13 | 14 | ![Animated example of a breadth-first search](https://upload.wikimedia.org/wikipedia/commons/4/46/Animated_BFS.gif) 15 | 16 | ### Pseudocode 17 | 18 | *Input:* A graph Graph and a starting vertex root of Graph 19 | *Output:* All vertices reachable from root labeled as explored. 20 | A non-recursive implementation of breadth-first search: 21 | 22 | ``` 23 | Breadth-First-Search(Graph, root): 24 | 25 | for each node n in Graph: 26 | n.distance = INFINITY 27 | n.parent = NIL 28 | 29 | create empty queue Q 30 | 31 | root.distance = 0 32 | Q.enqueue(root) 33 | 34 | while Q is not empty: 35 | 36 | current = Q.dequeue() 37 | 38 | for each node n that is adjacent to current: 39 | if n.distance == INFINITY: 40 | n.distance = current.distance + 1 41 | n.parent = current 42 | Q.enqueue(n) 43 | ``` 44 | 45 | ## Depth-first search 46 | 47 | Depth-first search (DFS) is an algorithm for traversing or searching tree or graph data structures. One starts at the root (selecting some arbitrary node as the root in the case of a graph) and explores as far as possible along each branch before backtracking. 48 | 49 | |____ |____ | 50 | |----------------------------|-----------------| 51 | |Class |Search algorithm | 52 | |Data structure |Graph | 53 | |Worst case performance |`O(|E|)` | 54 | |Worst case space complexity |`O(|V|)` | 55 | 56 | ![Order in which the nodes are visited](https://upload.wikimedia.org/wikipedia/commons/1/1f/Depth-first-tree.svg) 57 | 58 | ### Pseudocode 59 | *Input:* A graph G and a vertex v of G 60 | *Output:* All vertices reachable from v labeled as discovered 61 | A recursive implementation of DFS: 62 | 63 | ``` 64 | procedure DFS(G,v): 65 | label v as discovered 66 | for all edges from v to w in G.adjacentEdges(v) do 67 | if vertex w is not labeled as discovered then 68 | recursively call DFS(G,w) 69 | ``` 70 | 71 | # Additional Resources 72 | 73 | #### Graph - Wikipedia 74 | - Link: [Graph (Abstract Data Type) - Wikipedia](https://en.wikipedia.org/wiki/Graph_(abstract_data_type)) 75 | - Concepts: *Graph Node*, *Graph theory*, *search* and *depth first search* 76 | 77 | #### Depth First Search - Wikipedia 78 | - Link: [Depth First Search - Wikipedia](https://en.wikipedia.org/wiki/Depth-first_search) 79 | - Concepts: *Graph Node*, *Graph theory*, *search* and *depth first search* 80 | 81 | #### Breadth First Search - Wikipedia 82 | - Link: [Breadth First Search - Wikipedia](https://en.wikipedia.org/wiki/Breadth-first_search) 83 | - Concepts: *Graph Node*, *Graph theory*, *search* and *breadth first search* 84 | 85 | #### The breadth-first search algorithm - Khan Academy 86 | - Link: [The breadth-first search algorithm - Khan Academy](https://www.khanacademy.org/computing/computer-science/algorithms/breadth-first-search/a/the-breadth-first-search-algorithm) 87 | - Concepts: *Trees*, *Data Structures* 88 | 89 | #### Labyrinth Algorithms DFS and BFS Visulaization 90 | - Link: [Labyrinth Algorithms DFS and BFS Visulaization](http://bryukh.com/labyrinth-algorithms/) 91 | - Concepts: *Graph Node*, *Graph theory*, *search* and *depth first search* 92 | - Notes: Great site that visualizes BFS and DFS -------------------------------------------------------------------------------- /shortest-path/README.md: -------------------------------------------------------------------------------- 1 | # Shortest Path 2 | 3 | ## Dijkstra's 4 | Dijkstra's algorithm is an algorithm for finding the shortest paths between nodes in a graph, which may represent, for example, road networks. It was conceived by computer scientist Edsger W. Dijkstra in 1956 and published three years later 5 | 6 | ![dijkstra_animation](https://cloud.githubusercontent.com/assets/4650739/19616580/b6f12870-97b2-11e6-9bda-a7967f60cf2b.gif) 7 | 8 | ### Alogrithm: 9 | Let the node at which we are starting be called the initial node. Let the distance of node Y be the distance from the initial node to Y. Dijkstra's algorithm will assign some initial distance values and will try to improve them step by step. 10 | 11 | 1. Assign to every node a tentative distance value: set it to zero for our initial node and to infinity for all other nodes. 12 | 1. Set the initial node as current. Mark all other nodes unvisited. Create a set of all the unvisited nodes called the unvisited set. 13 | 1. For the current node, consider all of its unvisited neighbors and calculate their tentative distances. Compare the newly calculated tentative distance to the current assigned value and assign the smaller one. For example, if the current node A is marked with a distance of 6, and the edge connecting it with a neighbor B has length 2, then the distance to B (through A) will be 6 + 2 = 8. If B was previously marked with a distance greater than 8 then change it to 8. Otherwise, keep the current value. 14 | 1. When we are done considering all of the neighbors of the current node, mark the current node as visited and remove it from the unvisited set. A visited node will never be checked again. 15 | 1. If the destination node has been marked visited (when planning a route between two specific nodes) or if the smallest tentative distance among the nodes in the unvisited set is infinity (when planning a complete traversal; occurs when there is no connection between the initial node and remaining unvisited nodes), then stop. The algorithm has finished. 16 | 1. Otherwise, select the unvisited node that is marked with the smallest tentative distance, set it as the new "current node", and go back to step 3. 17 | 18 | ### Pseudocode 19 | ``` 20 | function Dijkstra(Graph, source): 21 | create vertex set Q 22 | 23 | for each vertex v in Graph: // Initialization 24 | dist[v] ← INFINITY // Unknown distance from source to v 25 | prev[v] ← UNDEFINED // Previous node in optimal path from source 26 | add v to Q // All nodes initially in Q (unvisited nodes) 27 | 28 | dist[source] ← 0 // Distance from source to source 29 | 30 | while Q is not empty: 31 | u ← vertex in Q with min dist[u] // Source node will be selected first 32 | remove u from Q 33 | 34 | for each neighbor v of u: // where v is still in Q. 35 | alt ← dist[u] + length(u, v) 36 | if alt < dist[v]: // A shorter path to v has been found 37 | dist[v] ← alt 38 | prev[v] ← u 39 | 40 | return dist[], prev[] 41 | ``` 42 | 43 | ## A* 44 | In computer science, A* (pronounced as "A star") is a computer algorithm that is widely used in pathfinding and graph traversal, the process of plotting an efficiently traversable path between multiple points, called nodes. Noted for its performance and accuracy, it enjoys widespread use. However, in practical travel-routing systems, it is generally outperformed by algorithms which can pre-process the graph to attain better performance, although other work has found A* to be superior to other approaches. 45 | 46 | ### Pseudocode 47 | ``` 48 | push startNode onto openList 49 | while(openList is not empty) { 50 | currentNode = find lowest f in openList 51 | if currentNode is final, return the successful path 52 | push currentNode onto closedList and remove from openList 53 | foreach neighbor of currentNode { 54 | if neighbor is not in openList { 55 | save g, h, and f then save the current parent 56 | add neighbor to openList 57 | } 58 | if neighbor is in openList but the current g is better than previous g { 59 | save g and f, then save the current parent 60 | } 61 | } 62 | ``` 63 | -------------------------------------------------------------------------------- /graph/README.md: -------------------------------------------------------------------------------- 1 | # Graph Node 2 | 3 | A graph is a representation of a set of objects where some pairs of objects are connected by links. The interconnected objects are represented by mathematical abstractions called vertices (also called nodes or points), and the links that connect some pairs of vertices are called edges (also called arcs or lines).[1] Typically, a graph is depicted in diagrammatic form as a set of dots for the vertices, joined by lines or curves for the edges. Graphs are one of the objects of study in discrete mathematics. 4 | 5 | - [ ] Skiena Lectures - great intro: 6 | - [ ] [CSE373 2012 - Lecture 11 - Graph Data Structures (video)](https://www.youtube.com/watch?v=OiXxhDrFruw&list=PLOtl7M3yp-DV69F32zdK7YJcNXpTunF2b&index=11) 7 | - [ ] [CSE373 2012 - Lecture 12 - Breadth-First Search (video)](https://www.youtube.com/watch?v=g5vF8jscteo&list=PLOtl7M3yp-DV69F32zdK7YJcNXpTunF2b&index=12) 8 | - [ ] [CSE373 2012 - Lecture 13 - Graph Algorithms (video)](https://www.youtube.com/watch?v=S23W6eTcqdY&list=PLOtl7M3yp-DV69F32zdK7YJcNXpTunF2b&index=13) 9 | - [ ] [CSE373 2012 - Lecture 14 - Graph Algorithms (con't) (video)](https://www.youtube.com/watch?v=WitPBKGV0HY&index=14&list=PLOtl7M3yp-DV69F32zdK7YJcNXpTunF2b) 10 | - [ ] [CSE373 2012 - Lecture 15 - Graph Algorithms (con't 2) (video)](https://www.youtube.com/watch?v=ia1L30l7OIg&index=15&list=PLOtl7M3yp-DV69F32zdK7YJcNXpTunF2b) 11 | - [ ] [CSE373 2012 - Lecture 16 - Graph Algorithms (con't 3) (video)](https://www.youtube.com/watch?v=jgDOQq6iWy8&index=16&list=PLOtl7M3yp-DV69F32zdK7YJcNXpTunF2b) 12 | 13 | - [ ] Graphs (review and more): 14 | 15 | - [ ] [6.006 Single-Source Shortest Paths Problem (video)](https://www.youtube.com/watch?v=Aa2sqUhIn-E&index=15&list=PLUl4u3cNGP61Oq3tWYp6V_F-5jb5L2iHb) 16 | - [ ] [6.006 Dijkstra (video)](https://www.youtube.com/watch?v=2E7MmKv0Y24&index=16&list=PLUl4u3cNGP61Oq3tWYp6V_F-5jb5L2iHb) 17 | - [ ] [6.006 Bellman-Ford (video)](https://www.youtube.com/watch?v=ozsuci5pIso&list=PLUl4u3cNGP61Oq3tWYp6V_F-5jb5L2iHb&index=17) 18 | - [ ] [6.006 Speeding Up Dijkstra (video)](https://www.youtube.com/watch?v=CHvQ3q_gJ7E&list=PLUl4u3cNGP61Oq3tWYp6V_F-5jb5L2iHb&index=18) 19 | - [ ] [Aduni: Graph Algorithms I - Topological Sorting, Minimum Spanning Trees, Prim's Algorithm - Lecture 6 (video)]( https://www.youtube.com/watch?v=i_AQT_XfvD8&index=6&list=PLFDnELG9dpVxQCxuD-9BSy2E7BWY3t5Sm) 20 | - [ ] [Aduni: Graph Algorithms II - DFS, BFS, Kruskal's Algorithm, Union Find Data Structure - Lecture 7 (video)]( https://www.youtube.com/watch?v=ufj5_bppBsA&list=PLFDnELG9dpVxQCxuD-9BSy2E7BWY3t5Sm&index=7) 21 | - [ ] [Aduni: Graph Algorithms III: Shortest Path - Lecture 8 (video)](https://www.youtube.com/watch?v=DiedsPsMKXc&list=PLFDnELG9dpVxQCxuD-9BSy2E7BWY3t5Sm&index=8) 22 | - [ ] [Aduni: Graph Alg. IV: Intro to geometric algorithms - Lecture 9 (video)](https://www.youtube.com/watch?v=XIAQRlNkJAw&list=PLFDnELG9dpVxQCxuD-9BSy2E7BWY3t5Sm&index=9) 23 | - [ ] [CS 61B 2014 (starting at 58:09) (video)](https://youtu.be/dgjX4HdMI-Q?list=PL-XXv-cvA_iAlnI-BQr9hjqADPBtujFJd&t=3489) 24 | - [ ] [CS 61B 2014: Weighted graphs (video)](https://www.youtube.com/watch?v=aJjlQCFwylA&list=PL-XXv-cvA_iAlnI-BQr9hjqADPBtujFJd&index=19) 25 | - [ ] [Greedy Algorithms: Minimum Spanning Tree (video)](https://www.youtube.com/watch?v=tKwnms5iRBU&index=16&list=PLUl4u3cNGP6317WaSNfmCvGym2ucw3oGp) 26 | - [ ] [Strongly Connected Components Kosaraju's Algorithm Graph Algorithm (video)](https://www.youtube.com/watch?v=RpgcYiky7uw) 27 | 28 | - Full Coursera Course: 29 | - [ ] [Algorithms on Graphs (video)](https://www.coursera.org/learn/algorithms-on-graphs/home/welcome) 30 | 31 | - Yegge: If you get a chance, try to study up on fancier algorithms: 32 | - [ ] Dijkstra's algorithm - see above - 6.006 33 | - [ ] A* 34 | - [ ] [A Search Algorithm](https://en.wikipedia.org/wiki/A*_search_algorithm) 35 | - [ ] [A* Pathfinding Tutorial (video)](https://www.youtube.com/watch?v=KNXfSOx4eEE) 36 | - [ ] [A* Pathfinding (E01: algorithm explanation) (video)](https://www.youtube.com/watch?v=-L-WgKMFuhE) 37 | 38 | 39 | #Tree (Graph Theory) 40 | 41 | A tree is an undirected graph in which any two vertices are connected by exactly one path. In other words, any acyclic connected graph is a tree. A forest is a disjoint union of trees. -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['airbnb-base', 'prettier'], 3 | rules: { 4 | 'no-underscore-dangle': 'off', 5 | 'no-console': 0, 6 | 'space-in-parens': 0, 7 | 'no-plusplus': 0, 8 | 'no-use-before-define': 0, 9 | 'padded-blocks': 0, 10 | 'no-param-reassign': 0, 11 | 'consistent-return': 0, 12 | 'no-bitwise': 0, 13 | 'no-shadow': 0, 14 | 'camelcase': 'off', // Allow snake_case for mathematical variables 15 | 'no-continue': 'off', // Allow continue statements 16 | 'no-restricted-syntax': 'off', // Allow for-in loops 17 | 'guard-for-in': 'off', // Allow for-in loops without guards 18 | 'no-unused-expressions': 'off', // Allow unused expressions in tests 19 | 'func-names': 'off', // Allow unnamed functions 20 | 'max-classes-per-file': 'off', // Allow multiple classes per file 21 | 'class-methods-use-this': 'off', // Allow methods that don't use this 22 | 'no-extend-native': 'off', // Allow extending native prototypes 23 | 'no-self-assign': 'off', // Allow self assignment 24 | 'no-mixed-operators': 'off', // Allow mixed operators for bitwise operations 25 | 'no-sequences': 'off', // Allow comma operator 26 | 'no-constant-condition': 'off', // Allow constant conditions 27 | 'no-undef': 'off', // Allow undefined variables (some test files) 28 | 'no-unused-vars': 'off', // Allow unused variables 29 | 'array-callback-return': 'off', // Allow array callbacks without return 30 | 'import/extensions': 'off', // Allow file extensions in imports 31 | 'import/order': 'off', // Allow any import order 32 | 'import/newline-after-import': 'off', // Don't require newlines after imports 33 | 'prefer-destructuring': 'off', // Allow non-destructured assignments 34 | 'arrow-parens': 'off', // Allow arrow functions without parentheses 35 | 'arrow-body-style': 'off', // Allow any arrow function style 36 | 'implicit-arrow-linebreak': 'off', // Allow implicit arrow linebreaks 37 | 'no-else-return': 'off', // Allow else after return 38 | 'no-case-declarations': 'off', // Allow declarations in case blocks 39 | 'no-confusing-arrow': 'off', // Allow confusing arrow functions 40 | 'no-multi-spaces': 'off', // Allow multiple spaces 41 | 'operator-linebreak': 'off', // Allow any operator linebreak 42 | 'no-multi-assign': 'off', // Allow chained assignments 43 | 'no-restricted-properties': 'off', // Allow Math.pow 44 | 'prefer-exponentiation-operator': 'off', // Allow Math.pow 45 | 'comma-dangle': 'off', // Allow trailing commas 46 | 'object-curly-spacing': 'off', // Allow any object spacing 47 | 'space-infix-ops': 'off', // Allow any operator spacing 48 | 'keyword-spacing': 'off', // Allow any keyword spacing 49 | 'indent': 'off', // Allow any indentation 50 | 'quotes': 'off', // Allow any quote style 51 | 'semi': 'off', // Allow any semicolon usage 52 | 'eol-last': 'off', // Don't require newline at end of file 53 | 'no-extra-semi': 'off', // Allow extra semicolons 54 | 'function-paren-newline': 'off', // Allow any function paren newlines 55 | 'comma-spacing': 'off', // Allow any comma spacing 56 | 'array-bracket-spacing': 'off', // Allow any array bracket spacing 57 | 'prefer-template': 'off', // Allow string concatenation 58 | 'no-multiple-empty-lines': 'off', // Allow multiple empty lines 59 | 'one-var': 'off', // Allow multiple variable declarations 60 | 'one-var-declaration-per-line': 'off', // Allow multiple declarations per line 61 | 'nonblock-statement-body-position': 'off', // Allow any statement position 62 | 'curly': 'off', // Allow non-curly if statements 63 | 'jsdoc/require-jsdoc': 'warn', // Require JSDoc for functions 64 | 'jsdoc/require-description': 'warn', // Require description in JSDoc 65 | 'jsdoc/require-param': 'warn', // Require param documentation 66 | 'jsdoc/require-returns': 'warn', // Require return documentation 67 | }, 68 | env: { 69 | browser: false, // browser global variables. 70 | node: true, // Node.js global variables and Node.js-specific rules. 71 | mocha: true, // adds all of the Mocha testing global variables. 72 | es2022: true, // Enable ES2022 features 73 | }, 74 | plugins: ['import', 'jsdoc'], 75 | parserOptions: { 76 | ecmaVersion: 2022, 77 | sourceType: 'script', 78 | }, 79 | }; 80 | -------------------------------------------------------------------------------- /test/fft.spec.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const fftMod = require('../fast-fourier-transforms/fft'); 3 | const testHelper = require('./fft-test-helper'); 4 | 5 | const { ComplexArray } = fftMod; 6 | const { 7 | assertComplexArraysAlmostEqual, 8 | assertFFTMatches, 9 | assertFFTMatchesDFT, 10 | } = testHelper; 11 | const { expect } = chai; 12 | 13 | describe('Fast Fourier Transformations', () => { 14 | describe('`FFT` method', () => { 15 | describe('on N=4 Arrays', () => { 16 | it('should return a single frequency given a constant array', () => { 17 | assertFFTMatches([1, 1, 1, 1], new ComplexArray([2, 0, 0, 0])); 18 | }); 19 | 20 | it('should return flat with a delta function input', () => { 21 | assertFFTMatches([1, 0, 0, 0], new ComplexArray([0.5, 0.5, 0.5, 0.5])); 22 | }); 23 | 24 | it('should return a single high freq', () => { 25 | assertFFTMatches([1, -1, 1, -1], new ComplexArray([0, 0, 2, 0])); 26 | }); 27 | 28 | it('should return a single low freq', () => { 29 | assertFFTMatches([1, 0, -1, -0], new ComplexArray([0, 1, 0, 1])); 30 | }); 31 | 32 | it('should return a high freq and DC', () => { 33 | assertFFTMatches([1, 0, 1, 0], new ComplexArray([1, 0, 1, 0])); 34 | }); 35 | }); 36 | 37 | describe('on N=6 Arrays', () => { 38 | it('should return a single frequency given a constant array', () => { 39 | assertFFTMatches( 40 | [1, 1, 1, 1, 1, 1], 41 | new ComplexArray([Math.sqrt(6), 0, 0, 0, 0, 0]), 42 | ); 43 | }); 44 | 45 | it('should return flat with a delta function input', () => { 46 | const a = 1 / Math.sqrt(6); 47 | 48 | assertFFTMatches( 49 | [1, 0, 0, 0, 0, 0], 50 | new ComplexArray([a, a, a, a, a, a]), 51 | ); 52 | }); 53 | }); 54 | 55 | describe('on N=`prime` Arrays', () => { 56 | it('should match the DFT', () => { 57 | const a = new ComplexArray(13).map((value) => { 58 | value.real = Math.random(); 59 | value.imag = Math.random(); 60 | }); 61 | 62 | assertFFTMatchesDFT(a); 63 | }); 64 | }); 65 | 66 | describe('on N=512 Arrays', () => { 67 | it('should match the DFT', () => { 68 | const a = new ComplexArray(512).map((value) => { 69 | value.real = Math.random(); 70 | value.imag = Math.random(); 71 | }); 72 | 73 | assertFFTMatchesDFT(a); 74 | }); 75 | }); 76 | 77 | describe('on N=900 Arrays', () => { 78 | it('should match the DFT', () => { 79 | const a = new ComplexArray(900).map((value) => { 80 | value.real = Math.random(); 81 | value.imag = Math.random(); 82 | }); 83 | 84 | assertFFTMatchesDFT(a); 85 | }); 86 | }); 87 | }); 88 | 89 | describe('`frequencyMap` method', () => { 90 | it('should not modify the original', () => { 91 | const original = new ComplexArray([1, 2, 3, 4]); 92 | const filtered = original.frequencyMap(() => {}); 93 | 94 | assertComplexArraysAlmostEqual(original, filtered); 95 | }); 96 | 97 | it('should halve the original', () => { 98 | const original = new ComplexArray([1, 2, 3, 4]); 99 | const filtered = original.frequencyMap((value, i) => { 100 | value.real /= 2; 101 | value.imag /= 2; 102 | }); 103 | 104 | assertComplexArraysAlmostEqual(new ComplexArray([0.5, 1, 1.5, 2]), filtered); 105 | }); 106 | 107 | it('should return zeroed ComplexArray', () => { 108 | const original = new ComplexArray([1, 2, 3, 4]); 109 | const filtered = original.frequencyMap((value, i) => { 110 | value.real = value.imag = 0; 111 | }); 112 | 113 | assertComplexArraysAlmostEqual(new ComplexArray([0, 0, 0, 0]), filtered); 114 | }); 115 | 116 | it('should shift the original', () => { 117 | const original = new ComplexArray([1, 2, 3, 4]); 118 | const filtered = original.frequencyMap((value, i) => { 119 | // Multiply by a phase to shift the original. 120 | const phase = { real: i % 2 ? 0 : (1 - i), imag: i % 2 ? (2 - i) : 0 }; 121 | 122 | [value.real, value.imag] = [ 123 | phase.real * value.real - phase.imag * value.imag, 124 | phase.real * value.imag + phase.imag * value.real, 125 | ]; 126 | }); 127 | 128 | assertComplexArraysAlmostEqual(new ComplexArray([4, 1, 2, 3]), filtered); 129 | }); 130 | }); 131 | }); 132 | -------------------------------------------------------------------------------- /binary-heap/binaryHeap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Binary Heap implementation with customizable scoring function 3 | * @class BinaryHeap 4 | * @param {Function} scoreFunction - Function to determine priority of elements 5 | */ 6 | function BinaryHeap(scoreFunction) { 7 | this.content = []; 8 | this.scoreFunction = scoreFunction; 9 | } 10 | 11 | BinaryHeap.prototype = { 12 | /** 13 | * Add an element to the heap 14 | * @param {*} element - The element to add 15 | */ 16 | push(element) { 17 | // Add the new element to the end of the array. 18 | this.content.push(element); 19 | // Allow it to bubble up. 20 | this.bubbleUp(this.content.length - 1); 21 | }, 22 | 23 | /** 24 | * Remove and return the highest priority element 25 | * @returns {*} The highest priority element 26 | */ 27 | pop() { 28 | // Store the first element so we can return it later. 29 | const result = this.content[0]; 30 | // Get the element at the end of the array. 31 | const end = this.content.pop(); 32 | // If there are any elements left, put the end element at the 33 | // start, and let it sink down. 34 | if (this.content.length > 0) { 35 | this.content[0] = end; 36 | this.sinkDown(0); 37 | } 38 | return result; 39 | }, 40 | 41 | remove(node) { 42 | const { length } = this.content; 43 | // To remove a value, we must search through the array to find 44 | // it. 45 | for (let i = 0; i < length; i++) { 46 | if (this.content[i] !== node) continue; 47 | // When it is found, the process seen in 'pop' is repeated 48 | // to fill up the hole. 49 | const end = this.content.pop(); 50 | // If the element we popped was the one we needed to remove, 51 | // we're done. 52 | if (i === length - 1) break; 53 | // Otherwise, we replace the removed element with the popped 54 | // one, and allow it to float up or sink down as appropriate. 55 | this.content[i] = end; 56 | this.bubbleUp(i); 57 | this.sinkDown(i); 58 | break; 59 | } 60 | }, 61 | 62 | size() { 63 | return this.content.length; 64 | }, 65 | 66 | bubbleUp(n) { 67 | // Fetch the element that has to be moved. 68 | const element = this.content[n]; const 69 | score = this.scoreFunction(element); 70 | // When at 0, an element can not go up any further. 71 | while (n > 0) { 72 | // Compute the parent element's index, and fetch it. 73 | const parentN = Math.floor((n + 1) / 2) - 1; 74 | const parent = this.content[parentN]; 75 | // If the parent has a lesser score, things are in order and we 76 | // are done. 77 | if (score >= this.scoreFunction(parent)) break; 78 | 79 | // Otherwise, swap the parent with the current element and 80 | // continue. 81 | this.content[parentN] = element; 82 | this.content[n] = parent; 83 | n = parentN; 84 | } 85 | }, 86 | 87 | sinkDown(n) { 88 | // Look up the target element and its score. 89 | const { length } = this.content; 90 | const element = this.content[n]; 91 | const elemScore = this.scoreFunction(element); 92 | 93 | while (true) { 94 | let child1; 95 | // Compute the indices of the child elements. 96 | const child2N = (n + 1) * 2; const 97 | child1N = child2N - 1; 98 | // This is used to store the new position of the element, 99 | // if any. 100 | let swap = null; 101 | // If the first child exists (is inside the array)... 102 | if (child1N < length) { 103 | // Look it up and compute its score. 104 | child1 = this.content[child1N], 105 | child1Score = this.scoreFunction(child1); 106 | // If the score is less than our element's, we need to swap. 107 | if (child1Score < elemScore) swap = child1N; 108 | } 109 | // Do the same checks for the other child. 110 | if (child2N < length) { 111 | const child2 = this.content[child2N]; 112 | const child2Score = this.scoreFunction(child2); 113 | if (child2Score < (swap == null ? elemScore : child1Score)) swap = child2N; 114 | } 115 | 116 | // No need to swap further, we are done. 117 | if (swap == null) break; 118 | 119 | // Otherwise, swap and continue. 120 | this.content[n] = this.content[swap]; 121 | this.content[swap] = element; 122 | n = swap; 123 | } 124 | }, 125 | }; 126 | 127 | module.exports = BinaryHeap; 128 | 129 | // Test 130 | // const heap = new BinaryHeap((x) => x); 131 | // const data = [10, 3, 4, 8, 2, 9, 7, 1, 2, 6, 5]; 132 | 133 | // data.forEach((el) => { 134 | // heap.push(el) 135 | // }); 136 | 137 | // heap.remove(2); 138 | // while (heap.size() > 0) { 139 | // console.log(heap.pop()); 140 | // }; 141 | -------------------------------------------------------------------------------- /test/graph.spec.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | 3 | const { expect } = chai; 4 | const Node = require('../graph/graphNode'); 5 | 6 | describe('Graphs', () => { 7 | let A; 8 | let B; 9 | let C; 10 | let D; 11 | let E; 12 | let F; 13 | 14 | beforeEach(() => { 15 | A = new Node('A', 'Joe'); 16 | B = new Node('B', 'Jon'); 17 | C = new Node('C', 'Ray'); 18 | D = new Node('D', 'JSON'); 19 | E = new Node('E', 'Marifel'); 20 | F = new Node('F', 'Nigel'); 21 | }); 22 | 23 | it('should be a function that exists', () => { 24 | expect(Node).to.exist; 25 | expect(Node).to.be.a('function'); 26 | }); 27 | 28 | it('Creating a new Node should return an object', () => { 29 | expect(A).to.exist; 30 | expect(A).to.be.an('object'); 31 | }); 32 | 33 | it('Node should have a property `name`', () => { 34 | expect(A.name).to.exist; 35 | expect(B.name).to.exist; 36 | expect(C.name).to.exist; 37 | expect(A.name).to.be.an('String'); 38 | expect(B.name).to.be.an('String'); 39 | expect(C.name).to.be.an('String'); 40 | expect(A.name).to.equal('A'); 41 | expect(B.name).to.equal('B'); 42 | expect(C.name).to.equal('C'); 43 | }); 44 | 45 | it('Node should have a property `value`', () => { 46 | expect(A.value).to.exist; 47 | expect(B.value).to.exist; 48 | expect(C.value).to.exist; 49 | expect(A.value).to.equal('Joe'); 50 | expect(B.value).to.equal('Jon'); 51 | expect(C.value).to.equal('Ray'); 52 | }); 53 | 54 | it('Node should have a property `neighbors`', () => { 55 | expect(A.neighbors).to.exist; 56 | expect(B.neighbors).to.exist; 57 | expect(C.neighbors).to.exist; 58 | expect(D.neighbors).to.exist; 59 | expect(E.neighbors).to.exist; 60 | expect(F.neighbors).to.exist; 61 | }); 62 | 63 | it('Node property `neighbors` should be initialized to an empty array', () => { 64 | expect(A.neighbors).to.be.an('Array'); 65 | expect(B.neighbors).to.be.an('Array'); 66 | expect(C.neighbors).to.be.an('Array'); 67 | expect(A.neighbors).to.deep.equal([]); 68 | expect(B.neighbors).to.deep.equal([]); 69 | expect(C.neighbors).to.deep.equal([]); 70 | }); 71 | 72 | it('Node should have a method `addNeighbors`', () => { 73 | expect(A.addNeighbors()).to.exist; 74 | expect(B.addNeighbors()).to.exist; 75 | expect(C.addNeighbors()).to.exist; 76 | }); 77 | 78 | it('Node method `addNeighbors` should return an array', () => { 79 | expect(A.addNeighbors([])).to.be.an('Array'); 80 | expect(B.addNeighbors([])).to.be.an('Array'); 81 | expect(C.addNeighbors([])).to.be.an('Array'); 82 | }); 83 | 84 | it('Node method `addNeighbors` should return an array of Nodes', () => { 85 | A.addNeighbors([B, C]); 86 | expect(A.neighbors[0].name).to.equal('B'); 87 | expect(A.neighbors[0].value).to.equal('Jon'); 88 | expect(A.neighbors[1].name).to.equal('C'); 89 | expect(A.neighbors[1].value).to.equal('Ray'); 90 | 91 | A.addNeighbors([D, E]); 92 | expect(A.neighbors[0].name).to.equal('B'); 93 | expect(A.neighbors[0].value).to.equal('Jon'); 94 | expect(A.neighbors[1].name).to.equal('C'); 95 | expect(A.neighbors[1].value).to.equal('Ray'); 96 | expect(A.neighbors[2].name).to.equal('D'); 97 | expect(A.neighbors[2].value).to.equal('JSON'); 98 | expect(A.neighbors[3].name).to.equal('E'); 99 | expect(A.neighbors[3].value).to.equal('Marifel'); 100 | }); 101 | 102 | it('Node should have a method `getNeighbors`', () => { 103 | expect(A.getNeighbors()).to.exist; 104 | expect(B.getNeighbors()).to.exist; 105 | expect(C.getNeighbors()).to.exist; 106 | expect(A.getNeighbors()).to.be.an('Array'); 107 | expect(B.getNeighbors()).to.be.an('Array'); 108 | expect(C.getNeighbors()).to.be.an('Array'); 109 | expect(A.getNeighbors()).to.deep.equal([]); 110 | expect(B.getNeighbors()).to.deep.equal([]); 111 | expect(B.getNeighbors()).to.deep.equal([]); 112 | }); 113 | 114 | it('Node `neighbors` should refernce other neighbors', () => { 115 | A.addNeighbors([B, C]); 116 | B.addNeighbors([D, E]); 117 | C.addNeighbors([F]); 118 | expect(A.neighbors[0].name).to.equal('B'); 119 | expect(A.neighbors[0].value).to.equal('Jon'); 120 | expect(A.neighbors[0].neighbors[0].name).to.equal('D'); 121 | expect(A.neighbors[1].name).to.equal('C'); 122 | expect(A.neighbors[1].value).to.equal('Ray'); 123 | expect(A.neighbors[1].neighbors[0].name).to.equal('F'); 124 | expect(B.neighbors[0].name).to.equal('D'); 125 | expect(B.neighbors[0].value).to.equal('JSON'); 126 | expect(B.neighbors[0].neighbors).to.deep.equal([]); 127 | expect(B.neighbors[1].name).to.equal('E'); 128 | expect(B.neighbors[1].value).to.equal('Marifel'); 129 | expect(B.neighbors[1].neighbors).to.deep.equal([]); 130 | }); 131 | }); 132 | -------------------------------------------------------------------------------- /test/graph-traversing.spec.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | 3 | const { expect } = chai; 4 | 5 | const Node = require('../graph/graphNode'); 6 | const breadthFirstSearch = require('../graph-traversing/breadth-first-search'); 7 | const depthFirstSearch = require('../graph-traversing/depth-first-search-imperative'); 8 | const depthFirstSearchRecursive = require('../graph-traversing/depth-first-search-recursive'); 9 | 10 | describe('Graph Traversing', () => { 11 | describe('Depth First Search - Recursive', () => { 12 | let A; 13 | let B; 14 | let C; 15 | let D; 16 | let E; 17 | let F; 18 | 19 | beforeEach(() => { 20 | A = new Node('A', 'Joe'); 21 | B = new Node('B', 'Jon'); 22 | C = new Node('C', 'Ray'); 23 | D = new Node('D', 'JSON'); 24 | E = new Node('E', 'Marifel'); 25 | F = new Node('F', 'Nigel'); 26 | A.addNeighbors([B, C]); 27 | B.addNeighbors([D, E]); 28 | C.addNeighbors([F]); 29 | }); 30 | 31 | it('should be a function that exists', () => { 32 | expect(depthFirstSearchRecursive).to.exist; 33 | expect(depthFirstSearchRecursive).to.be.a('function'); 34 | }); 35 | 36 | it('should return the node with the value of `searchFor` stored in its value property', () => { 37 | expect(depthFirstSearchRecursive(A, 'Joe').value).to.equal('Joe'); 38 | expect(depthFirstSearchRecursive(A, 'JSON').value).to.equal('JSON'); 39 | expect(depthFirstSearchRecursive(A, 'JSON').name).to.equal('D'); 40 | expect(depthFirstSearchRecursive(A, 'Nigel').value).to.equal('Nigel'); 41 | expect(depthFirstSearchRecursive(A, 'Nigel').name).to.equal('F'); 42 | expect(depthFirstSearchRecursive(B, 'Marifel').value).to.equal('Marifel'); 43 | expect(depthFirstSearchRecursive(A, 'Marifel').name).to.equal('E'); 44 | }); 45 | 46 | it('should return false if it cant find the value in the graph', () => { 47 | expect(depthFirstSearchRecursive(F, 'Joe')).to.equal(false); 48 | expect(depthFirstSearchRecursive(E, 'Joe')).to.equal(false); 49 | }); 50 | }); 51 | 52 | describe('Depth First Search - Imperative', () => { 53 | let A; 54 | let B; 55 | let C; 56 | let D; 57 | let E; 58 | let F; 59 | 60 | beforeEach(() => { 61 | A = new Node('A', 'Joe'); 62 | B = new Node('B', 'Jon'); 63 | C = new Node('C', 'Ray'); 64 | D = new Node('D', 'JSON'); 65 | E = new Node('E', 'Marifel'); 66 | F = new Node('F', 'Nigel'); 67 | A.addNeighbors([B, C]); 68 | B.addNeighbors([D, E]); 69 | C.addNeighbors([F]); 70 | }); 71 | 72 | it('should be a function that exists', () => { 73 | expect(depthFirstSearch).to.exist; 74 | expect(depthFirstSearch).to.be.a('function'); 75 | }); 76 | 77 | it('should return the node with the value of `searchFor` stored in its value property', () => { 78 | expect(depthFirstSearch(A, 'Joe').value).to.equal('Joe'); 79 | expect(depthFirstSearch(A, 'JSON').value).to.equal('JSON'); 80 | expect(depthFirstSearch(A, 'JSON').name).to.equal('D'); 81 | expect(depthFirstSearch(A, 'Nigel').value).to.equal('Nigel'); 82 | expect(depthFirstSearch(A, 'Nigel').name).to.equal('F'); 83 | expect(depthFirstSearch(B, 'Marifel').value).to.equal('Marifel'); 84 | expect(depthFirstSearch(A, 'Marifel').name).to.equal('E'); 85 | }); 86 | 87 | it('should return false if it cant find the value in the graph', () => { 88 | expect(depthFirstSearch(F, 'Joe')).to.equal(false); 89 | expect(depthFirstSearch(E, 'Joe')).to.equal(false); 90 | }); 91 | }); 92 | 93 | describe('Breadth First Search - Imperative', () => { 94 | let A; 95 | let B; 96 | let C; 97 | let D; 98 | let E; 99 | let F; 100 | 101 | beforeEach(() => { 102 | A = new Node('A', 'Joe'); 103 | B = new Node('B', 'Jon'); 104 | C = new Node('C', 'Ray'); 105 | D = new Node('D', 'JSON'); 106 | E = new Node('E', 'Marifel'); 107 | F = new Node('F', 'Nigel'); 108 | A.addNeighbors([B, C]); 109 | B.addNeighbors([D, E]); 110 | C.addNeighbors([F]); 111 | }); 112 | 113 | it('should be a function that exists', () => { 114 | expect(breadthFirstSearch).to.exist; 115 | expect(breadthFirstSearch).to.be.a('function'); 116 | }); 117 | 118 | it('should return the traversal path from the starting point all the way to the end', () => { 119 | expect(breadthFirstSearch(A)).to.deep.equal(['A', 'B', 'C', 'D', 'E', 'F']); 120 | expect(breadthFirstSearch(B)).to.deep.equal(['B', 'D', 'E']); 121 | expect(breadthFirstSearch(C)).to.deep.equal(['C', 'F']); 122 | expect(breadthFirstSearch(D)).to.deep.equal(['D']); 123 | expect(breadthFirstSearch(E)).to.deep.equal(['E']); 124 | expect(breadthFirstSearch(F)).to.deep.equal(['F']); 125 | }); 126 | }); 127 | }); 128 | -------------------------------------------------------------------------------- /shortest-path/aStar.js: -------------------------------------------------------------------------------- 1 | const BinaryHeap = require('../binary-heap/binaryHeap'); 2 | 3 | const pathTo = (node) => { 4 | const path = []; 5 | let curr = node; 6 | 7 | while (curr.parent) { 8 | path.unshift(curr); 9 | curr = curr.parent; 10 | } 11 | return path; 12 | }; 13 | 14 | const getHeap = () => new BinaryHeap((node) => node.f); 15 | 16 | const astar = { 17 | /** 18 | * Perform an A* Search on a graph given a start and end node. 19 | * @param {Graph} graph - The graph to search 20 | * @param {GridNode} start - Starting node 21 | * @param {GridNode} end - Target node 22 | * @param {Object} [options] - Search options 23 | * @param {bool} [options.closest] - Specifies whether to return the path to the closest node if the target is unreachable 24 | * @param {Function} [options.heuristic] - Heuristic function (see astar.heuristics) 25 | * @returns {Array} Array of nodes representing the path 26 | */ 27 | search: (graph, start, end, options) => { 28 | graph.cleanDirty(); 29 | options = options || {}; 30 | const heuristic = options.heuristic || astar.heuristics.manhattan; 31 | const closest = options.closest || false; 32 | 33 | const openHeap = getHeap(); 34 | let closestNode = start; // set the start node to be the closest if required 35 | 36 | start.h = heuristic(start, end); 37 | graph.markDirty(start); 38 | 39 | openHeap.push(start); 40 | 41 | while (openHeap.size() > 0) { 42 | // Grab the lowest f(x) to process next. Heap keeps this sorted for us. 43 | const currentNode = openHeap.pop(); 44 | 45 | // End case -- result has been found, return the traced path. 46 | if (currentNode === end) { 47 | return pathTo(currentNode); 48 | } 49 | 50 | // Normal case -- move currentNode from open to closed, process each of its neighbors. 51 | currentNode.closed = true; 52 | 53 | // Find all neighbors for the current node. 54 | const neighbors = graph.neighbors(currentNode); 55 | 56 | for (let i = 0, il = neighbors.length; i < il; ++i) { 57 | const neighbor = neighbors[i]; 58 | 59 | if (neighbor.closed || neighbor.isWall()) { 60 | // Not a valid node to process, skip to next neighbor. 61 | continue; 62 | } 63 | 64 | // The g score is the shortest distance from start to current node. 65 | // We need to check if the path we have arrived at 66 | // this neighbor is the shortest one we have seen yet. 67 | const gScore = currentNode.g + neighbor.getCost(currentNode); 68 | const beenVisited = neighbor.visited; 69 | 70 | if (!beenVisited || gScore < neighbor.g) { 71 | 72 | // Found an optimal (so far) path to this node. 73 | // Take score for node to see how good it is. 74 | neighbor.visited = true; 75 | neighbor.parent = currentNode; 76 | neighbor.h = neighbor.h || heuristic(neighbor, end); 77 | neighbor.g = gScore; 78 | neighbor.f = neighbor.g + neighbor.h; 79 | graph.markDirty(neighbor); 80 | if (closest) { 81 | // If the neighbour is closer than the current closestNode 82 | // or if it's equally close but has a cheaper path than 83 | // the current closest node then it becomes the closest node 84 | if ( 85 | neighbor.h < closestNode.h 86 | || (neighbor.h === closestNode.h && neighbor.g < closestNode.g) 87 | ) { 88 | closestNode = neighbor; 89 | } 90 | } 91 | 92 | if (!beenVisited) { 93 | // Pushing to heap will put it in proper place based on the 'f' value. 94 | openHeap.push(neighbor); 95 | } else { 96 | // Already seen the node, but since it has been 97 | // rescored we need to reorder it in the heap 98 | openHeap.rescoreElement(neighbor); 99 | } 100 | } 101 | } 102 | } 103 | 104 | if (closest) { 105 | return pathTo(closestNode); 106 | } 107 | 108 | // No result was found - empty array signifies failure to find path. 109 | return []; 110 | }, 111 | // See list of heuristics: http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html 112 | heuristics: { 113 | manhattan: (pos0, pos1) => { 114 | const d1 = Math.abs(pos1.x - pos0.x); 115 | const d2 = Math.abs(pos1.y - pos0.y); 116 | return d1 + d2; 117 | }, 118 | diagonal: (pos0, pos1) => { 119 | const D = 1; 120 | const D2 = Math.sqrt(2); 121 | const d1 = Math.abs(pos1.x - pos0.x); 122 | const d2 = Math.abs(pos1.y - pos0.y); 123 | return (D * (d1 + d2)) + ((D2 - (2 * D)) * Math.min(d1, d2)); 124 | }, 125 | }, 126 | cleanNode: (node) => { 127 | node.f = 0; 128 | node.g = 0; 129 | node.h = 0; 130 | node.visited = false; 131 | node.closed = false; 132 | node.parent = null; 133 | }, 134 | }; 135 | 136 | module.exports = astar; 137 | 138 | // http://theory.stanford.edu/~amitp/GameProgramming/ImplementationNotes.html 139 | // http://www.briangrinstead.com/blog/astar-search-algorithm-in-javascript/ 140 | // https://github.com/bgrins/javascript-astar/blob/master/astar.js 141 | // https://en.wikipedia.org/wiki/A*_search_algorithm 142 | -------------------------------------------------------------------------------- /test/queue.spec.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const Queue = require('../queue/queue'); 3 | const PriorityQueue = require('../queue/priority-queue'); 4 | 5 | const { expect } = chai; 6 | 7 | describe( 'Queues and Priority Queues', () => { 8 | describe( 'Queue', () => { 9 | describe( '`enqueue` and `dequeue` methods', () => { 10 | let queue; 11 | 12 | beforeEach(() => { 13 | queue = new Queue(); 14 | }); 15 | 16 | it( 'should return `null` if the queue is empty', () => { 17 | expect(queue.dequeue()).to.equal(null); 18 | expect(queue.dequeue()).to.equal(null); 19 | expect(queue.dequeue()).to.equal(null); 20 | }); 21 | it( 'should return the last value pushed onto the stack', () => { 22 | queue.enqueue('turtle'); 23 | queue.enqueue('dog'); 24 | queue.enqueue('cat'); 25 | expect(queue.dequeue()).to.equal('turtle'); 26 | }); 27 | it('should remove the last element from the stack when popped', () => { 28 | queue.enqueue('turtle'); 29 | queue.enqueue('dog'); 30 | queue.enqueue('cat'); 31 | queue.dequeue(); 32 | expect(queue.dequeue()).to.equal('dog'); 33 | }); 34 | it('should return null when all of the nodes have been dequeued off the queue', () => { 35 | queue.enqueue('turtle'); 36 | queue.enqueue('dog'); 37 | queue.enqueue('cat'); 38 | queue.dequeue(); 39 | queue.dequeue(); 40 | queue.dequeue(); 41 | expect(queue.dequeue()).to.equal(null); 42 | }); 43 | }); 44 | describe( '`isEmpty` method', () => { 45 | let queue; 46 | 47 | beforeEach(() => { 48 | queue = new Queue(); 49 | }); 50 | 51 | it( 'should initially be empty', () => { 52 | expect(queue.isEmpty()).to.equal(true); 53 | }); 54 | 55 | it( 'should return false when an node is added to the queue', () => { 56 | queue.enqueue('turtle'); 57 | expect(queue.isEmpty()).to.equal(false); 58 | }); 59 | }); 60 | }); 61 | 62 | describe( 'Priority Queue', () => { 63 | let priorityQueue; 64 | let priorityQueue2; 65 | 66 | beforeEach(() => { 67 | priorityQueue = new PriorityQueue(); 68 | priorityQueue2 = new PriorityQueue(); 69 | }); 70 | 71 | describe( 'constructor', () => { 72 | it( 'should initially be empty', () => { 73 | expect(priorityQueue).to.deep.equal({ 74 | _nodes: [], 75 | }); 76 | expect(priorityQueue.isEmpty()).to.equal(true); 77 | }); 78 | }); 79 | 80 | describe( '`enqueue` method', () => { 81 | it( 'should be queued in order of priority', () => { 82 | priorityQueue.enqueue(1, 'BMO'); 83 | priorityQueue.enqueue(2, 'Cake'); 84 | priorityQueue.enqueue(3, 'Finn'); 85 | priorityQueue.enqueue(4, 'Jake'); 86 | expect(priorityQueue).to.deep.equal({ 87 | _nodes: [ 88 | { key: 'BMO', priority: 1 }, 89 | { key: 'Cake', priority: 2 }, 90 | { key: 'Finn', priority: 3 }, 91 | { key: 'Jake', priority: 4 }, 92 | ], 93 | }); 94 | priorityQueue2.enqueue(4, 'BMO'); 95 | priorityQueue2.enqueue(3, 'Cake'); 96 | priorityQueue2.enqueue(2, 'Finn'); 97 | priorityQueue2.enqueue(1, 'Jake'); 98 | expect(priorityQueue2).to.deep.equal({ 99 | _nodes: [ 100 | { key: 'Jake', priority: 1 }, 101 | { key: 'Finn', priority: 2 }, 102 | { key: 'Cake', priority: 3 }, 103 | { key: 'BMO', priority: 4 }, 104 | ], 105 | }); 106 | }); 107 | it( 'should be queued in the order they arrive if the prioroty is the same', () => { 108 | priorityQueue.enqueue(1, 'BMO'); 109 | priorityQueue.enqueue(1, 'Cake'); 110 | priorityQueue.enqueue(1, 'Finn'); 111 | priorityQueue.enqueue(1, 'Jake'); 112 | expect(priorityQueue).to.deep.equal({ 113 | _nodes: [ 114 | { key: 'BMO', priority: 1 }, 115 | { key: 'Cake', priority: 1 }, 116 | { key: 'Finn', priority: 1 }, 117 | { key: 'Jake', priority: 1 }, 118 | ], 119 | }); 120 | }); 121 | }); 122 | describe( '`dequeue` method', () => { 123 | it( 'should dequeue the highest priority items first', () => { 124 | priorityQueue.enqueue(4, 'BMO'); 125 | priorityQueue.enqueue(3, 'Cake'); 126 | priorityQueue.enqueue(2, 'Finn'); 127 | priorityQueue.enqueue(1, 'Jake'); 128 | expect(priorityQueue.dequeue()).to.deep.equal('Jake'); 129 | expect(priorityQueue.dequeue()).to.deep.equal('Finn'); 130 | expect(priorityQueue.dequeue()).to.deep.equal('Cake'); 131 | expect(priorityQueue.dequeue()).to.deep.equal('BMO'); 132 | expect(priorityQueue.dequeue()).to.deep.equal(null); 133 | }); 134 | it( 'should return null when the list is empty', () => { 135 | priorityQueue.enqueue(4, 'BMO'); 136 | priorityQueue.enqueue(3, 'Cake'); 137 | priorityQueue.enqueue(2, 'Finn'); 138 | priorityQueue.enqueue(1, 'Jake'); 139 | expect(priorityQueue.dequeue()).to.deep.equal('Jake'); 140 | expect(priorityQueue.dequeue()).to.deep.equal('Finn'); 141 | expect(priorityQueue.dequeue()).to.deep.equal('Cake'); 142 | expect(priorityQueue.dequeue()).to.deep.equal('BMO'); 143 | expect(priorityQueue.dequeue()).to.deep.equal(null); 144 | expect(priorityQueue.dequeue()).to.deep.equal(null); 145 | expect(priorityQueue.dequeue()).to.deep.equal(null); 146 | expect(priorityQueue.dequeue()).to.deep.equal(null); 147 | }); 148 | }); 149 | 150 | }); 151 | }); 152 | -------------------------------------------------------------------------------- /test/bitwise.spec.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const bitwise_basics = require('../bitwise/bitwise-basics'); 3 | const bitwise_rgb_hex_binary = require('../bitwise/bitwise-rgb-hex-binary'); 4 | 5 | const { expect } = chai; 6 | 7 | describe('Bitwise', () => { 8 | describe('Bitwise Basics', () => { 9 | describe('bitwise AND', () => { 10 | it('should return the binary & of two integers', () => { 11 | // 1001 & 1110 = 1000 12 | expect(bitwise_basics.bitwiseAND(9, 14)).to.equal('1000'); 13 | }); 14 | }); 15 | describe('bitwise OR', () => { 16 | it('should return the binary | of two integers', () => { 17 | // 1001 | 1110 = 1111 18 | expect(bitwise_basics.bitwiseOR(9, 14)).to.equal('1111'); 19 | }); 20 | }); 21 | describe('bitwise XOR', () => { 22 | it('should return the binary ^ of two integers', () => { 23 | // 1001 ^ 1110 = 0111 24 | expect(bitwise_basics.bitwiseXOR(9, 14)).to.equal('111'); 25 | }); 26 | }); 27 | describe('bitwise NOT', () => { 28 | it('should return the binary NOT of two integers', () => { 29 | // ~1001 = 0110 30 | expect(bitwise_basics.bitwiseNOT(9)).to.equal('11111111111111111111111111110110'); 31 | }); 32 | }); 33 | describe('bitwise Left Shift', () => { 34 | it('should return the binary left shift of a integer by n bits', () => { 35 | // 1001 << 2 = 100100 36 | expect(bitwise_basics.bitwiseLeftShift(9, 2)).to.equal('100100'); 37 | }); 38 | }); 39 | describe('bitwise Sign Propagating Right Shift', () => { 40 | it('should return the binary right of a integers by n bits', () => { 41 | // 1001 >> 2 = 0010 42 | expect(bitwise_basics.bitwiseSignPropagatingRightShift(9, 2)).to.equal('10'); 43 | }); 44 | }); 45 | describe('bitwise Zero Fill Right Shift', () => { 46 | it('should return the binary right shift of a integer by n bits', () => { 47 | // 1001 << 2 = 100100 48 | expect(bitwise_basics.bitwiseZeroFillRightShift(9, 2)).to.equal('10'); 49 | // 11111111111111111111111111110111 >>> 2 = 1073741821 50 | expect(bitwise_basics.bitwiseZeroFillRightShift(-9, 2)).to.equal('111111111111111111111111111101'); 51 | }); 52 | }); 53 | describe('bitwise is Even', () => { 54 | it('should return a boolean depending on whether n is even', () => { 55 | expect(bitwise_basics.isEven(9)).to.equal(false); 56 | expect(bitwise_basics.isEven(2)).to.equal(true); 57 | }); 58 | }); 59 | describe('bitwise is Odd', () => { 60 | it('should return a boolean depending on whether n is odd', () => { 61 | expect(bitwise_basics.isOdd(9)).to.equal(true); 62 | expect(bitwise_basics.isOdd(2)).to.equal(false); 63 | }); 64 | }); 65 | describe('bitwise dec2bin', () => { 66 | it('should return a binary equivent of n', () => { 67 | expect(bitwise_basics.dec2bin(9)).to.equal('1001'); 68 | expect(bitwise_basics.dec2bin(2)).to.equal('10'); 69 | expect(bitwise_basics.dec2bin(11)).to.equal('1011'); 70 | expect(bitwise_basics.dec2bin(-1)).to.equal('11111111111111111111111111111111'); 71 | }); 72 | }); 73 | describe('bitwise avgInt', () => { 74 | it('should return a the average of two ints', () => { 75 | // a + b / 2 76 | expect(bitwise_basics.avgInt(6, 12)).to.equal(9); 77 | expect(bitwise_basics.avgInt(2, 80)).to.equal(41); 78 | expect(bitwise_basics.avgInt(6, 1233)).to.equal(619); 79 | expect(bitwise_basics.avgInt(345345, 3234)).to.equal(174289); 80 | expect(bitwise_basics.avgInt(12643, 32222)).to.equal(22432); 81 | expect(bitwise_basics.avgInt(34444, 9999)).to.equal(22221); 82 | }); 83 | }); 84 | describe('bitwise plus One Int', () => { 85 | it('should return a int plus 1', () => { 86 | expect(bitwise_basics.plusOneInt(9)).to.equal(10); 87 | expect(bitwise_basics.plusOneInt(2)).to.equal(3); 88 | }); 89 | }); 90 | }); 91 | describe('Bitwise RGB HEX Binary', () => { 92 | describe('RGB', () => { 93 | let RGBarr; 94 | 95 | beforeEach(() => { 96 | RGBarr = [ 97 | [0, 0, 0], // black 98 | [0, 255, 0], // green 99 | [255, 255, 255] // white 100 | ]; 101 | }); 102 | describe('To Bin', () => { 103 | it('should convert a RGB value to Binary', () => { 104 | const bin = []; 105 | // convert some RGB color values to hex and to binary 106 | RGBarr.forEach((rgb) => { 107 | bin.push(bitwise_rgb_hex_binary.RGBToBin(rgb[0], rgb[1], rgb[2])); 108 | }); 109 | // black 110 | expect(bin[0]).to.equal('000000000000000000000000'); 111 | // green 112 | expect(bin[1]).to.equal('000000001111111100000000'); 113 | // white 114 | expect(bin[2]).to.equal('111111111111111111111111'); 115 | 116 | }); 117 | }); 118 | describe('To Hex', () => { 119 | it('should convert a RGB value to Hex', () => { 120 | const bin = []; 121 | // convert some RGB color values to hex and to hex 122 | RGBarr.forEach((rgb) => { 123 | bin.push(bitwise_rgb_hex_binary.RGBToHex(rgb[0], rgb[1], rgb[2])); 124 | }); 125 | // black 126 | expect(bin[0]).to.equal('000000'); 127 | // green 128 | expect(bin[1]).to.equal('00FF00'); 129 | // white 130 | expect(bin[2]).to.equal('FFFFFF'); 131 | }); 132 | }); 133 | }); 134 | describe('Hex', () => { 135 | let HexArr; 136 | 137 | beforeEach(() => { 138 | HexArr = [ 139 | '000000', // black 140 | '00FF00', // green 141 | 'FFFFFF' // white 142 | ]; 143 | }); 144 | describe('To RGB', () => { 145 | it('should convert a HEX value to RGB', () => { 146 | const rgb = []; 147 | // convert a hexidecimal color string to 0..255 R,G,B 148 | HexArr.forEach((hex) => { 149 | rgb.push(bitwise_rgb_hex_binary.hexToRGB(parseInt(hex, 16))); 150 | }); 151 | // black 152 | expect(rgb[0]).to.deep.equal([0, 0, 0]); 153 | // green 154 | expect(rgb[1]).to.deep.equal([0, 255, 0]); 155 | // white 156 | expect(rgb[2]).to.deep.equal([255, 255, 255]); 157 | }); 158 | }); 159 | }); 160 | describe('Binart', () => { 161 | let binArr; 162 | 163 | beforeEach(() => { 164 | binArr = [ 165 | '000000000000000000000000', // black 166 | '000000001111111100000000', // green 167 | '111111111111111111111111' // white 168 | ]; 169 | }); 170 | describe('To RGB', () => { 171 | it('should convert a binary value to RGB', () => { 172 | const rgb = []; 173 | // convert a hexidecimal color string to 0..255 R,G,B 174 | binArr.forEach((bin) => { 175 | rgb.push(bitwise_rgb_hex_binary.binToRGB(bin)); 176 | }); 177 | // black 178 | expect(rgb[0]).to.deep.equal([0, 0, 0]); 179 | // green 180 | expect(rgb[1]).to.deep.equal([0, 255, 0]); 181 | // white 182 | expect(rgb[2]).to.deep.equal([255, 255, 255]); 183 | 184 | }); 185 | }); 186 | }); 187 | }); 188 | }); 189 | -------------------------------------------------------------------------------- /list/list.js: -------------------------------------------------------------------------------- 1 | class Node { 2 | /** 3 | * Creates an instance of Node. 4 | * 5 | * @param {any} value Node's value 6 | * @param {Node} [next] The next Node in the list 7 | */ 8 | constructor(value, next = null) { 9 | this.value = value; 10 | this.next = next; 11 | } 12 | } 13 | 14 | /** 15 | * Javascript list implementation 16 | * 17 | * @export 18 | * @class List 19 | */ 20 | class List { 21 | /** 22 | * Creates an instance of List. 23 | * 24 | * @param {any} initialValue Value to initiate the list with. 25 | */ 26 | constructor(initialValue) { 27 | if (initialValue) { 28 | this.head = new Node(initialValue); 29 | } 30 | } 31 | 32 | /** 33 | * Computes the length of the list 34 | * @returns {number} The length of the list 35 | */ 36 | getLength() { 37 | let current = this.head; 38 | let length = 0; 39 | 40 | while (current) { 41 | current = current.next; 42 | length += 1; 43 | } 44 | 45 | return length; 46 | } 47 | 48 | /** 49 | * Returns the head of the list (the first element) 50 | * @returns {Node} The head node (the first node in the list) 51 | */ 52 | getHead() { 53 | return this.head; 54 | } 55 | 56 | /** 57 | * Returns the tail of the list (the last element) 58 | * @returns {Node} The tail node (the last node in the list) 59 | */ 60 | getTail() { 61 | return this.reduce((_, node) => node, undefined, false); 62 | } 63 | 64 | /** 65 | * @callback reduceCallbackFn 66 | * @param {any} accumulated 67 | * @param {any} current 68 | * @return {any} 69 | */ 70 | 71 | /** 72 | * Reduces the list to a single value 73 | * 74 | * @param {reduceCallbackFN} callbackFn Callback which reduces the list. 75 | * @param {any} [startingValue] Value to initiate the reducing with. 76 | * @param {boolean} [extractValues=true] Decides on what will be passed to the callbackFn, 77 | * either values or whole nodes. 78 | * @returns {any} Reduced value 79 | */ 80 | reduce(callbackFn, startingValue, extractValues = true) { 81 | let currentNode; 82 | let accumulated; 83 | 84 | let extractorFn; 85 | if (extractValues) { 86 | extractorFn = (node) => node.value; 87 | } else { 88 | extractorFn = (node) => node; 89 | } 90 | 91 | if (!this.head) { 92 | return startingValue; 93 | } 94 | 95 | if (startingValue === undefined) { 96 | currentNode = this.head; 97 | accumulated = startingValue; 98 | } else { 99 | currentNode = this.head.next; 100 | accumulated = extractorFn(this.head); 101 | } 102 | 103 | while (currentNode) { 104 | accumulated = callbackFn(accumulated, extractorFn(currentNode)); 105 | currentNode = currentNode.next; 106 | } 107 | 108 | return accumulated; 109 | } 110 | 111 | /** 112 | * @callback valueCallbackFn 113 | * @param {any} value 114 | * @param {number} index 115 | */ 116 | 117 | /** 118 | * Traverses the list and executes the callback function for each element in the list. 119 | * The first argument of the callback function is the node's value, the second one is the index. 120 | * 121 | * @param {valueCallbackFn} callbackFn Function invoked for each element in the list. 122 | */ 123 | forEach(callbackFn) { 124 | let index = 0; 125 | 126 | this.reduce((_, value) => { 127 | callbackFn(value, index); 128 | index += 1; 129 | return null; 130 | }); 131 | } 132 | 133 | /** 134 | * Executes callbackFn on each item from the list and returns the results as another list. 135 | * 136 | * @param {valueCallbackFn} callbackFn Function invoked for each element in the list 137 | * @returns {List} Transformed list 138 | */ 139 | map(callbackFn) { 140 | const outputList = new List(); 141 | 142 | this.forEach((value, index) => { 143 | const transformedValue = callbackFn(value, index); 144 | outputList.pushBack(transformedValue); 145 | }); 146 | 147 | return outputList; 148 | } 149 | 150 | /** 151 | * Retrieves the Node with a given index 152 | * 153 | * @param {number} targetIndex Wanted node's index 154 | * @returns {Node} Found node 155 | */ 156 | get(targetIndex) { 157 | let currentIndex = 0; 158 | let currentNode = this.head; 159 | 160 | if (targetIndex < 0) { 161 | throw new Error('Index exceeds list\'s size'); 162 | } 163 | 164 | while (currentIndex < targetIndex && currentNode) { 165 | if (currentNode.next === null) { 166 | throw new Error('Index exceeds list\'s size'); 167 | } 168 | currentIndex += 1; 169 | currentNode = currentNode.next; 170 | } 171 | 172 | return currentNode; 173 | } 174 | 175 | /** 176 | * Adds a value to the end of the list 177 | * 178 | * @param {any} newValue A value to be added 179 | * @returns {List} This list. Allows for chainability 180 | */ 181 | pushBack(newValue) { 182 | const newNode = new Node(newValue); 183 | const tail = this.getTail(); 184 | 185 | if (tail) { 186 | tail.next = newNode; 187 | } else { 188 | this.head = newNode; 189 | } 190 | 191 | return this; 192 | } 193 | 194 | /** 195 | * Adds a value at a given index 196 | * 197 | * @param {any} value Value to be added. 198 | * @param {number} index Index at which the value should be added. 199 | * @throws {Error} Index must not exceed list size. 200 | * @returns {List} This list. Allows for chainability. 201 | */ 202 | push(value, index) { 203 | if (index === 0) { 204 | // Replace head 205 | this.head = new Node(value, this.head); 206 | } else { 207 | const previousNode = this.get(index - 1); 208 | if (!previousNode) { 209 | throw new Error('Index exceeds list\'s size'); 210 | } 211 | 212 | const nextNode = previousNode.next; 213 | previousNode.next = new Node(value, nextNode); 214 | } 215 | 216 | return this.head; 217 | } 218 | 219 | /** 220 | * Removes nodes with specific values from the list 221 | * 222 | * @param {any} valueToRemove A value to be removed 223 | * @returns {List} This list. Allows for chainability. 224 | */ 225 | remove(valueToRemove) { 226 | if (this.head.next === null) { 227 | this.head = {}; 228 | return this.head; 229 | } 230 | if (this.head.value === valueToRemove) { 231 | this.head = this.head.next; 232 | return this.remove(valueToRemove); 233 | } 234 | 235 | this._removeRecursive(this.head, valueToRemove); 236 | return this; 237 | } 238 | 239 | /** 240 | * Used internally by remove. Replaces the current node with a next one if the value matches. 241 | * 242 | * @private 243 | * @param {Node} previousNode Reference to the previous node. 244 | * @param {any} valueToRemove Value to be removed. 245 | */ 246 | _removeRecursive(previousNode, valueToRemove) { 247 | if (!previousNode.next) { 248 | return; 249 | } 250 | const currentNode = previousNode.next; 251 | 252 | if (currentNode.value === valueToRemove) { 253 | previousNode.next = currentNode.next; 254 | this._removeRecursive(previousNode, valueToRemove); 255 | } else { 256 | this._removeRecursive(currentNode, valueToRemove); 257 | } 258 | } 259 | 260 | /** 261 | * Returns the first node that value matches. 262 | * 263 | * @param {any} value Value to be found 264 | * @returns {Node} Node with that value 265 | */ 266 | find(value) { 267 | const findRecursive = (node, value) => { 268 | if (!node) { 269 | return null; 270 | } if (node.value === value) { 271 | return node; 272 | } 273 | return findRecursive(node.next, value); 274 | }; 275 | 276 | return findRecursive(this.head, value); 277 | } 278 | 279 | /** 280 | * Converts the list to an array 281 | * 282 | * @returns {any[]} Array of values from the list 283 | */ 284 | getValues() { 285 | const valuesArray = []; 286 | this.forEach((value) => valuesArray.push(value)); 287 | return valuesArray; 288 | } 289 | 290 | } 291 | 292 | module.exports = List; 293 | 294 | // Source: https://github.com/Gelio/js-list 295 | -------------------------------------------------------------------------------- /fast-fourier-transforms/fft.js: -------------------------------------------------------------------------------- 1 | const baseComplexArray = require('../complex-array/complex-array'); 2 | 3 | // Math constants and functions we need. 4 | const { PI } = Math; 5 | const { SQRT1_2 } = Math; 6 | 7 | /** 8 | * Performs Fast Fourier Transform on input data 9 | * @param {Array|ComplexArray} input - Input data to transform 10 | * @returns {ComplexArray} Transformed complex array 11 | */ 12 | function FFT(input) { 13 | return ensureComplexArray(input).FFT(); 14 | } 15 | 16 | /** 17 | * Performs Inverse Fast Fourier Transform on input data 18 | * @param {Array|ComplexArray} input - Input data to transform 19 | * @returns {ComplexArray} Inverse transformed complex array 20 | */ 21 | function InvFFT(input) { 22 | return ensureComplexArray(input).InvFFT(); 23 | } 24 | 25 | /** 26 | * Applies frequency domain filtering to input data 27 | * @param {Array|ComplexArray} input - Input data to filter 28 | * @param {Function} filterer - Filter function to apply 29 | * @returns {ComplexArray} Filtered complex array 30 | */ 31 | function frequencyMap(input, filterer) { 32 | return ensureComplexArray(input).frequencyMap(filterer); 33 | } 34 | 35 | class ComplexArray extends baseComplexArray { 36 | /** 37 | * Performs Fast Fourier Transform on this complex array 38 | * @returns {ComplexArray} Transformed complex array 39 | */ 40 | FFT() { 41 | return fft(this, false); 42 | } 43 | 44 | /** 45 | * Performs Inverse Fast Fourier Transform on this complex array 46 | * @returns {ComplexArray} Inverse transformed complex array 47 | */ 48 | InvFFT() { 49 | return fft(this, true); 50 | } 51 | 52 | /** 53 | * Applies a frequency-space filter to input, and returns the real-space filtered input 54 | * @param {Function} filterer - Filter function that accepts freq, i, n and modifies freq.real and freq.imag 55 | * @returns {ComplexArray} Filtered complex array 56 | */ 57 | frequencyMap(filterer) { 58 | return this.FFT().map(filterer).InvFFT(); 59 | } 60 | } 61 | 62 | /** 63 | * Ensures input is a ComplexArray instance 64 | * @param {Array|ComplexArray} input - Input to convert 65 | * @returns {ComplexArray} ComplexArray instance 66 | */ 67 | function ensureComplexArray(input) { 68 | return input instanceof ComplexArray && input || new ComplexArray(input); 69 | } 70 | 71 | /** 72 | * Performs FFT using appropriate algorithm based on input length 73 | * @param {ComplexArray} input - Input complex array 74 | * @param {boolean} inverse - Whether to perform inverse FFT 75 | * @returns {ComplexArray} Transformed complex array 76 | */ 77 | function fft(input, inverse) { 78 | const n = input.length; 79 | 80 | if (n & (n - 1)) { 81 | return FFT_Recursive(input, inverse); 82 | } 83 | return FFT_2_Iterative(input, inverse); 84 | 85 | } 86 | 87 | /** 88 | * Recursive FFT implementation for non-power-of-2 lengths 89 | * @param {ComplexArray} input - Input complex array 90 | * @param {boolean} inverse - Whether to perform inverse FFT 91 | * @returns {ComplexArray} Transformed complex array 92 | */ 93 | function FFT_Recursive(input, inverse) { 94 | const n = input.length; 95 | 96 | if (n === 1) { 97 | return input; 98 | } 99 | 100 | const output = new ComplexArray(n, input.ArrayType); 101 | 102 | // Use the lowest odd factor, so we are able to use FFT_2_Iterative in the 103 | // recursive transforms optimally. 104 | const p = LowestOddFactor(n); 105 | const m = n / p; 106 | const normalisation = 1 / Math.sqrt(p); 107 | let recursive_result = new ComplexArray(m, input.ArrayType); 108 | 109 | // Loops go like O(n Σ p_i), where p_i are the prime factors of n. 110 | // for a power of a prime, p, this reduces to O(n p log_p n) 111 | for (let j = 0; j < p; j++) { 112 | for (let i = 0; i < m; i++) { 113 | recursive_result.real[i] = input.real[i * p + j]; 114 | recursive_result.imag[i] = input.imag[i * p + j]; 115 | } 116 | // Don't go deeper unless necessary to save allocs. 117 | if (m > 1) { 118 | recursive_result = fft(recursive_result, inverse); 119 | } 120 | 121 | const del_f_r = Math.cos(2 * PI * j / n); 122 | const del_f_i = (inverse ? -1 : 1) * Math.sin(2 * PI * j / n); 123 | let f_r = 1; 124 | let f_i = 0; 125 | 126 | for (let i = 0; i < n; i++) { 127 | const _real = recursive_result.real[i % m]; 128 | const _imag = recursive_result.imag[i % m]; 129 | 130 | output.real[i] += f_r * _real - f_i * _imag; 131 | output.imag[i] += f_r * _imag + f_i * _real; 132 | 133 | [f_r, f_i] = [ 134 | f_r * del_f_r - f_i * del_f_i, 135 | f_i = f_r * del_f_i + f_i * del_f_r, 136 | ]; 137 | } 138 | } 139 | 140 | // Copy back to input to match FFT_2_Iterative in-placeness 141 | // TODO: faster way of making this in-place? 142 | for (let i = 0; i < n; i++) { 143 | input.real[i] = normalisation * output.real[i]; 144 | input.imag[i] = normalisation * output.imag[i]; 145 | } 146 | 147 | return input; 148 | } 149 | 150 | /** 151 | * Iterative FFT implementation for power-of-2 lengths 152 | * @param {ComplexArray} input - Input complex array 153 | * @param {boolean} inverse - Whether to perform inverse FFT 154 | * @returns {ComplexArray} Transformed complex array 155 | */ 156 | function FFT_2_Iterative(input, inverse) { 157 | const n = input.length; 158 | 159 | const output = BitReverseComplexArray(input); 160 | const output_r = output.real; 161 | const output_i = output.imag; 162 | // Loops go like O(n log n): 163 | // width ~ log n; i,j ~ n 164 | let width = 1; 165 | while (width < n) { 166 | const del_f_r = Math.cos(PI / width); 167 | const del_f_i = (inverse ? -1 : 1) * Math.sin(PI / width); 168 | for (let i = 0; i < n / (2 * width); i++) { 169 | let f_r = 1; 170 | let f_i = 0; 171 | for (let j = 0; j < width; j++) { 172 | const l_index = 2 * i * width + j; 173 | const r_index = l_index + width; 174 | 175 | const left_r = output_r[l_index]; 176 | const left_i = output_i[l_index]; 177 | const right_r = f_r * output_r[r_index] - f_i * output_i[r_index]; 178 | const right_i = f_i * output_r[r_index] + f_r * output_i[r_index]; 179 | 180 | output_r[l_index] = SQRT1_2 * (left_r + right_r); 181 | output_i[l_index] = SQRT1_2 * (left_i + right_i); 182 | output_r[r_index] = SQRT1_2 * (left_r - right_r); 183 | output_i[r_index] = SQRT1_2 * (left_i - right_i); 184 | 185 | [f_r, f_i] = [ 186 | f_r * del_f_r - f_i * del_f_i, 187 | f_r * del_f_i + f_i * del_f_r, 188 | ]; 189 | } 190 | } 191 | width <<= 1; 192 | } 193 | 194 | return output; 195 | } 196 | 197 | /** 198 | * Reverses the bit order of an index for FFT 199 | * @param {number} index - Index to reverse 200 | * @param {number} n - Length of array 201 | * @returns {number} Bit-reversed index 202 | */ 203 | function BitReverseIndex(index, n) { 204 | let bitreversed_index = 0; 205 | 206 | while (n > 1) { 207 | bitreversed_index <<= 1; 208 | bitreversed_index += index & 1; 209 | index >>= 1; 210 | n >>= 1; 211 | } 212 | return bitreversed_index; 213 | } 214 | 215 | /** 216 | * Reverses the bit order of a complex array for FFT 217 | * @param {ComplexArray} array - Array to reverse 218 | * @returns {ComplexArray} Bit-reversed array 219 | */ 220 | function BitReverseComplexArray(array) { 221 | const n = array.length; 222 | const flips = new Set(); 223 | 224 | for (let i = 0; i < n; i++) { 225 | const r_i = BitReverseIndex(i, n); 226 | 227 | if (flips.has(i)) continue; 228 | 229 | [array.real[i], array.real[r_i]] = [array.real[r_i], array.real[i]]; 230 | [array.imag[i], array.imag[r_i]] = [array.imag[r_i], array.imag[i]]; 231 | 232 | flips.add(r_i); 233 | } 234 | 235 | return array; 236 | } 237 | 238 | /** 239 | * Finds the lowest odd factor of a number 240 | * @param {number} n - Number to factor 241 | * @returns {number} Lowest odd factor 242 | */ 243 | function LowestOddFactor(n) { 244 | const sqrt_n = Math.sqrt(n); 245 | let factor = 3; 246 | 247 | while (factor <= sqrt_n) { 248 | if (n % factor === 0) return factor; 249 | factor += 2; 250 | } 251 | return n; 252 | } 253 | 254 | module.exports = { 255 | FFT, 256 | InvFFT, 257 | frequencyMap, 258 | ComplexArray, 259 | }; 260 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JavaScript Data Structures and Algorithms 2 | 3 | [![Coverage Status][cover]][cover-url] 4 | [![Build Status][tests]][tests-url] 5 | [![Maintainability][maintainability]][maintainability-url] 6 | [![stars][stars]][stars-url] 7 | [![pr][pr]][pr-url] 8 | [![license][license]][license-url] 9 | [![twitter][twitter]][twitter-url] 10 | [![Greenkeeper badge](https://badges.greenkeeper.io/JoeKarlsson/data-structures.svg)](https://greenkeeper.io/) 11 | 12 | I put this repository together to get ready for technical interviews. I hope it helps you get ready for your next big technical interview. If you like what you see plz give it a star. Also, feel free to contribute :D 13 | 14 | ## What is an algorithm? 15 | 16 | In simple terms, it is possible to say that an algorithm is a sequence of steps which allow to solve a certain task ( Yes, not just computers use algorithms, humans also use them). Now, an algorithm should have three important characteristics to be considered valid: 17 | 18 | 1. **It should be finite:** If your algorithm never ends trying to solve the problem it was designed to solve then it is useless 19 | 20 | 1. **It should have well defined instructions:** Each step of the algorithm has to be precisely defined; the instructions should be unambiguously specified for each case. 21 | 22 | 1. **It should be effective:** The algorithm should solve the problem it was designed to solve. And it should be possible to demonstrate that the algorithm converges with just a paper and pencil. 23 | 24 | ## Getting Started 25 | 26 | 1. Run `npm install` 27 | 1. Run `npm test` to run all of the algorithms 28 | 29 | This repo covers the following topics: 30 | 31 | * [Binary Heap](https://github.com/JoeKarlsson/data-structures/tree/master/binary-heap) 32 | * [Bitwise](https://github.com/JoeKarlsson/data-structures/tree/master/bitwise) 33 | * [Complex Arrays](https://github.com/JoeKarlsson/data-structures/tree/master/complex-array) 34 | * [Graph Traversing](https://github.com/JoeKarlsson/data-structures/tree/master/graph-traversing) 35 | * [Breadth First Search](https://github.com/JoeKarlsson/data-structures/blob/master/graph-traversing/breadth-first-search.js) 36 | * [Depth First Search](https://github.com/JoeKarlsson/data-structures/blob/master/graph-traversing/depth-first-search-recursive.js) 37 | * [Graph](https://github.com/JoeKarlsson/data-structures/tree/master/graph) 38 | * [Graph Node](https://github.com/JoeKarlsson/data-structures/blob/master/graph/graphNode.js) 39 | * [Grid Graph](https://github.com/JoeKarlsson/data-structures/blob/master/graph/gridGraph.js) 40 | * [Tree Node](https://github.com/JoeKarlsson/data-structures/blob/master/graph/tree.js) 41 | * [Hash Table](https://github.com/JoeKarlsson/data-structures/tree/master/hash-table) 42 | * [Fast Fourier Transformations](https://github.com/JoeKarlsson/data-structures/tree/master/fast-fourier-transforms) 43 | * [Linked Lists](https://github.com/JoeKarlsson/data-structures/tree/master/linked-list) 44 | * [List](https://github.com/JoeKarlsson/data-structures/tree/master/list) 45 | * [Memoization](https://github.com/JoeKarlsson/data-structures/tree/master/memoization) 46 | * [Queue](https://github.com/JoeKarlsson/data-structures/tree/master/queue) 47 | * [Priority Queue](https://github.com/JoeKarlsson/data-structures/blob/master/queue/priorityQueue.js) 48 | * [Queue](https://github.com/JoeKarlsson/data-structures/blob/master/queue/queue.js) 49 | * [Rabin Karp](https://github.com/JoeKarlsson/data-structures/tree/master/rabin-karp) 50 | * [Search](https://github.com/JoeKarlsson/data-structures/tree/master/search) 51 | * [Binary Search Trees](https://github.com/JoeKarlsson/data-structures/tree/master/search) 52 | * [Linear](https://github.com/JoeKarlsson/data-structures/blob/master/search/linearSearch.js) 53 | * [Shortest Path](https://github.com/JoeKarlsson/data-structures/tree/master/shortest-path) 54 | * [A*](https://github.com/JoeKarlsson/data-structures/blob/master/shortest-path/aStar.js) 55 | * [Dijkstra](https://github.com/JoeKarlsson/data-structures/blob/master/shortest-path/dijkstra.js) 56 | * [Sorting](https://github.com/JoeKarlsson/data-structures/tree/master/sorting-algorithms) 57 | * [Bubble Sort](https://github.com/JoeKarlsson/data-structures/blob/master/sorting/bubblesort.js) 58 | * [Insertion Sort](https://github.com/JoeKarlsson/data-structures/blob/master/sorting/insertionsort.js) 59 | * [Merge Sort](https://github.com/JoeKarlsson/data-structures/blob/master/sorting/mergesort.js) 60 | * [Quick Sort (duh)](https://github.com/JoeKarlsson/data-structures/blob/master/sorting/quicksort.js) 61 | * [Selection Sort](https://github.com/JoeKarlsson/data-structures/blob/master/sorting/selectionsort.js) 62 | * [Stack](https://github.com/JoeKarlsson/data-structures/tree/master/stack) 63 | * [Trie](https://github.com/JoeKarlsson/data-structures/tree/master/trie) 64 | 65 | An algorithm is a self-contained step-by-step set of operations to be performed. Algorithms perform calculation, data processing, and/or automated reasoning tasks. 66 | 67 | "Elegant" (compact) programs, "good" (fast) programs : The notion of "simplicity and elegance" appears informally in Knuth and precisely in Chaitin: 68 | 69 | Knuth: ". . .we want good algorithms in some loosely defined aesthetic sense. One criterion . . . is the length of time taken to perform the algorithm . . .. Other criteria are adaptability of the algorithm to computers, its simplicity and elegance, etc" 70 | 71 | Chaitin: " . . . a program is 'elegant,' by which I mean that it's the smallest possible program for producing the output that it does" 72 | 73 | ### TODO 74 | 75 | * Timsort 76 | * Floyd-Warshall 77 | * Traveling Salesman 78 | * k-way merge 79 | * Matching users to servers, using Gayle-Shapely Algorithm for Stable Marriage problem 80 | * This is a beautiful algorithm for fair matching. Simple, elegant and effective. In its core form, it’s also straightforward to implement. Has numerous applications. See: Stable marriage problem - Wikipedia 81 | * A toy implementation of Viterbi algorithm 82 | * Ubiquitous in cell phone technology, and many other applications, Viterbi algorithm is a Dynamic Programming based algorithm that finds the most likely sequence of states. 83 | * SSL transport, is the bane of safe existence on Internet these days. One of the most well-known algorithms in secure transport, is RSA, named by the first initials of its inventors. Implementing RSA is fun and instructive e.g. C code to implement RSA Algorithm(Encryption and Decryption) 84 | * Safe Browsing (or similar) using Bloom filters 85 | * Bloom filters found very rare usage until the world got more online and we hit scale. But these days, we see new applications very frequently.Chrome browser uses Bloom filters to make preliminary decision on safe browsing. See some novel applications here. 86 | * Implement an LALR parser 87 | * As a CS student, you may have already implemented it as part of your compiler’s class. But if not, then you should. LALR parsing makes syntactic sense of source code, whichever language you use. Many implementations of LALR exist. e.g. Where can I find a _simple_, easy to understand implementation of an LR(1) parser generator? Also, use YACC to understand LALR parsing better. 88 | * Treemap using Red Black Trees! 89 | * RB Trees are not algorithms, but they are famed enough, that no discussion of tantalizing DS/Algorithms is complete without discussing them. The smoothest way to see/implement RB Trees, is to look at Treemap implementation in Java. 90 | * Circle Drawing using Bresenham’s algorithm 91 | * Ever wondered, how circles are drawn on the screen, with minimal jaggedness (aliasing)? Bresenham’s elegant algorithm is at play here. See a version here: Circle Generation Algorithm . A refreshing use of a similar algorithm, is to make properly sized tabs in Chrome. Something we see almost every day. Such hidden gems! 92 | * Implement PageRank 93 | * Can’t miss this. This transformed our lives in ways we never thought possible. Get started here: Pagerank Explained Correctly with Examples 94 | 95 | ## Contributing 96 | 97 | Don't hesitate to create a pull request. Every contribution is appreciated. In development you can start the tests by calling `npm test`. Checkout our [contribution README](https://github.com/JoeKarlsson/data-structures/blob/master/CONTRIBUTING.md) for more info. 98 | 99 | 1. Fork it! 100 | 2. Create your feature branch: ```git checkout -b my-new-feature``` 101 | 3. Commit your changes: ```git commit -am 'Add some feature'``` 102 | 4. Push to the branch: ````git push origin my-new-feature```` 103 | 5. Submit a pull request :D 104 | 105 |

Maintainers

106 | 107 | 108 | 109 | 110 | 116 | 117 | 118 |
111 | 113 |
114 | Joe Karlsson 115 |
119 | 120 |

LICENSE

121 | 122 | #### [MIT](./LICENSE) 123 | 124 | [tests]: https://travis-ci.org/JoeKarlsson/data-structures.svg?branch=master 125 | [tests-url]: https://travis-ci.org/JoeKarlsson/data-structures 126 | 127 | [maintainability]: https://api.codeclimate.com/v1/badges/462bdda833cde7f72e01/maintainability 128 | [maintainability-url]: https://codeclimate.com/github/JoeKarlsson/data-structures/maintainability 129 | 130 | [pr]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg 131 | [pr-url]: CONTRIBUTING.md 132 | 133 | [cover]: https://coveralls.io/repos/github/JoeKarlsson/data-structures/badge.svg?branch=master 134 | [cover-url]: https://coveralls.io/github/JoeKarlsson/data-structures?branch=master 135 | 136 | [stars]: https://img.shields.io/github/stars/JoeKarlsson/data-structures.svg?style=flat-square 137 | [stars-url]: https://github.com/JoeKarlsson/data-structures/stargazers 138 | 139 | [license]: https://img.shields.io/github/license/JoeKarlsson/data-structures.svg 140 | [license-url]: https://github.com/JoeKarlsson/data-structures/blob/master/LICENSE 141 | 142 | [twitter]: https://img.shields.io/twitter/url/https/github.com/JoeKarlsson/data-structures.svg?style=social&style=flat-square 143 | [twitter-url]: https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Fgithub.com%2FJoeKarlsson%2Fdata-structures 144 | -------------------------------------------------------------------------------- /test/list.spec.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const List = require('../list/list'); 3 | 4 | const { expect } = chai; 5 | 6 | describe( 'List', () => { 7 | let list; 8 | let values; 9 | let list2; 10 | let numValues; 11 | 12 | beforeEach(() => { 13 | values = ['lorem', 'ipsum', 'dolor', 'et']; 14 | list = new List(); 15 | values.forEach((value) => list.pushBack(value)); 16 | 17 | numValues = [1, 2, 3, 4, 5]; 18 | list2 = new List(); 19 | numValues.forEach((value) => list2.pushBack(value)); 20 | }); 21 | 22 | describe( 'Constructor', () => { 23 | it( 'should return a new empty List', () => { 24 | const list2 = new List('Cat'); 25 | const list3 = new List(); 26 | expect(list2).to.deep.equal({ 27 | head: { 28 | value: 'Cat', 29 | next: null, 30 | }, 31 | }); 32 | expect(list3).to.deep.equal({}); 33 | }); 34 | }); 35 | describe( '`pushback` method', () => { 36 | it( 'should add a new node to the end of the list', () => { 37 | expect(list).to.deep.equal({ 38 | head: { 39 | value: 'lorem', 40 | next: { 41 | value: 'ipsum', 42 | next: { 43 | value: 'dolor', 44 | next: { 45 | value: 'et', 46 | next: null, 47 | }, 48 | }, 49 | }, 50 | }, 51 | }); 52 | expect(list.pushBack('Cat')).to.deep.equal({ 53 | head: { 54 | value: 'lorem', 55 | next: { 56 | value: 'ipsum', 57 | next: { 58 | value: 'dolor', 59 | next: { 60 | value: 'et', 61 | next: { 62 | value: 'Cat', 63 | next: null, 64 | }, 65 | }, 66 | }, 67 | }, 68 | }, 69 | }); 70 | }); 71 | }); 72 | describe( '`getLength` method', () => { 73 | it( 'should the entire List with a new node', () => { 74 | const list2 = new List('Cat'); 75 | const list3 = new List(); 76 | 77 | expect(list.getLength()).to.equal(4); 78 | expect(list2.getLength()).to.equal(1); 79 | expect(list3.getLength()).to.equal(0); 80 | }); 81 | }); 82 | describe( '`gethead` method', () => { 83 | it( 'should the return head of the List', () => { 84 | expect(list.getHead()).to.deep.equal({ 85 | value: 'lorem', 86 | next: { 87 | value: 'ipsum', 88 | next: { 89 | value: 'dolor', 90 | next: { 91 | value: 'et', 92 | next: null, 93 | }, 94 | }, 95 | }, 96 | }); 97 | }); 98 | }); 99 | describe( '`getTail` method', () => { 100 | it( 'should return the tail of the list', () => { 101 | expect(list.getTail()).to.deep.equal({ 102 | value: 'et', 103 | next: null, 104 | }); 105 | }); 106 | }); 107 | describe( '`get` method', () => { 108 | it( 'should retrieve the Node with a given index', () => { 109 | expect(list.get(0)).to.deep.equal({ 110 | value: 'lorem', 111 | next: { 112 | value: 'ipsum', 113 | next: { 114 | value: 'dolor', 115 | next: { 116 | value: 'et', 117 | next: null, 118 | }, 119 | }, 120 | }, 121 | }); 122 | expect(list.get(1)).to.deep.equal({ 123 | value: 'ipsum', 124 | next: { 125 | value: 'dolor', 126 | next: { 127 | value: 'et', 128 | next: null, 129 | }, 130 | }, 131 | }); 132 | expect(list.get(2)).to.deep.equal({ 133 | value: 'dolor', 134 | next: { 135 | value: 'et', 136 | next: null, 137 | }, 138 | }); 139 | expect(list.get(3)).to.deep.equal({ 140 | value: 'et', 141 | next: null, 142 | }); 143 | }); 144 | it( 'should throw an error if the index exceeds list\'s size', () => { 145 | expect(list.get.bind(list, 100)).to.throw('Index exceeds list\'s size'); 146 | expect(list.get.bind(list, -1)).to.throw('Index exceeds list\'s size'); 147 | expect(list.get.bind(list, 5)).to.throw('Index exceeds list\'s size'); 148 | }); 149 | }); 150 | describe( '`push` method', () => { 151 | it( '`push(0, *)` should at a new node at the head', () => { 152 | expect(list.push('cat', 0)).to.deep.equal({ 153 | value: 'cat', 154 | next: { 155 | value: 'lorem', 156 | next: { 157 | value: 'ipsum', 158 | next: { 159 | value: 'dolor', 160 | next: { 161 | value: 'et', 162 | next: null, 163 | }, 164 | }, 165 | }, 166 | }, 167 | }); 168 | }); 169 | it( 'should add a new node at index', () => { 170 | expect(list.push('Cat', 2)).to.deep.equal({ 171 | value: 'lorem', 172 | next: { 173 | value: 'ipsum', 174 | next: { 175 | value: 'Cat', 176 | next: { 177 | value: 'dolor', 178 | next: { 179 | value: 'et', 180 | next: null, 181 | }, 182 | }, 183 | }, 184 | }, 185 | }); 186 | }); 187 | it( 'should throw an error if the index exceeds list\'s size', () => { 188 | expect(list.push.bind(list, 'Cat', 100)).to.throw('Index exceeds list\'s size'); 189 | expect(list.push.bind(list, 'Cat', -1)).to.throw('Index exceeds list\'s size'); 190 | expect(list.push.bind(list, 'Cat', 5)).to.throw('Index exceeds list\'s size'); 191 | }); 192 | }); 193 | describe( '`find` method', () => { 194 | it( 'should return the first node that value matches', () => { 195 | expect(list.find('lorem')).to.deep.equal({ 196 | value: 'lorem', 197 | next: { 198 | value: 'ipsum', 199 | next: { 200 | value: 'dolor', 201 | next: { 202 | value: 'et', 203 | next: null, 204 | }, 205 | }, 206 | }, 207 | }); 208 | expect(list.find('ipsum')).to.deep.equal({ 209 | value: 'ipsum', 210 | next: { 211 | value: 'dolor', 212 | next: { 213 | value: 'et', 214 | next: null, 215 | }, 216 | }, 217 | }); 218 | expect(list.find('dolor')).to.deep.equal({ 219 | value: 'dolor', 220 | next: { 221 | value: 'et', 222 | next: null, 223 | }, 224 | }); 225 | expect(list.find('et')).to.deep.equal({ 226 | value: 'et', 227 | next: null, 228 | }); 229 | }); 230 | it( 'should return null if the value is not in the list', () => { 231 | expect(list.find('foobar')).to.equal(null); 232 | expect(list.find(undefined)).to.equal(null); 233 | expect(list.find(null)).to.equal(null); 234 | expect(list.find(1001)).to.equal(null); 235 | }); 236 | 237 | }); 238 | describe( '`getValues` method', () => { 239 | it( 'should return an array of the list', () => { 240 | expect(list.getValues()).to.deep.equal(values); 241 | }); 242 | }); 243 | describe( '`reduce` method', () => { 244 | it( 'should reduce the list to a single value', () => { 245 | const numValues = [1, 2, 3, 4, 5]; 246 | const list2 = new List(); 247 | numValues.forEach((value) => list2.pushBack(value)); 248 | 249 | expect(list2.reduce((prev, curr) => prev + curr, 0)).to.equal(15); 250 | 251 | expect(list2.reduce((prev, curr) => prev + curr, 2)).to.equal(15); 252 | 253 | expect(list.reduce((prev, curr) => prev + curr, 0)).to.equal('loremipsumdoloret'); 254 | }); 255 | }); 256 | 257 | describe( '`forEach` method', () => { 258 | it( 'should traverse the list and executes the callback function for each element in the list.', () => { 259 | 260 | let numSum = 0; 261 | 262 | list2.forEach((el) => { 263 | numSum += el; 264 | }); 265 | expect(numSum).to.deep.equal(15); 266 | 267 | let stringSum = ''; 268 | list.forEach((el) => { 269 | stringSum += el; 270 | }); 271 | expect(stringSum).to.deep.equal('loremipsumdoloret'); 272 | }); 273 | }); 274 | 275 | describe( '`map` method', () => { 276 | it( 'should execute callbackFn on each item from the list and returns the results as another list.', () => { 277 | expect(list2.map((el) => ++el)).to.deep.equal({ 278 | head: { 279 | value: 2, 280 | next: { 281 | value: 3, 282 | next: { 283 | value: 4, 284 | next: { 285 | value: 5, 286 | next: { 287 | value: 6, 288 | next: null, 289 | }, 290 | }, 291 | }, 292 | }, 293 | }, 294 | }); 295 | expect(list.map((el) => `${el} foo`)).to.deep.equal({ 296 | head: { 297 | value: 'lorem foo', 298 | next: { 299 | value: 'ipsum foo', 300 | next: { 301 | value: 'dolor foo', 302 | next: { 303 | value: 'et foo', 304 | next: null, 305 | }, 306 | }, 307 | }, 308 | }, 309 | }); 310 | }); 311 | }); 312 | 313 | describe( '`remove` method', () => { 314 | it( 'should remove nodes with specific values from the list', () => { 315 | expect(list.remove('ipsum')).to.deep.equal({ 316 | head: { 317 | value: 'lorem', 318 | next: { 319 | value: 'dolor', 320 | next: { 321 | value: 'et', 322 | next: null, 323 | }, 324 | }, 325 | }, 326 | }); 327 | expect(list.remove('et')).to.deep.equal({ 328 | head: { 329 | value: 'lorem', 330 | next: { 331 | value: 'dolor', 332 | next: null, 333 | }, 334 | }, 335 | }); 336 | expect(list.remove('dolor')).to.deep.equal({ 337 | head: { 338 | value: 'lorem', 339 | next: null, 340 | }, 341 | }); 342 | list.remove('lorem'); 343 | expect(list).to.deep.equal({ 344 | head: {}, 345 | }); 346 | }); 347 | }); 348 | 349 | }); 350 | -------------------------------------------------------------------------------- /sorting/README.md: -------------------------------------------------------------------------------- 1 | # Sorting Algorithms 2 | 3 | 5 popular sorting algorithms implemented manually and visualized with DOM manipulation. 4 | The algorithms included are : bubble sort, merge sort, insertion sort, selection sort and quick sort. In addition to visualizations, this readme also includes high-level explanations and psuedo code for each sorting implementation, and big Θ worst case/ best case scenarios. 5 | 6 | You can check out a live version of these sorting algorithms [here](https://joekarlsson.github.io/sorting-alogorithms/): 7 | 8 | 9 | ## Visualizations 10 | 11 | Click on the different sorting algorithms to see visualizations for each different algorithm implementation. Before each sort, click on reset to randomize the order of the array. You are also able to adjust the speed of the sorting intervals at the top. 12 | 13 | ## bubble sort 14 | 15 | ### [javascript implementation](public/js/bubblesort.js) 16 | 17 | Bubble sort works in a nature similar to its name, the lesser or lighter values will 'bubble' to the beginning of the array, and the heavier values will sink. 18 | ``` 19 | procedure bubbleSort( A : list ) 20 | do 21 | swapped = false 22 | for i < A.length - 1 23 | if A[i-1] > A[i] 24 | swap ( A[i-1] > A[i] ) 25 | swapped = true 26 | end if 27 | end for 28 | while swapped == true 29 | end procedure 30 | ``` 31 | This process is done by making a do while loop, where the condition will exit if the array is sorted. Then we iterate over the array, and compare each element to the next one. If the element is lighter, then you swap their positions in the array, repeat until you hit the end of the array while always setting a conditional value to true. If you loop through the array, and the conditional value is never set, meaning there are no swaps, then you exit out of the array. 32 | 33 | ### Best Case 34 | Linearly increases time spent to sort with size of array. O(n) 35 | 36 | ### Worst Case 37 | Exponentially increases time spent to sort. O(n^2) 38 | 39 | ## merge sort 40 | 41 | ### [javascript implementation](public/js/mergesort.js) 42 | 43 | The process for this method takes two procedures, first splitting the array into smaller lists, then merging those lists back together, comparing the values of the two lists. Recursion is used to split the array into the smallest possible lists. 44 | ``` psuedocode 45 | procedure mergeSort ( A : list ) 46 | if A.length < 2 47 | return A 48 | 49 | middle = A.length / 2 50 | left = A.slice 0, middle 51 | right = A.slice middle, A.length 52 | 53 | return merge(mergeSort(left), mergeSort(right); 54 | 55 | procedure merge (left, right) 56 | result = [] 57 | while left.length && right.length both exist 58 | if left[0] <= right[0] 59 | push left to result 60 | else 61 | push right to result 62 | end while 63 | 64 | while only left exists 65 | push left 66 | 67 | while only right exists 68 | push right 69 | 70 | return result 71 | ``` 72 | If the list is 0 or 1 length, then its already sorted and we can return the array. Else, we have to find the mid point of the aray, then split it into a left and right array. Our second piece of logic takes in a left and right array, which returns a merged array. If both left and right arrays have a length, then we compare the first value of both, and push the lower value to the return array. Our first procedure utilizes recursion to get the smallest available arrays. 73 | 74 | ### Best & Worst Case 75 | Logarithimically and linearlly increases time spent to sort with size array. It's more efficient the larger the size of the array, not great for small arrays. O(n log(n)) 76 | 77 | ## insertion sort 78 | 79 | ### [javascript implementation](public/js/insertionsort.js) 80 | 81 | For each iteration in our insertion sort method, a single element is taken to find its location in a new sorted list. This pattern repeats until the old array has no elements left. 82 | 83 | ``` 84 | for i = 1 to A.length - 1 85 | j = i 86 | while j > 0 and A[j-1] > A[j] 87 | swap A[j-1] and A[j] 88 | j = j - 1 89 | ``` 90 | 91 | First, we have to iterate over our array, then we set a variable to our position in the array, j. While j has a length, we compare our values between our position and the next position in the array. If the next position is bigger, then we swap the two, and decrement j by 1; 92 | 93 | ### Best Case 94 | Linerally increases time with size. O(n) 95 | 96 | ### Worst Case 97 | Exponentially increases time with size. O(n^2) 98 | 99 | ## selection sort 100 | 101 | ### [javascript implementation](public/js/selectionsort.js) 102 | 103 | Searches the array to find the smallest value, then loops again to find the next smallest value. Assuming minimum is the first element, we compare our minimums then swap it with the position it should be in from the beginning. 104 | 105 | ``` 106 | for i = 0 to A.length - 1 107 | 108 | min = i; 109 | 110 | for j = i + 1 to A.length 111 | 112 | if A[j] < A[min] 113 | 114 | min = j 115 | 116 | if min != i 117 | swap A[i] and A[min] 118 | ``` 119 | We loop through our array, and set our min position variable to our first positon. We then loop again through the array with our variable j, and check if our position is our new minimum. If it is, then we set our min position to j. If min is not i, then we swap our new minimum with our current position. 120 | 121 | ### Best & Worst Case 122 | Exponentially increases time with size. O(n^2) 123 | 124 | ## quicksort 125 | 126 | ### [javascript implementation](public/js/quicksort.js) 127 | 128 | Otherwise known as partition-exchange sort, quicksort picks a pivot from the array, then reorders the array with values lower than the pivot before the pivot, and higher values after the pivot. After this is done, pivot belongs in this position, and we recursively apply the same steps to the other partitions. 129 | 130 | ``` 131 | quicksort(A, left, right) 132 | 133 | pivot = null 134 | 135 | if left does not exist 136 | left = 0 137 | 138 | if right does not exist 139 | right = A.length - 1 140 | 141 | if left < right 142 | pivot = position between left and right 143 | newPart = partition(A, pivot, left, right) 144 | quicksort(A, left, newPart - 1) 145 | quicksort(A, newPart + 1, right) 146 | 147 | partition(A, pivot, left, right) 148 | 149 | pivotValue = A[pivot] 150 | index = left 151 | 152 | swap pivot and right 153 | 154 | for i = left i <= right i++ 155 | 156 | if A[i] < pivotValue 157 | swap i and index 158 | index++ 159 | 160 | swap right and index 161 | 162 | return index 163 | ``` 164 | First we determine a pivot and loop through our array, if the value at our position is less than or equal to our pivot, then swap our position of i in the array to our position of j. 165 | Return j. We recursively do this until we have no more partitions. 166 | 167 | ### Best Case 168 | 169 | Logarithimically and linearlly increases time spent to sort with size array. It's more efficient the larger the size of the array, not great for small arrays. O(n log(n)) 170 | 171 | ### Worst Case 172 | 173 | Exponentially increase the time spent to sort with size array. 174 | 175 | - [ ] [Bubble Sort (video)](https://www.youtube.com/watch?v=P00xJgWzz2c&index=1&list=PL89B61F78B552C1AB) 176 | - [ ] [Analyzing Bubble Sort (video)](https://www.youtube.com/watch?v=ni_zk257Nqo&index=7&list=PL89B61F78B552C1AB) 177 | - [ ] [Insertion Sort, Merge Sort (video)](https://www.youtube.com/watch?v=Kg4bqzAqRBM&index=3&list=PLUl4u3cNGP61Oq3tWYp6V_F-5jb5L2iHb) 178 | - [ ] [Insertion Sort (video)](https://www.youtube.com/watch?v=c4BRHC7kTaQ&index=2&list=PL89B61F78B552C1AB) 179 | - [ ] [Merge Sort (video)](https://www.youtube.com/watch?v=GCae1WNvnZM&index=3&list=PL89B61F78B552C1AB) 180 | - [ ] [Quicksort (video)](https://www.youtube.com/watch?v=y_G9BkAm6B8&index=4&list=PL89B61F78B552C1AB) 181 | - [ ] [Selection Sort (video)](https://www.youtube.com/watch?v=6nDMgr0-Yyo&index=8&list=PL89B61F78B552C1AB) 182 | 183 | - [ ] Stanford lectures on sorting: 184 | - [ ] [Lecture 15 | Programming Abstractions (video)](https://www.youtube.com/watch?v=ENp00xylP7c&index=15&list=PLFE6E58F856038C69) 185 | - [ ] [Lecture 16 | Programming Abstractions (video)](https://www.youtube.com/watch?v=y4M9IVgrVKo&index=16&list=PLFE6E58F856038C69) 186 | 187 | - [ ] Shai Simonson, [Aduni.org](http://www.aduni.org/): 188 | - [ ] [Algorithms - Sorting - Lecture 2 (video)](https://www.youtube.com/watch?v=odNJmw5TOEE&list=PLFDnELG9dpVxQCxuD-9BSy2E7BWY3t5Sm&index=2) 189 | - [ ] [Algorithms - Sorting II - Lecture 3 (video)](https://www.youtube.com/watch?v=hj8YKFTFKEE&list=PLFDnELG9dpVxQCxuD-9BSy2E7BWY3t5Sm&index=3) 190 | 191 | - [ ] Steven Skiena lectures on sorting: 192 | - [ ] [lecture begins at 26:46 (video)](https://youtu.be/ute-pmMkyuk?list=PLOtl7M3yp-DV69F32zdK7YJcNXpTunF2b&t=1600) 193 | - [ ] [lecture begins at 27:40 (video)](https://www.youtube.com/watch?v=yLvp-pB8mak&index=8&list=PLOtl7M3yp-DV69F32zdK7YJcNXpTunF2b) 194 | - [ ] [lecture begins at 35:00 (video)](https://www.youtube.com/watch?v=q7K9otnzlfE&index=9&list=PLOtl7M3yp-DV69F32zdK7YJcNXpTunF2b) 195 | - [ ] [lecture begins at 23:50 (video)](https://www.youtube.com/watch?v=TvqIGu9Iupw&list=PLOtl7M3yp-DV69F32zdK7YJcNXpTunF2b&index=10) 196 | 197 | - [ ] UC Berkeley: 198 | - [ ] [CS 61B Lecture 29: Sorting I (video)](https://www.youtube.com/watch?v=EiUvYS2DT6I&list=PL4BBB74C7D2A1049C&index=29) 199 | - [ ] [CS 61B Lecture 30: Sorting II (video)](https://www.youtube.com/watch?v=2hTY3t80Qsk&list=PL4BBB74C7D2A1049C&index=30) 200 | - [ ] [CS 61B Lecture 32: Sorting III (video)](https://www.youtube.com/watch?v=Y6LOLpxg6Dc&index=32&list=PL4BBB74C7D2A1049C) 201 | - [ ] [CS 61B Lecture 33: Sorting V (video)](https://www.youtube.com/watch?v=qNMQ4ly43p4&index=33&list=PL4BBB74C7D2A1049C) 202 | 203 | - [ ] - Merge sort code: 204 | - [ ] [Using output array](http://www.cs.yale.edu/homes/aspnes/classes/223/examples/sorting/mergesort.c) 205 | - [ ] [In-place](https://github.com/jwasham/practice-cpp/blob/master/merge_sort/merge_sort.cc) 206 | - [ ] - Quick sort code: 207 | - [ ] [Implementation](http://www.cs.yale.edu/homes/aspnes/classes/223/examples/randomization/quick.c) 208 | - [ ] [Implementation](https://github.com/jwasham/practice-c/blob/master/quick_sort/quick_sort.c) 209 | 210 | - [ ] Implement: 211 | - [ ] Mergesort: O(n log n) average and worst case 212 | - [ ] Quicksort O(n log n) average case 213 | - Selection sort and insertion sort are both O(n^2) average and worst case 214 | - For heapsort, see Heap data structure above. 215 | 216 | - [ ] For curiosity - not required: 217 | - [ ] [Radix Sort](http://www.cs.yale.edu/homes/aspnes/classes/223/notes.html#radixSort) 218 | - [ ] [Radix Sort (video)](https://www.youtube.com/watch?v=xhr26ia4k38) 219 | - [ ] [Radix Sort, Counting Sort (linear time given constraints) (video)](https://www.youtube.com/watch?v=Nz1KZXbghj8&index=7&list=PLUl4u3cNGP61Oq3tWYp6V_F-5jb5L2iHb) 220 | - [ ] [Randomization: Matrix Multiply, Quicksort, Freivalds' algorithm (video)](https://www.youtube.com/watch?v=cNB2lADK3_s&index=8&list=PLUl4u3cNGP6317WaSNfmCvGym2ucw3oGp) 221 | - [ ] [Sorting in Linear Time (video)](https://www.youtube.com/watch?v=pOKy3RZbSws&list=PLUl4u3cNGP61hsJNdULdudlRL493b-XZf&index=14) 222 | -------------------------------------------------------------------------------- /search/binary-search-tree.js: -------------------------------------------------------------------------------- 1 | const LinkedList = require('../linked-list/linkedList'); 2 | 3 | // Private Helper functions 4 | const makeNode = ( value ) => { 5 | const node = {}; 6 | node.value = value; 7 | node.left = null; 8 | node.right = null; 9 | return node; 10 | }; 11 | 12 | class BinarySearchTree { 13 | constructor() { 14 | this.root = null; 15 | } 16 | 17 | add( value ) { 18 | const currentNode = makeNode( value ); 19 | if ( !this.root ) { 20 | this.root = currentNode; 21 | } else { 22 | this.insert( currentNode ); 23 | } 24 | return this.root; 25 | } 26 | 27 | // Find and insert a new node in the BST 28 | insert(currentNode) { 29 | const { value } = currentNode; 30 | 31 | const traverse = (node) => { 32 | if (value > node.value) { 33 | if (!node.right) { 34 | node.right = currentNode; 35 | return; 36 | } 37 | traverse(node.right); 38 | } else if (value < node.value) { 39 | if (!node.left) { 40 | node.left = currentNode; 41 | return; 42 | } 43 | traverse(node.left); 44 | } 45 | }; 46 | traverse(this.root); 47 | } 48 | 49 | // Find and return the node based on it's value 50 | get(start, searchFor, parent = null, isLeft = true) { 51 | if (!start) { 52 | return null; // key not found 53 | } 54 | if ( searchFor < start.value ) { 55 | return this.get( start.left, searchFor, start, true ); 56 | } if ( searchFor > start.value ) { 57 | return this.get( start.right, searchFor, start, false ); 58 | } 59 | // key is equal to node key 60 | return { 61 | current: start, 62 | parent, 63 | isLeft, 64 | }; 65 | } 66 | 67 | remove(value) { 68 | let found = false; 69 | let childCount; 70 | let replacement; 71 | let replacementParent; 72 | 73 | // find the node 74 | const { 75 | current, 76 | parent, 77 | isLeft, 78 | } = this.get(this.root, value); 79 | 80 | if (current) { 81 | found = true; 82 | } 83 | // only proceed if the node was found 84 | if (found) { 85 | // Figure out how many children 86 | childCount = (current.left !== null ? 1 : 0) 87 | + (current.right !== null ? 1 : 0); 88 | // special case: the value is at the root 89 | if (current === this.root) { 90 | switch (childCount) { 91 | // two children, little work to do 92 | case 2: 93 | // new root will be the old root's left child 94 | // ...maybe 95 | replacement = this.root.left; 96 | // find the right-most leaf node to be 97 | // the real new root 98 | while (replacement.right !== null) { 99 | replacementParent = replacement; 100 | replacement = replacement.right; 101 | } 102 | // it's not the first node on the left 103 | if (replacementParent !== null) { 104 | // remove the new root from it's 105 | // previous position 106 | replacementParent.right = replacement.left; 107 | // give the new root all of the old 108 | // root's children 109 | replacement.right = this.root.right; 110 | replacement.left = this.root.left; 111 | } else { 112 | // just assign the children 113 | replacement.right = this.root.right; 114 | } 115 | // officially assign new root 116 | this.root = replacement; 117 | break; 118 | // no default 119 | } 120 | 121 | // non-root values 122 | } else { 123 | switch (childCount) { 124 | // Leaf Node 125 | case 0: 126 | if (isLeft) { 127 | parent.left = null; 128 | } else { 129 | parent.right = null; 130 | } 131 | break; 132 | // 1 child 133 | case 1: 134 | if ( isLeft ) { 135 | parent.left = current.left; 136 | } else { 137 | parent.right = current.left; 138 | } 139 | break; 140 | 141 | // two children, a bit more complicated - we need to determine 142 | // replacement node 143 | case 2: 144 | 145 | // find the min node on the right side of curr node 146 | const traverse = (node) => (!node.left ? node : traverse( node.left )); 147 | const minNode = traverse(current.right); 148 | 149 | const result = this.get(this.root, minNode.value); 150 | 151 | // copy the value in targetted node 152 | current.value = minNode.value; 153 | 154 | // Delete duplicate node 155 | if ( result.isLeft ) { 156 | result.parent.left = minNode.left; 157 | } else { 158 | result.parent.right = minNode.left; 159 | } 160 | 161 | // no default 162 | } 163 | } 164 | } 165 | } 166 | 167 | contains(value) { 168 | const node = this.root; 169 | const traverse = ( node ) => { 170 | if (!node) { 171 | return false; 172 | } 173 | if ( value === node.value ) { 174 | return true; 175 | } if ( value > node.value ) { 176 | return traverse(node.right); 177 | } if ( value < node.value ) { 178 | return traverse( node.left ); 179 | } 180 | }; 181 | return traverse( node ); 182 | } 183 | 184 | // find the left most node to find the min value of a binary tree; 185 | findMin() { 186 | const node = this.root; 187 | const traverse = ( node ) => (!node.left ? node.value : traverse( node.left )); 188 | return traverse(node); 189 | } 190 | 191 | // find the right most node to find the max value of a binary tree; 192 | findMax() { 193 | const node = this.root; 194 | const traverse = ( node ) => (!node.right ? node.value : traverse( node.right )); 195 | return traverse( node ); 196 | } 197 | 198 | getDepth() { 199 | let maxDepth = 0; 200 | const node = this.root; 201 | const traverse = (node, depth) => { 202 | if ( !node ) return null; 203 | if ( node ) { 204 | maxDepth = depth > maxDepth ? depth : maxDepth; 205 | traverse( node.left, depth + 1 ); 206 | traverse( node.right, depth + 1 ); 207 | } 208 | }; 209 | traverse( node, 0 ); 210 | return maxDepth; 211 | } 212 | 213 | countLeaves() { 214 | let count = 0; 215 | const node = this.root; 216 | const traverse = ( node ) => { 217 | if ( !node) { 218 | return null; 219 | } 220 | if ( !node.left && !node.right ) { 221 | count++; 222 | } else { 223 | traverse(node.left) + traverse(node.right); 224 | } 225 | }; 226 | traverse(node); 227 | return count; 228 | } 229 | 230 | // Find the averages of all nodes on at each depth 231 | nodeAverages() { 232 | const node = this.root; 233 | const result = {}; 234 | const depthAverages = []; 235 | 236 | const traverse = ( node, depth ) => { 237 | if ( !node ) { 238 | return null; 239 | } 240 | if ( node ) { 241 | if ( !result[depth] ) { 242 | result[depth] = [node.value]; 243 | } else { 244 | result[depth].push( node.value ); 245 | } 246 | } 247 | // check to see if node is a leaf, depth stays the same if it is 248 | // otherwise increment depth for possible right and left nodes 249 | if ( node.right || node.left ) { 250 | traverse(node.left, depth + 1); 251 | traverse(node.right, depth + 1); 252 | } 253 | }; 254 | traverse(node, 0); 255 | 256 | // get averages and breadthFirst 257 | for ( const key in result ) { 258 | const len = result[key].length; 259 | let depthAvg = 0; 260 | for ( let i = 0; i < len; i++ ) { 261 | depthAvg += result[key][i]; 262 | } 263 | depthAverages.push( Number( ( depthAvg / len ).toFixed( 2 ) ) ); 264 | } 265 | return depthAverages; 266 | } 267 | 268 | /* BREADTH FIRST TREE TRAVERSAL */ 269 | 270 | /* Breadth First Search finds all the siblings at each level 271 | in order from left to right or from right to left. */ 272 | breadthFirstLTR() { 273 | let node = this.root; 274 | const queue = [node]; 275 | const result = []; 276 | while (node) { 277 | result.push(node.value); 278 | node.left && queue.push(node.left); 279 | node.right && queue.push(node.right); 280 | node = queue.shift(); 281 | } 282 | return result; 283 | } 284 | 285 | breadthFirstRTL() { 286 | let node = this.root; 287 | const queue = [node]; 288 | const result = []; 289 | while (node) { 290 | result.push(node.value); 291 | node.right && queue.push(node.right); 292 | node.left && queue.push(node.left); 293 | node = queue.shift(); 294 | } 295 | return result; 296 | } 297 | 298 | /* DEPTH FIRST TRAVERSALS */ 299 | 300 | /* preOrder is a type of depth-first traversal that tries 301 | togo deeper in the tree before exploring siblings. It 302 | returns the shallowest descendants first. 303 | 304 | 1) Display the data part of root element (or current element) 305 | 2) Traverse the left subtree by recursively calling the pre-order function. 306 | 3) Traverse the right subtree by recursively calling the pre-order function. */ 307 | preOrder() { 308 | const result = []; 309 | const node = this.root; 310 | const traverse = ( node ) => { 311 | result.push(node.value); 312 | node.left && traverse(node.left); 313 | node.right && traverse(node.right); 314 | }; 315 | traverse(node); 316 | return result; 317 | } 318 | 319 | /* inOrder traversal is a type of depth-first traversal 320 | that also tries to go deeper in the tree before exploring siblings. 321 | however, it returns the deepest descendents first 322 | 323 | 1) Traverse the left subtree by recursively calling the pre-order function. 324 | 2) Display the data part of root element (or current element) 325 | 3) Traverse the right subtree by recursively calling the pre-order function. */ 326 | inOrder() { 327 | const result = []; 328 | const node = this.root; 329 | const traverse = ( node ) => { 330 | node.left && traverse(node.left); 331 | result.push(node.value); 332 | node.right && traverse(node.right); 333 | }; 334 | traverse(node); 335 | return result; 336 | } 337 | 338 | /* postOrder traversal is a type of depth-first traversal 339 | that also tries to go deeper in the tree before exploring siblings. 340 | however, it returns the deepest descendents first 341 | 342 | 1) Traverse the left subtree by recursively calling the pre-order function. 343 | 2) Display the data part of root element (or current element) 344 | 3) Traverse the right subtree by recursively calling the pre-order function. */ 345 | postOrder() { 346 | const result = []; 347 | const node = this.root; 348 | const traverse = (node) => { 349 | node.left && traverse(node.left); 350 | node.right && traverse(node.right); 351 | result.push(node.value); 352 | }; 353 | traverse(node); 354 | return result; 355 | } 356 | 357 | // Convert a binary search tree to a linked-list in place. 358 | convertToLinkedList() { 359 | const node = this.root; 360 | if ( !node ) { 361 | return null; 362 | } 363 | const list = new LinkedList(); 364 | const result = this.inOrder( node ); 365 | 366 | for ( let i = 0; i < result.length; i++) { 367 | list.add( result[i] ); 368 | } 369 | return list; 370 | } 371 | } 372 | 373 | module.exports = BinarySearchTree; 374 | -------------------------------------------------------------------------------- /test/linked-list.spec.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const LinkedList = require('../linked-list/linkedList'); 3 | 4 | const { expect } = chai; 5 | 6 | describe('Linked List', () => { 7 | let newLinkedList; 8 | 9 | it('should be a function', () => { 10 | expect(LinkedList).to.exist; 11 | expect(LinkedList).to.be.a('function'); 12 | }); 13 | 14 | describe('returns a LinkedList object used to interact with the private Linked List object', () => { 15 | beforeEach(() => { 16 | newLinkedList = new LinkedList(); 17 | }); 18 | it('should return a module object', () => { 19 | expect(newLinkedList).to.be.an('object'); 20 | }); 21 | }); 22 | 23 | describe('Linked List has methods available through Linked List Object', () => { 24 | beforeEach(() => { 25 | newLinkedList = new LinkedList(); 26 | }); 27 | it('should have a method named `getHead`', () => { 28 | expect(newLinkedList.getHead).to.exist; 29 | expect(newLinkedList.getHead).to.be.a('function'); 30 | }); 31 | it('should have a method named `getTail`', () => { 32 | expect(newLinkedList.getTail).to.exist; 33 | expect(newLinkedList.getTail).to.be.a('function'); 34 | }); 35 | it('should have a method named `add`', () => { 36 | expect(newLinkedList.add).to.exist; 37 | expect(newLinkedList.add).to.be.a('function'); 38 | }); 39 | it('should have a method named `remove`', () => { 40 | expect(newLinkedList.remove).to.exist; 41 | expect(newLinkedList.remove).to.be.a('function'); 42 | }); 43 | it('should have a method named `get`', () => { 44 | expect(newLinkedList.get).to.exist; 45 | expect(newLinkedList.get).to.be.a('function'); 46 | }); 47 | it('should have a method named `insert`', () => { 48 | expect(newLinkedList.insert).to.exist; 49 | expect(newLinkedList.insert).to.be.a('function'); 50 | }); 51 | }); 52 | 53 | describe('`getHead` method', () => { 54 | let urlList; 55 | 56 | beforeEach(() => { 57 | urlList = new LinkedList(); 58 | }); 59 | it('should retrieve the value of the first node in a list', () => { 60 | expect(urlList.getHead).to.be.a('function'); 61 | expect(urlList.getHead()).to.be.null; 62 | }); 63 | }); 64 | 65 | describe('`getTail` method', () => { 66 | let urlList; 67 | 68 | beforeEach(() => { 69 | urlList = new LinkedList(); 70 | }); 71 | it('should retrieve the value of the first node in a list', () => { 72 | expect(urlList.getTail).to.be.a('function'); 73 | expect(urlList.getTail()).to.be.null; 74 | }); 75 | }); 76 | 77 | describe('`add` method', () => { 78 | let newNodeA; 79 | let newLinkedListA; 80 | let newLinkedListB; 81 | let newLinkedListC; 82 | 83 | beforeEach(() => { 84 | newLinkedListA = new LinkedList(); // return new node 85 | newLinkedListB = new LinkedList(); // for `head` and `tail` 86 | newLinkedListC = new LinkedList(); // for `tail` 87 | newNodeA = newLinkedListA.add('http://slashdot.org'); 88 | }); 89 | 90 | describe('should return a new node object after appending the node to the list', () => { 91 | it('should return a new node object', () => { 92 | expect(newNodeA).to.not.be.undefined; 93 | expect(newNodeA.value).to.be.equal('http://slashdot.org'); 94 | }); 95 | it('should have a property named `value`', () => { 96 | expect(newNodeA.value).to.exist; 97 | }); 98 | it('should have a property named `next`', () => { 99 | expect(newNodeA.next).to.be.null; 100 | }); 101 | }); 102 | 103 | describe('should append new nodes', () => { 104 | it('`head` and `tail` should reference the same node object when adding to an empty list', () => { 105 | // add a new node! 106 | newLinkedListB.add('http://devleague.com'); 107 | 108 | // test! 109 | expect(newLinkedListB.getHead().value).to.equal('http://devleague.com'); 110 | expect(newLinkedListB.getTail().value).to.equal('http://devleague.com'); 111 | 112 | // really the same? 113 | expect(newLinkedListB.getHead()).to.equal(newLinkedListB.getTail()); 114 | }); 115 | }); 116 | 117 | describe('should append even more nodes', () => { 118 | it('`tail` should reference the most recently added node', () => { 119 | // add new nodes 120 | newLinkedListC.add('http://eff.org'); 121 | newLinkedListC.add('http://devleague.com'); 122 | 123 | // tests! 124 | // console.log(newLinkedListC.getHead()); 125 | expect(newLinkedListC.getHead().value).to.equal('http://eff.org'); 126 | expect(newLinkedListC.getTail().value).to.equal('http://devleague.com'); 127 | 128 | // add another node 129 | newLinkedListC.add('http://xkcd.org'); 130 | 131 | // test! 132 | expect(newLinkedListC.getHead().value).to.equal('http://eff.org'); 133 | expect(newLinkedListC.getTail().value).to.equal('http://xkcd.org'); 134 | }); 135 | }); 136 | }); 137 | describe('`get` method', () => { 138 | let urlList; 139 | let bookList; 140 | 141 | beforeEach(() => { 142 | urlList = new LinkedList(); 143 | bookList = new LinkedList(); 144 | 145 | const urlArr = [ 146 | 'news.ycombinator.com', 147 | 'mozilla.org', 148 | 'eff.org', 149 | 'icann.org', 150 | ]; 151 | 152 | const bookArr = [ 153 | 'Ready Player One', 154 | '1982', 155 | 'Neuromancer', 156 | 'Snow Crash', 157 | ]; 158 | 159 | urlArr.forEach((url) => { 160 | urlList.add(url); 161 | }); 162 | bookArr.forEach((book) => { 163 | bookList.add(book); 164 | }); 165 | }); 166 | 167 | describe('takes an argument', () => { 168 | it('should find a node by it\'s index in the Linked List', () => { 169 | // urlList Tests 170 | expect(urlList.get(0).value).to.equal('news.ycombinator.com'); 171 | expect(urlList.get(1).value).to.equal('mozilla.org'); 172 | expect(urlList.get(2).value).to.equal('eff.org'); 173 | expect(urlList.get(3).value).to.equal('icann.org'); 174 | 175 | expect(urlList.getHead().value).to.equal('news.ycombinator.com'); 176 | expect(urlList.getTail().value).to.equal('icann.org'); 177 | 178 | // bookList Tests 179 | expect(bookList.get(0).value).to.equal('Ready Player One'); 180 | expect(bookList.get(1).value).to.equal('1982'); 181 | expect(bookList.get(2).value).to.equal('Neuromancer'); 182 | expect(bookList.get(3).value).to.equal('Snow Crash'); 183 | 184 | expect(bookList.getHead().value).to.equal('Ready Player One'); 185 | expect(bookList.getTail().value).to.equal('Snow Crash'); 186 | }); 187 | it('should return `false` if no node is found', () => { 188 | expect(urlList.get(4)).to.be.false; 189 | expect(urlList.get(5)).to.be.false; 190 | expect(bookList.get(4)).to.be.false; 191 | expect(bookList.get(5)).to.be.false; 192 | }); 193 | }); 194 | }); 195 | 196 | describe('`remove` method', () => { 197 | let urlList; 198 | let bookList; 199 | 200 | beforeEach(() => { 201 | urlList = new LinkedList(); 202 | bookList = new LinkedList(); 203 | 204 | const urlArr = [ 205 | 'news.ycombinator.com', 206 | 'mozilla.org', 207 | 'eff.org', 208 | 'icann.org', 209 | ]; 210 | 211 | const bookArr = [ 212 | 'Ready Player One', 213 | '1982', 214 | 'Neuromancer', 215 | 'Snow Crash', 216 | ]; 217 | 218 | urlArr.forEach((url) => { 219 | urlList.add(url); 220 | }); 221 | bookArr.forEach((book) => { 222 | bookList.add(book); 223 | }); 224 | }); 225 | 226 | describe('takes an argument', () => { 227 | it('should remove a node by it\'s index in the Linked List', () => { 228 | // urlList Tests 229 | // remove middle node 230 | urlList.remove(2); 231 | 232 | // test new node at position 2 233 | expect(urlList.get(2).value).to.equal('icann.org'); 234 | 235 | // remove last node 236 | urlList.remove(2); 237 | 238 | // retrieve new node at position 2 239 | expect(urlList.get(2)).to.be.false; 240 | expect(urlList.getHead().value).to.equal('news.ycombinator.com'); 241 | expect(urlList.getTail().value).to.equal('mozilla.org'); 242 | 243 | // bookList Tests 244 | // remove first node 245 | bookList.remove(0); 246 | expect(bookList.get(0).value).to.equal('1982'); 247 | // console.log(bookList.get(0)) 248 | 249 | bookList.remove(1); 250 | expect(bookList.getHead().value).to.equal('1982'); 251 | expect(bookList.getTail().value).to.equal('Snow Crash'); 252 | }); 253 | it('should return `false` if a node cannot be found to be removed', () => { 254 | expect(urlList.remove(9)).to.be.false; 255 | expect(urlList.remove(4)).to.be.false; 256 | expect(bookList.remove(4)).to.be.false; 257 | expect(bookList.remove(6)).to.be.false; 258 | }); 259 | }); 260 | }); 261 | 262 | describe('`insert` method', () => { 263 | let urlList; 264 | let bookList; 265 | 266 | beforeEach(() => { 267 | urlList = new LinkedList(); 268 | bookList = new LinkedList(); 269 | 270 | const urlArr = [ 271 | 'news.ycombinator.com', 272 | 'icann.org', 273 | ]; 274 | 275 | const bookArr = [ 276 | 'Neuromancer', 277 | 'Snow Crash', 278 | ]; 279 | 280 | urlArr.forEach((url) => { 281 | urlList.add(url); 282 | }); 283 | bookArr.forEach((book) => { 284 | bookList.add(book); 285 | }); 286 | }); 287 | describe('takes two arguments, a `value` and an `index`', () => { 288 | it('should add a new node at a given index', () => { 289 | // insert into second position of list 290 | urlList.insert('mozilla.org', 1); 291 | expect(urlList.get(0).value).to.be.equal('news.ycombinator.com'); 292 | expect(urlList.get(1).value).to.be.equal('mozilla.org'); 293 | expect(urlList.get(2).value).to.be.equal('icann.org'); 294 | 295 | // insert into beginning of list 296 | bookList.insert('Ready Player One', 0); 297 | expect(bookList.get(0).value).to.be.equal('Ready Player One'); 298 | expect(bookList.get(1).value).to.be.equal('Neuromancer'); 299 | expect(bookList.get(2).value).to.be.equal('Snow Crash'); 300 | 301 | urlList.insert('devleague.com', 1); 302 | expect(urlList.get(0).value).to.be.equal('news.ycombinator.com'); 303 | expect(urlList.get(1).value).to.be.equal('devleague.com'); 304 | expect(urlList.get(2).value).to.be.equal('mozilla.org'); 305 | expect(urlList.get(3).value).to.be.equal('icann.org'); 306 | 307 | // insert into index `1` 308 | bookList.insert('The Stranger', 1); 309 | expect(bookList.getHead().value).to.be.equal('Ready Player One'); 310 | expect(bookList.get(0).value).to.be.equal('Ready Player One'); 311 | expect(bookList.get(1).value).to.be.equal('The Stranger'); 312 | expect(bookList.get(2).value).to.be.equal('Neuromancer'); 313 | }); 314 | it('should return `false` if the index given is a value larger than the List\'s length', () => { 315 | // urlList has two items, it's max index value is 1 316 | expect(urlList.insert('boingboing.net', 3)).to.be.false; 317 | expect(urlList.getHead().value).to.be.equal('news.ycombinator.com'); 318 | expect(urlList.get(0).value).to.be.equal('news.ycombinator.com'); 319 | expect(urlList.get(1).value).to.be.equal('icann.org'); 320 | expect(urlList.getTail().value).to.be.equal('icann.org'); 321 | 322 | // test -1 323 | expect(bookList.insert('The Stranger', -1)).to.be.false; 324 | expect(bookList.getHead().value).to.be.equal('Neuromancer'); 325 | expect(bookList.get(0).value).to.be.equal('Neuromancer'); 326 | expect(bookList.get(1).value).to.be.equal('Snow Crash'); 327 | expect(bookList.getTail().value).to.equal('Snow Crash'); 328 | 329 | }); 330 | }); 331 | }); 332 | }); 333 | --------------------------------------------------------------------------------