├── .gitignore ├── LICENSE ├── README.md ├── js ├── LinkedLists │ └── SimpleLinkedList.js ├── Trees │ ├── AVLTree.js │ ├── BinarySearchTree.js │ └── Heap.js ├── euler │ ├── 001-multiples-3-and-5.js │ └── 002-even-fibonacci-numbers.js ├── graphs │ └── undirectedGraph.js ├── sorting │ ├── BubbleSort.js │ ├── InsertionSort.js │ ├── MergeSort.js │ ├── QuickSort.js │ └── SelectionSort.js └── strings │ └── stringMatches.js ├── karma.conf.js ├── package.json ├── specs ├── LinkedLists │ └── SimpleLinkedLists.spec.js ├── Trees │ ├── AVLTree.spec.js │ ├── BinarySearchTree.spec.js │ ├── Heap.spec.js │ └── readme.markdown ├── euler │ ├── 001-multiples-3-and-5.spec.js │ └── 002-even-fibonacci-numbers.spec.js ├── graphs │ └── undirectedGraph.spec.js ├── sorting │ ├── BubbleSort.spec.js │ ├── InsertionSort.spec.js │ ├── MergeSort.spec.js │ ├── QuickSort.spec.js │ └── SelectionSort.spec.js └── strings │ └── findStringMatches.spec.js └── utils └── logger.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jaime 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Algorithms and Data Structures in JavaScript 2 | 3 | Basic examples of data structures and algorithms in JavaScript written TDD-style #FTW 4 | -------------------------------------------------------------------------------- /js/LinkedLists/SimpleLinkedList.js: -------------------------------------------------------------------------------- 1 | function LinkedList(value){ 2 | let head = null; 3 | 4 | if (value) head = Node(value); 5 | 6 | return { 7 | get head(){ return head;}, 8 | hasItem, 9 | append, 10 | remove, 11 | removeFirst, 12 | insert, 13 | getLast, 14 | getNthLast 15 | }; 16 | 17 | function hasItem(value){ 18 | let iter = getTraverseLinkedListIterator(head); 19 | for (let item of iter){ 20 | if (item.value === value) return true; 21 | } 22 | return false; 23 | } 24 | 25 | function append(value){ 26 | if (!head) head = Node(value); 27 | else { 28 | let lastItem = head; 29 | while (lastItem.next !== null) lastItem = lastItem.next; 30 | lastItem.next = Node(value); 31 | } 32 | } 33 | 34 | function insert(value){ 35 | if (!head) head = Node(value); 36 | else { 37 | let currentHead = head; 38 | head = Node(value, /* next */ currentHead); 39 | } 40 | } 41 | 42 | function remove(value){ 43 | if (!hasItem(value)) { 44 | throw new Error(`Item ${value} is not in the list`); 45 | } 46 | if (value === head.value) { 47 | head = head.next; 48 | } else { 49 | removeItemInside(value); 50 | } 51 | } 52 | 53 | function removeItemInside(value){ 54 | let previousItem, 55 | currentItem = head; 56 | 57 | while (currentItem){ 58 | if (currentItem.value === value){ 59 | previousItem.next = currentItem.next; 60 | } 61 | previousItem = currentItem; 62 | currentItem = currentItem.next; 63 | } 64 | } 65 | 66 | function removeFirst(){ 67 | if (head) { 68 | var value = head.value; 69 | head = head.next; 70 | return value; 71 | } else { 72 | throw new Error('no items to remove'); 73 | } 74 | } 75 | 76 | function getLast(){ 77 | let iter = getTraverseLinkedListIterator(head); 78 | for (let item of iter){ 79 | if (!item.next) return item.value; 80 | } 81 | throw new Error("The list is empty"); 82 | } 83 | 84 | function getNthLast(position){ 85 | let iter = getTraverseLinkedListIterator(head), 86 | values = Array.from(iter).map(i => i.value), // BOOM! 87 | index = values.length - (position); 88 | 89 | if (index < 0) throw new Error('there are no items in that position. The list is too small'); 90 | return values[index]; 91 | } 92 | 93 | // Abstract linked list iteration 94 | // inside a generator Say WHAAAAAT! 95 | function* getTraverseLinkedListIterator(head){ 96 | let currentItem = head; 97 | while(currentItem){ 98 | yield currentItem; 99 | currentItem = currentItem.next; 100 | } 101 | } 102 | 103 | } 104 | 105 | function Node(value, next=null){ 106 | return { 107 | value, 108 | next 109 | }; 110 | } 111 | 112 | function Stack(){ 113 | let list = new LinkedList(); 114 | 115 | return { 116 | push: value => list.insert(value), 117 | hasItem: value => list.hasItem(value), 118 | pop: () => list.removeFirst(), 119 | isEmpty: () => list.head == null 120 | }; 121 | } 122 | 123 | 124 | -------------------------------------------------------------------------------- /js/Trees/AVLTree.js: -------------------------------------------------------------------------------- 1 | // 2 | // Running wild without ; !!!! Wiiii!!! 3 | // 4 | 5 | class AVLTree { 6 | constructor(value){ 7 | this.root = value ? AVLTreeNode(value, null, this) : undefined; 8 | } 9 | add(value){ 10 | l.log('adding AVL node with value: ' + value); 11 | if (!this.root) this.root = AVLTreeNode(value, null, this) 12 | else this.addNode(this.root, value) 13 | } 14 | addNode(node, value){ 15 | if (value < node.value){ 16 | if (node.left) this.addNode(node.left, value) 17 | else node.left = AVLTreeNode(value, node, this) 18 | } else { 19 | if (node.right) this.addNode(node.right, value) 20 | else node.right = AVLTreeNode(value, node, this) 21 | } 22 | // this is evaluated for each node going upwards 23 | // like a baws! 24 | if (node.isUnbalanced()) node.balance() 25 | } 26 | } 27 | 28 | 29 | function AVLTreeNode(value, parent, tree){ 30 | return { 31 | value, 32 | left: null, 33 | right: null, 34 | parent, 35 | tree, 36 | isUnbalanced, 37 | balance, 38 | getBalanceFactor, 39 | isRightHeavy(){ return this.getBalanceFactor() > 1}, 40 | isLeftHeavy(){ return this.getBalanceFactor() < -1} 41 | } 42 | 43 | function isUnbalanced() { 44 | const height = this.getBalanceFactor(); 45 | const isUnbalanced = Math.abs(height) > 1 46 | if (isUnbalanced) l.log('is unbalanced with height: ' + height) 47 | return isUnbalanced; 48 | } 49 | 50 | function getBalanceFactor() { 51 | const leftHeight = getHeight(this.left) 52 | const rightHeight = getHeight(this.right) 53 | return rightHeight - leftHeight; 54 | } 55 | 56 | function getHeight(node){ 57 | if (!node) return 0 58 | else return 1 + Math.max(getHeight(node.left), getHeight(node.right)) 59 | } 60 | 61 | function balance(){ 62 | l.log('balancing node with value: ' + this.value); 63 | if (this.isRightHeavy() && this.right.isLeftHeavy()) 64 | balanceRightLeft(this) 65 | else if (this.isRightHeavy()) 66 | balanceRight(this) 67 | else if (this.isLeftHeavy() && this.left.isRightHeavy()) 68 | balanceLeftRight(this) 69 | else 70 | balanceLeft(this) 71 | } 72 | 73 | function balanceRightLeft(node){ 74 | l.log('node ' + this + ' needs to be balanced right-left'); 75 | } 76 | function balanceRight(node){ 77 | l.log('node ' + node.value + ' needs to be balanced right'); 78 | let oldRoot = node 79 | let root = oldRoot.right 80 | rightChildBecomesRoot(oldRoot, root) 81 | rightChildLeftsNodeBecomesOldRootRightNode(oldRoot, root) 82 | oldRootBecomesRootLeftNode(oldRoot, root) 83 | } 84 | 85 | function rightChildBecomesRoot(oldRoot, root){ 86 | oldRoot.right = null 87 | root.parent = oldRoot.parent; 88 | oldRoot.parent = root; 89 | // update link in parent 90 | if (root.parent) { 91 | if (root.parent.left === oldRoot) root.parent.left = root; 92 | if (root.parent.right === oldRoot) root.parent.right = root; 93 | } 94 | l.log(root.value + ' becomes root'); 95 | } 96 | 97 | function rightChildLeftsNodeBecomesOldRootRightNode(oldRoot, root){ 98 | if (root.left) { 99 | oldRoot.right = root.left 100 | root.left = null 101 | l.log(root.left.value + ' becomes old root ' + oldRoot.value + ' right child') 102 | } 103 | } 104 | 105 | function oldRootBecomesRootLeftNode(oldRoot, root){ 106 | root.left = oldRoot 107 | l.log(root.left.value + ' becomes root ' + root.value + ' left child') 108 | // #4. if this is the root of the tree, update it 109 | if (oldRoot.tree.root === oldRoot) { 110 | l.log('change tree root to ' + root.value) 111 | oldRoot.tree.root = root 112 | } 113 | } 114 | 115 | function balanceLeftRight(node){ 116 | l.log('node ' + this + ' needs to be balanced left-right'); 117 | } 118 | function balanceLeft(node){ 119 | l.log('node ' + this + ' needs to be balanced left'); 120 | } 121 | 122 | } 123 | 124 | 125 | -------------------------------------------------------------------------------- /js/Trees/BinarySearchTree.js: -------------------------------------------------------------------------------- 1 | function Tree(value){ 2 | // private members 3 | let root; 4 | 5 | if(value) { root = TreeNode(value); } 6 | 7 | // public API 8 | return { 9 | get root() { return root;}, 10 | add, 11 | addRecursively, 12 | addMany, 13 | traversePreOrder, 14 | traversePreOrderIteratively, 15 | traverseInOrder, 16 | traversePostOrder, 17 | find, 18 | findRecursively 19 | }; 20 | 21 | function add(value){ 22 | if (!root) { return root = TreeNode(value);} 23 | 24 | let tree = getBinarySearchTreeIterator(root, value); 25 | for (let currentNode of tree){ 26 | if (value <= currentNode.value && !currentNode.left){ 27 | return currentNode.left = TreeNode(value); 28 | } else if (value > currentNode.value && !currentNode.right){ 29 | return currentNode.right = TreeNode(value); 30 | } 31 | } 32 | 33 | } 34 | 35 | function addRecursively(value){ 36 | if (!root) { return root = TreeNode(value);} 37 | return addRecursivelyOnNode(root, value); 38 | } 39 | 40 | function addRecursivelyOnNode(node, value){ 41 | // simplified to add minor or equal to left child 42 | if (value <= node.value) { 43 | if (!node.left) { 44 | return node.left = TreeNode(value); 45 | } 46 | return addRecursivelyOnNode(node.left, value); 47 | } else { 48 | if (!node.right) { 49 | return node.right = TreeNode(value); 50 | } 51 | return addRecursivelyOnNode(node.right, value); 52 | } 53 | throw new Error(); 54 | } 55 | 56 | function addMany(...values){ 57 | values.forEach(value => addRecursively(value)); 58 | } 59 | 60 | function traversePreOrder(processNode){ 61 | traverse(root, processNode); 62 | 63 | function traverse(node, processNode){ 64 | processNode(node); 65 | if (node.left) traverse(node.left, processNode); 66 | if (node.right) traverse(node.right, processNode); 67 | } 68 | } 69 | 70 | function traverseInOrder(processNode){ 71 | traverse(root, processNode); 72 | 73 | function traverse(node, processNode){ 74 | if (node.left) traverse(node.left, processNode); 75 | processNode(node); 76 | if (node.right) traverse(node.right, processNode); 77 | } 78 | } 79 | 80 | function traversePostOrder(processNode){ 81 | traverse(root, processNode); 82 | 83 | function traverse(node, processNode){ 84 | if (node.left) traverse(node.left, processNode); 85 | if (node.right) traverse(node.right, processNode); 86 | processNode(node); 87 | } 88 | } 89 | 90 | function traversePreOrderIteratively(processNode){ 91 | let stack = Stack(); // => global namespace haha! 92 | 93 | // for each node: 94 | // - process node 95 | // - go left 96 | // - go right 97 | 98 | let currentNode = root; 99 | while(currentNode){ 100 | // process Node 101 | processNode(currentNode); 102 | // go left 103 | if (currentNode.left){ 104 | // push into stack to go right later 105 | stack.push(currentNode.right); 106 | currentNode = currentNode.left; 107 | } else if (currentNode.right) { 108 | // only right node 109 | currentNode = currentNode.right; 110 | } else { 111 | // leave (go back) 112 | if (stack.isEmpty()){ currentNode = undefined;} 113 | else {currentNode = stack.pop();} 114 | } 115 | } 116 | } 117 | 118 | function find(value){ 119 | let nodes = getBinarySearchTreeIterator(root, value); 120 | for(let node of nodes){ 121 | if (node.value === value) return node; 122 | } 123 | throw Error(`Node with value ${value} was not found!`); 124 | } 125 | 126 | function findRecursively(value){ 127 | return findRecursivelyInNode(root, value); 128 | } 129 | 130 | function findRecursivelyInNode(node, value){ 131 | if (!node) throw Error(`Node with value ${value} was not found!`); 132 | if (node.value === value) return node; 133 | if (value > node.value) return findRecursivelyInNode(node.right, value); 134 | if (value < node.value) return findRecursivelyInNode(node.left, value); 135 | } 136 | 137 | } 138 | 139 | // TODO: Need to setup ES6 modules 140 | // right now everything is declared globally. Oh no! :O 141 | function TreeNode(value){ 142 | let left, right; 143 | return { 144 | value, 145 | left, 146 | right 147 | }; 148 | } 149 | 150 | function* getBinarySearchTreeIterator(root, value){ 151 | yield root; 152 | 153 | let currentNode = root; 154 | while(currentNode){ 155 | if (value <= currentNode.value){ currentNode = currentNode.left; } 156 | else { currentNode = currentNode.right; } 157 | yield currentNode; 158 | } 159 | } 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /js/Trees/Heap.js: -------------------------------------------------------------------------------- 1 | function Heap(value){ 2 | // private members 3 | let root; 4 | 5 | if(value) { root = HeapNode(value); } 6 | 7 | // public API 8 | return { 9 | get root() {return root;}, 10 | add 11 | }; 12 | 13 | function add(value){ 14 | if (!root) root = HeapNode(value); 15 | else addToSubHeap(root, value); 16 | // console.log('out: ' + JSON.stringify(root)); 17 | } 18 | 19 | function addToSubHeap(node, value){ 20 | // console.log('in node: ' + JSON.stringify(node) + ' value: ' + value); 21 | 22 | if (value > node.value) { 23 | // if the value is bigger than the current node 24 | // then switch it and position the old value 25 | let oldValue = node.value; 26 | node.value = value; 27 | value = oldValue; 28 | } 29 | 30 | if (!node.left) node.left = HeapNode(value); 31 | else if (!node.right) node.right = HeapNode(value) 32 | else addToSubHeap(node.left, value); 33 | } 34 | 35 | /*add, 36 | /*addRecursively, 37 | addMany, 38 | traversePreOrder, 39 | traversePreOrderIteratively, 40 | traverseInOrder, 41 | traversePostOrder, 42 | find, 43 | findRecursively*/ 44 | } 45 | 46 | // TODO: Need to setup ES6 modules 47 | // right now everything is declared globally. Oh no! :O 48 | function HeapNode(value){ 49 | let left, right; 50 | return { 51 | value, 52 | left, 53 | right 54 | }; 55 | } 56 | -------------------------------------------------------------------------------- /js/euler/001-multiples-3-and-5.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * If we list all the natural numbers below 10 that are multiples 4 | * of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23. 5 | * 6 | * Find the sum of all the multiples of 3 or 5 below 1000. 7 | * 8 | * 9 | */ 10 | 11 | function MultipleOf3And5Calculator() { 12 | 13 | return { 14 | sumAllMultiplesBelow 15 | }; 16 | 17 | function sumAllMultiplesBelow(top){ 18 | var sum = 0; 19 | for (var i = 0; i < top; i++) { // O(n) 20 | if (i % 3 === 0) { sum+=i; } 21 | else if (i % 5 === 0) { sum+=i; } 22 | } 23 | return sum; 24 | } 25 | 26 | // TODO: more efficient? 27 | // You don't need to traverse all items between 0 and top 28 | // You can just traverse the multiples of 3 and 5 29 | } 30 | -------------------------------------------------------------------------------- /js/euler/002-even-fibonacci-numbers.js: -------------------------------------------------------------------------------- 1 | /* 2 | * ## 002. Even Fibonacci numbers 3 | * 4 | * Each new term in the Fibonacci sequence is generated 5 | * by adding the previous two terms. By starting with 1 and 2, 6 | * the first 10 terms will be: 7 | * 8 | * 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ... 9 | * 10 | * By considering the terms in the Fibonacci sequence 11 | * whose values do not exceed four million, find the sum 12 | * of the even-valued terms. 13 | * 14 | */ 15 | 16 | function* getFibonacciIterator(n){ 17 | if ( n < 1) return; 18 | if ( n >= 1) yield 1; 19 | if ( n >= 2) yield 2; 20 | 21 | let previousPreviousNumber = 1, 22 | previousNumber = 2, 23 | currentNumber = previousPreviousNumber + previousNumber; 24 | 25 | while (currentNumber <= n){ 26 | yield currentNumber; 27 | previousPreviousNumber = previousNumber; 28 | previousNumber = currentNumber; 29 | currentNumber = previousPreviousNumber + previousNumber; 30 | } 31 | } 32 | 33 | class FibonacciCalculator{ 34 | sumEvenNumbersUnder(n){ 35 | let fibonacci = getFibonacciIterator(n); 36 | let numbers = Array.from(fibonacci); 37 | return numbers 38 | .filter(n => n % 2 === 0) 39 | .reduce((aggr, n) => aggr+n, /* aggr */ 0); 40 | } 41 | } 42 | 43 | 44 | -------------------------------------------------------------------------------- /js/graphs/undirectedGraph.js: -------------------------------------------------------------------------------- 1 | function UndirectedGraph(){ 2 | /* private stuff */ 3 | const state = { 4 | nodes: new Map() 5 | }; 6 | 7 | return Object.assign({}, 8 | state, { 9 | /* public API */ 10 | count, 11 | addNode, 12 | find, 13 | addEdge, 14 | findLengthOfShortestPathBetween 15 | }); 16 | 17 | function count(){ 18 | return state.nodes.size; 19 | } 20 | 21 | function addNode(value){ 22 | state.nodes.set(value, UndirectedGraphNode(value)); 23 | } 24 | 25 | function find(value){ 26 | if (!state.nodes.has(value)) throw Error(`Node {$value} not found`); 27 | return state.nodes.get(value); 28 | } 29 | 30 | function addEdge(name, from, to){ 31 | const fromNode = find(from); 32 | const toNode = find(to); 33 | fromNode.addEdge(name, toNode); 34 | } 35 | 36 | function findLengthOfShortestPathBetween(from, to){ 37 | // do next 38 | if (from === to) return 0; 39 | const nodeFrom = state.nodes.get(from); 40 | const nodeTo = state.nodes.get(to); 41 | 42 | l.log(`starting search from ${from} to ${to}`); 43 | let visitors = spawnInitialVisitors(nodeFrom, nodeTo); 44 | l.log(`spawned initial ${visitors.length} visitors`); 45 | let count = 0; 46 | while(visitors.some(v => v.isActive)){ 47 | visitors 48 | .filter(v => v.isActive) 49 | .forEach(v => v.moveNext()); 50 | spawnVisitors(visitors); 51 | l.log(`spawned visitors, now we have ${visitors.filter(v => v.isActive).length} active visitors`); 52 | l.log(`spawned visitors, now we have ${visitors.length} visitors`); 53 | count++; 54 | if (count > 3) break; 55 | } 56 | 57 | return visitors 58 | .map(v => v.length) 59 | .reduce((agg, length) => Math.min(agg, length), 9999); 60 | } 61 | 62 | function spawnInitialVisitors(from, to){ 63 | return Array 64 | .from(from.nodes) 65 | .map(n => Visitor({from: from, to: to, next: n})); 66 | } 67 | 68 | function spawnVisitors(visitors){ 69 | const activeVisitors = visitors.filter(v => v.isActive); 70 | l.log(`Active visitors ${activeVisitors.length}`); 71 | for(let v of activeVisitors){ 72 | for(let n of v.unvisitedNodes){ 73 | l.log(`Next unvisited node is ${n.value}`); 74 | l.log(`Destination is ${v.to.value}`); 75 | let newVisitor = Visitor({visited: [...v.visited], to: v.to, next: n}) 76 | visitors.push(newVisitor); 77 | } 78 | visitors.shift(v) 79 | } 80 | }; 81 | } 82 | 83 | function Visitor({from, to, next, visited = []}){ // TODO: test all this!! 84 | if (visited.length === 0 && !from) throw Error("from and visited can't be both empty"); 85 | if (visited.length === 0) visited.push(from); 86 | let currentNode = from || visited[visited.length-1]; 87 | 88 | l.log(`Create visitor with ${visited.map(v => v.value).join(',')} visited nodes. Moves next to ${next.value}`); 89 | 90 | return { 91 | visited, 92 | to, 93 | moveNext, 94 | get length() { return visited.length - 1;}, 95 | get isActive() { return !hasArrived() && hasUnvisitedNodes();}, 96 | get unvisitedNodes() { return getUnvisitedNodes();}, 97 | toString(){ 98 | return `currentNode: ${currentNode.value}, to: ${to.value}, next: ${next.value}`; 99 | } 100 | }; 101 | 102 | function moveNext(){ 103 | l.log(`Current node is ${currentNode.value}`); 104 | l.log(`Moving next to ${next.value}. Visited ${visited.map(v => v.value).join(',')}`); 105 | visited.push(next); 106 | currentNode = next; 107 | if (hasArrived()){l.log(`Arrived!!`);} 108 | } 109 | 110 | function hasArrived(){ 111 | return visited.map(v => v.value).includes(to.value); 112 | } 113 | 114 | function hasUnvisitedNodes(){ 115 | const adjacentNodes = Array.from(currentNode.nodes); 116 | return adjacentNodes 117 | .some(n => !visited.map(v => v.value).includes(n.value)); 118 | } 119 | 120 | function getUnvisitedNodes(){ 121 | const adjacentNodes = Array.from(currentNode.nodes); 122 | const unvisitedNodes = adjacentNodes 123 | .filter(n => !visited.map(v => v.value).includes(n.value)); 124 | l.log(`Have unvisited nodes ${unvisitedNodes.map(v => v.value).join(',')}`); 125 | return unvisitedNodes; 126 | } 127 | } 128 | 129 | 130 | function UndirectedGraphNode(value){ 131 | const state = { 132 | nodes: new Set() 133 | }; 134 | 135 | return Object.assign(state, { 136 | value, 137 | addEdge 138 | }); 139 | 140 | function addEdge(name, otherNode){ 141 | state.nodes.add(otherNode); 142 | otherNode.nodes.add(this); 143 | } 144 | } 145 | 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /js/sorting/BubbleSort.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * BubbleSort 4 | * 5 | * - while items to sort 6 | * - for each item 7 | * - compare with neighbor 8 | * - if bigger, swap 9 | * 10 | */ 11 | 12 | 13 | function bubbleSort(array){ 14 | // degenerate case 15 | if (array.length <= 1) return array; 16 | 17 | let itemsAreSwapped = false; 18 | do { 19 | itemsAreSwapped = false; 20 | for(let i = 0; i < array.length - 1; i++){ 21 | if (array[i] > array[i+1]){ 22 | let swappedItem = array[i]; 23 | array[i] = array[i+1]; 24 | array[i+1] = swappedItem; 25 | itemsAreSwapped = true; 26 | } 27 | } 28 | } while(itemsAreSwapped); 29 | 30 | return array; 31 | } 32 | 33 | -------------------------------------------------------------------------------- /js/sorting/InsertionSort.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Insertion Sort 3 | * 4 | * - for each item in the array 5 | * - for each previous item in the array 6 | * - if item is smaller than previous item swap it 7 | */ 8 | 9 | function insertionSort(array){ 10 | if (array.length <= 1) return array; 11 | 12 | for(let i = 1; i < array.length; i++) { // 1 while i<2 13 | for (let j = i; j > 0; j--) { // 1 while j>0... 14 | if (array[j] < array[j-1]){ // a[1] < a[0] 15 | swap({from:j, to: j-1, array}); // swap(1,0,array) 16 | } 17 | } 18 | } 19 | 20 | return array; 21 | 22 | function swap({from, to, array}){ 23 | let valueFrom = array[from]; 24 | array[from] = array[to]; 25 | array[to] = valueFrom; 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /js/sorting/MergeSort.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Merge Sort 3 | * 4 | * 5 | * 1. divide in half 6 | * 2. when array divided in subarrays of one element 7 | * 3. reconstruct (merge) in sort order 8 | * 9 | */ 10 | 11 | function mergeSort(array){ 12 | if (array.length <= 1) return array; 13 | 14 | // divide in half 15 | // reconstruct in order 16 | 17 | let middleIndex = parseInt(array.length/2); 18 | let firstHalf = array.slice(0, middleIndex); 19 | let secondHalf = array.slice(middleIndex, array.length); 20 | 21 | let sortedFirstHalf = mergeSort(firstHalf); 22 | let sortedSecondHalf = mergeSort(secondHalf); 23 | 24 | return sortSubArrays(sortedFirstHalf, sortedSecondHalf); 25 | } 26 | 27 | function sortSubArrays(firstHalf, secondHalf){ 28 | let array = [...firstHalf, ...secondHalf]; 29 | return insertionSort(array); // reusing insertionSort to order partially sorted arrays 30 | } 31 | -------------------------------------------------------------------------------- /js/sorting/QuickSort.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Quick Sort 3 | * 4 | * [5,3,1,4,2] 5 | * // pick 4 as pivot 6 | * [5,3,1,*4*,2] 7 | * // move elements so everything to the left is smaller than 4, everything to the right is larger than 4 8 | * [3,1,2,*4*,5] 9 | * // now we continue with the [3,1,2] subarray and pick 2 as pivot 10 | * [3,1,*2*] 11 | * [1,*2*,3] 12 | * // continue with the [5] subarray (it's sorted!) 13 | * // in fact all the array is sorted!! 14 | * [1,2,3,4,5] 15 | * 16 | */ 17 | 18 | function quickSort(array){ // [5, 3, 1, 4, 2] // [5,4,3,2] 19 | // base case 20 | if (array.length <= 1) return array; 21 | 22 | // pick a pivot 23 | let pivotIndex = parseInt(array.length/2); // 1 // 3 24 | 25 | let smallerItems = array.filter((item,idx) => item <= array[pivotIndex] && idx !== pivotIndex); // [] // [2] 26 | let biggerItems = array.filter((item,idx) => item > array[pivotIndex] && idx !== pivotIndex); // [5,3,4,2] // [5,4] 27 | 28 | let sortedSmallerItems = quickSort(smallerItems); // sort [] // sort [2] 29 | let sortedBiggerItems = quickSort(biggerItems); // sort [5,4,3,2] // sort [5, 4] 30 | 31 | return [...sortedSmallerItems, array[pivotIndex], ...sortedBiggerItems]; 32 | } 33 | 34 | 35 | // TODO: program it in-place! 36 | -------------------------------------------------------------------------------- /js/sorting/SelectionSort.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Selection Sort 3 | * 4 | * 1. Enumerate the array from the first unsorted item to the end 5 | * 2. Identify smallest item 6 | * 3. Swap smallest item with the first unsorted item 7 | */ 8 | 9 | 10 | function selectionSort(array){ 11 | if (array.length <= 1) return array; 12 | 13 | for(let i = 0; i < array.length; i++){ // i = 0 14 | let s = i; // as in Smallest // s = i = 0 15 | for(let j = i; j < array.length - 1; j++) { 16 | if (array[j+1] < array[s]) { // a[1] < a[0]... 17 | s = j+1; 18 | } 19 | } 20 | swap({from:s, to: i, array}); 21 | } 22 | 23 | return array; 24 | 25 | function swap({from, to, array}){ 26 | let toValue = array[to]; 27 | array[to] = array[from]; 28 | array[from] = toValue; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /js/strings/stringMatches.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Find string matches in a arbitrary string 4 | * 5 | * 6 | */ 7 | 8 | 9 | class StringMatcher { 10 | constructor(strategy){ 11 | this.strategy = strategy 12 | } 13 | findMatches(text, term){ 14 | if (term === '') return StringMatcher.NoMatches 15 | if (term === null || term === undefined) throw new Error("Invalid term"); 16 | 17 | return this.strategy.findMatches(text, term); 18 | } 19 | 20 | } 21 | StringMatcher.NoMatches = Object.freeze([]); 22 | 23 | 24 | class FindMatchesNaively{ 25 | findMatches(text, term){ 26 | const matches = []; 27 | for(let i = 0; i <= text.length - term.length; i++) 28 | if (this.findMatchAtIndex(i, text, term)) matches.push(new StringMatch(term, i)); 29 | return matches; 30 | } 31 | 32 | findMatchAtIndex(index, text, term){ 33 | let subtext = text.substr(index, term.length); 34 | let foundMatch = subtext === term; 35 | 36 | l.log(`Comparing [${subtext}] and [${term}]`); 37 | if (foundMatch) l.log(`Found Match!!`); 38 | 39 | return foundMatch 40 | } 41 | 42 | } 43 | 44 | class StringMatch{ 45 | constructor(term, index){ 46 | this.term = term; 47 | this.index = index; 48 | } 49 | } 50 | 51 | class FindMatchesWithBoyerMooreHoorst { 52 | 53 | findMatches(text, term) { 54 | l.log(`attempting to find ${term} inside ${text}`); 55 | 56 | const matches = []; 57 | const jumpsTable = this.buildTermJumpingTable(term); 58 | for(let i = 0; i < text.length - term.length; i++) { 59 | // compare from the last term letter 60 | l.log(`searching at index ${i}`); 61 | 62 | for(let j = i + term.length - 1; j >= i; j--) { 63 | l.log(`attempting match between term ${term[j-i]} and ${text[j]}`); 64 | if (text[j] === term[j-i]){ 65 | l.log(`matched letter ${text[j]} with ${term[j-i]}`); 66 | // match! 67 | if (j-i === 0) { 68 | // we are matching the last letter 69 | // so we've got a match 70 | l.log(`FOUND MATCH!!`); 71 | matches.push(new StringMatch(term, i)) 72 | i = i + term.length - 1; 73 | } 74 | } else { 75 | // no match => jump! 76 | l.log(`no match`); 77 | if (jumpsTable.has(text[j])){ 78 | let jumps = jumpsTable.get(text[j]) 79 | l.log(`Found ${text[j]} must jump ${jumps}`); 80 | i = i + jumps; 81 | } else { 82 | l.log(`Didn't find ${text[j]} must jump ${term.length}`); 83 | i = i + term.length - 1; 84 | } 85 | break; 86 | } 87 | } 88 | } 89 | 90 | return matches; 91 | } 92 | 93 | buildTermJumpingTable(term){ 94 | // TRUTH 95 | // any word => 5 96 | // store how many jumps you need to do when 97 | // failing a compare 98 | // T => 5 - 0 - 1 = 4 99 | // R => 5 - 1 - 1 = 3 100 | // U => 5 - 2 - 1 = 2 101 | // T => 5 - 3 - 1 = 1 102 | // H => 5 103 | const jumpsTable = new Map() 104 | for(let i = 0; i < term.lenght -1; i++){ 105 | jumpsTable.set(letter, term.length - i - 1) 106 | } 107 | return jumpsTable; 108 | } 109 | } 110 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Mon Jan 11 2016 00:32:44 GMT+0100 (CET) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['jasmine'], 14 | 15 | 16 | // list of files / patterns to load in the browser 17 | files: [ 18 | 'node_modules/babel-polyfill/dist/polyfill.js', 19 | 'utils/**/*.js', 20 | 'js/**/*.js', 21 | 'specs/**/*.spec.js' 22 | ], 23 | 24 | 25 | // list of files to exclude 26 | exclude: [ 27 | ], 28 | 29 | 30 | // preprocess matching files before serving them to the browser 31 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 32 | preprocessors: { 33 | 'utils/**/*.js': ['babel'], 34 | 'js/**/*.js': ['babel'], 35 | 'specs/**/*.spec.js': ['babel'] 36 | }, 37 | 38 | babelPreprocessor: { 39 | options: { 40 | presets: ['es2015'], // use the es2015 preset 41 | sourceMap: 'inline' // inline source maps inside compiled files 42 | }, 43 | filename: function (file) { 44 | return file.originalPath.replace(/\.js$/, '.es5.js'); 45 | }, 46 | sourceFileName: function (file) { 47 | return file.originalPath; 48 | } 49 | }, 50 | 51 | // possible values: 'dots', 'progress' 52 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 53 | reporters: ['progress'], 54 | 55 | 56 | // web server port 57 | port: 9876, 58 | 59 | 60 | // enable / disable colors in the output (reporters and logs) 61 | colors: true, 62 | 63 | 64 | // level of logging 65 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 66 | logLevel: config.LOG_INFO, 67 | 68 | 69 | // enable / disable watching file and executing tests whenever any file changes 70 | autoWatch: true, 71 | 72 | 73 | // start these browsers 74 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 75 | browsers: ['PhantomJS'], 76 | 77 | 78 | // Continuous Integration mode 79 | // if true, Karma captures browsers, runs the tests and exits 80 | singleRun: false, 81 | 82 | // Concurrency level 83 | // how many browser should be started simultaneous 84 | concurrency: Infinity 85 | }) 86 | } 87 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AlgorithmsAndDataStructuresInJavaScript", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/Vintharas/AlgorithmsAndDataStructuresInJavaScript.git" 12 | }, 13 | "author": "Vintharas", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/Vintharas/AlgorithmsAndDataStructuresInJavaScript/issues" 17 | }, 18 | "homepage": "https://github.com/Vintharas/AlgorithmsAndDataStructuresInJavaScript", 19 | "devDependencies": { 20 | "babel-polyfill": "^6.3.14", 21 | "babel-preset-es2015": "^6.3.13", 22 | "jasmine-core": "^2.4.1", 23 | "karma": "^0.13.19", 24 | "karma-babel-preprocessor": "^6.0.1", 25 | "karma-chrome-launcher": "^0.2.2", 26 | "karma-jasmine": "^0.3.6", 27 | "karma-phantomjs-launcher": "^0.2.3", 28 | "phantomjs": "^1.9.19" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /specs/LinkedLists/SimpleLinkedLists.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Linked Lists 3 | * 4 | * - create linked list with 1 item (head) 5 | * - having an empty list 6 | * - should be able to add an item 7 | * - should throw an error when trying to remove first item 8 | * - having a list with an item 9 | * - hasItem returns true if the list has that item 10 | * - hasItem returns false if the list doesn't have that item 11 | * - should be able to add an item to an existing list 12 | * - should be able to add multiple items to the list 13 | * - should be able to insert an item at the beginning 14 | * - having a list with multiple items 15 | * - hasItem returns true if the list has an item 16 | * - hasItem returns false if the list doesn't have an item 17 | * - remove first item from the list by value 18 | * - remove an item from the list by value 19 | * - should throw error when trying to remove an item that doesn't exist 20 | * - remove first item of the list 21 | * - get the nth-to-last element of the list 22 | * 23 | */ 24 | 25 | describe("Linked Lists", () => { 26 | 27 | it("Should be able to create an empty Linked List", () => { 28 | // Arrange, Act 29 | var l = LinkedList(); 30 | // Assert 31 | expect(l.head).toBe(null); 32 | }); 33 | 34 | it("Should be able to create a Linked List from a single item which would be its head", () => { 35 | // Arrange, Act 36 | var l = LinkedList(5); 37 | // Assert 38 | expect(l.head).toBeDefined(); 39 | expect(l.head.value).toBe(5); 40 | }); 41 | 42 | describe("Given that I have an empty linked list", () => { 43 | 44 | it("should be able to add an item to it", () => { 45 | // Arrange 46 | var list = LinkedList(); 47 | // Act 48 | list.append(1); 49 | // Assert 50 | expect(list.head).toBeDefined(); 51 | expect(list.head.value).toBe(1); 52 | }); 53 | 54 | it("should throw an exception when trying to remove the first item", () => { 55 | // Arrange 56 | var list = LinkedList(); 57 | // Act, Assert 58 | expect(() => list.removeFirst()).toThrowError(); 59 | }); 60 | 61 | }); 62 | 63 | describe("Given that I have a linked list with an item", () => { 64 | var list; 65 | 66 | beforeEach(() => { 67 | list = LinkedList(5); 68 | }); 69 | 70 | describe("hasItem", () => { 71 | it("should return true when an element is within the list", () => { 72 | // Arrange, Act, Assert 73 | expect(list.hasItem(5)).toBe(true); 74 | }); 75 | 76 | it("should return false when an element is within the list", () => { 77 | // Arrange, Act, Assert 78 | expect(list.hasItem(7)).toBe(false); 79 | }); 80 | }); 81 | 82 | it("should be able to add another item at the end of the list", () => { 83 | // Arrange, Act 84 | list.append(3); 85 | // Assert 86 | var head = list.head; 87 | expect(head.value).toBe(5); 88 | expect(head.next).toBeDefined(); 89 | expect(head.next.value).toBe(3); 90 | }); 91 | 92 | it("should be able to add multiple items at the end of the list", () => { 93 | // Arrange, Act 94 | list.append(3); 95 | list.append(6); 96 | list.append(8); 97 | // Assert 98 | var head = list.head; 99 | expect(head.value).toBe(5); 100 | expect(head.next.value).toBe(3); 101 | expect(head.next.next.value).toBe(6); 102 | expect(head.next.next.next.value).toBe(8); 103 | }); 104 | 105 | it("should be able to insert another item at the beginning of the list", () => { 106 | // Arrange, Act 107 | list.insert(1); 108 | // Assert 109 | var head = list.head; 110 | expect(head.value).toBe(1); 111 | }); 112 | }); 113 | 114 | describe("Given that I have a linked list with multiple items", () => { 115 | var list; 116 | beforeEach(() => { 117 | list = LinkedList(5); // 5 => 3 => 8 => 9 118 | list.append(3); 119 | list.append(8); 120 | list.append(9); 121 | }); 122 | 123 | describe("hasItem", () => { 124 | it("should return true when an element is within the list", () => { 125 | // Arrange, Act, Assert 126 | expect(list.hasItem(8)).toBe(true); 127 | }); 128 | 129 | it("should return false when an element is within the list", () => { 130 | // Arrange, Act, Assert 131 | expect(list.hasItem(100)).toBe(false); 132 | }); 133 | }); 134 | 135 | it("should be able to remove the first item from the list by value", () => { 136 | // Arrange, Act 137 | list.remove(5); 138 | // Assert 139 | expect(list.hasItem(5)).toBe(false, 'list has item 5'); 140 | expect(list.hasItem(3)).toBe(true, 'list doesnt have item 3'); 141 | expect(list.hasItem(8)).toBe(true, 'list doesnt have item 8'); 142 | expect(list.hasItem(9)).toBe(true, 'list doesnt have item 9'); 143 | }); 144 | 145 | it("should be able to remove any item from the list by value", () => { 146 | // Arrange, Act 147 | list.remove(3); 148 | // Assert 149 | expect(list.hasItem(5)).toBe(true, 'list doesnt have item 5'); 150 | expect(list.hasItem(3)).toBe(false, 'list has item 3'); 151 | expect(list.hasItem(8)).toBe(true, 'list doesnt have item 8'); 152 | expect(list.hasItem(9)).toBe(true, 'list doesnt have item 9'); 153 | }); 154 | 155 | it("should throw an error if you try to remove an item that is not in the list", () => { 156 | // Arrange, Act, Assert 157 | expect(() => list.remove(111)).toThrowError(); 158 | }); 159 | 160 | it("should be able to remove the first item from a list", () => { 161 | // Arrange, Act 162 | var value = list.removeFirst(); 163 | // Assert 164 | expect(value).toBe(5); 165 | }); 166 | 167 | it("should be able to get the last element of the list", () => { 168 | // Arrange, Act 169 | var value = list.getLast(); 170 | // Assert 171 | expect(value).toBe(9) 172 | }); 173 | 174 | it("should be able to get the second to last element of the list", () => { 175 | // Arrange, Act 176 | var value = list.getNthLast(2); 177 | // Assert 178 | expect(value).toBe(8); 179 | }); 180 | 181 | it("should be able to get the third to last element of the list", () => { 182 | // Arrange, Act 183 | var value = list.getNthLast(3); 184 | // Assert 185 | expect(value).toBe(3); 186 | }); 187 | 188 | it("should throw an error when you try to get the seventh to last element of the list which doesn't exist", () => { 189 | // Arrange, Act, Assert 190 | expect(() => list.getNthLast(100)).toThrowError(); 191 | }); 192 | 193 | 194 | }); 195 | 196 | }); 197 | 198 | /* 199 | * Implement a Stack using a linked list (LIFO data structure) 200 | * 201 | * - pop 202 | * - push 203 | * - hasItem => for testing 204 | * - isEmpty 205 | * 206 | */ 207 | 208 | describe("Stack", () => { 209 | 210 | it("should be able to create an empty stack", () => { 211 | // Arrange, Act 212 | var stack = Stack(); 213 | // Assert 214 | expect(stack).not.toBeUndefined(); 215 | }); 216 | 217 | it("should be able to push an item to the stack", () => { 218 | // Arrange 219 | var stack = Stack(); 220 | // Act 221 | stack.push(10); 222 | // Assert 223 | expect(stack.hasItem(10)).toBe(true); 224 | }); 225 | 226 | it("should be able to push multiple items", () => { 227 | // Arrange 228 | var stack = Stack(); 229 | // Act 230 | stack.push(10); 231 | stack.push(20); 232 | stack.push(30); 233 | // Assert 234 | expect(stack.hasItem(10)).toBe(true); 235 | expect(stack.hasItem(20)).toBe(true); 236 | expect(stack.hasItem(30)).toBe(true); 237 | }); 238 | 239 | it("should be able to pop an item, that is, remove the last item added to the stack", () => { 240 | // Arrange 241 | var stack = Stack(); 242 | // Act 243 | stack.push(1); 244 | stack.push(2); 245 | var value = stack.pop(); 246 | // Assert 247 | expect(value).toBe(2); 248 | }); 249 | 250 | describe("isEmpty", () => { 251 | describe("When we have an empty stack", () => { 252 | it("Should return true", () => { 253 | // Arrange 254 | var stack = Stack(); 255 | // Act 256 | var isEmpty = stack.isEmpty(); 257 | // Assert 258 | expect(isEmpty).toBe(true); 259 | }); 260 | }); 261 | 262 | describe("When we have an stack with items", () => { 263 | it("Should return false", () => { 264 | // Arrange 265 | var stack = Stack(); 266 | stack.push(22); 267 | // Act 268 | var isEmpty = stack.isEmpty(); 269 | // Assert 270 | expect(isEmpty).toBe(false); 271 | }); 272 | }); 273 | 274 | describe("When we have an stack with items and then remove all items", () => { 275 | it("Should return true", () => { 276 | // Arrange 277 | var stack = Stack(); 278 | stack.push(22); 279 | stack.pop(); 280 | // Act 281 | var isEmpty = stack.isEmpty(); 282 | // Assert 283 | expect(isEmpty).toBe(true); 284 | }); 285 | }); 286 | }); 287 | 288 | }); 289 | -------------------------------------------------------------------------------- /specs/Trees/AVLTree.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * AVL Tree (self-balancing tree) 3 | * 4 | * 5 | * BST are awesome for searching, adding and removing items with a 6 | * runtime complexity of O(logn). But that's only when they 7 | * are balanced. In the worst case scenario, when you add 1, 2, 3 8 | * 4, 5, etc, a BST results in a linked list. In that case all 9 | * these operations suddenly become O(n). 10 | * 11 | * AVL to the rescue!!! AVL is a self-balancing tree that runs a 12 | * balancing algorithm when adding or removing nodes results in 13 | * the left or right subtree heights differing in more than 1. 14 | * 15 | * 16 | * - AVLTree 17 | * - Create new empty AVLTree 18 | * - Create new AVLTree with root 19 | * - Add node 20 | * - Remove node 21 | * - Find node (like BST) 22 | * - Clear (like BST) 23 | * - Count (like BST) 24 | * - Traversals (like BST) 25 | * 26 | * 27 | * This is a toughie to implement :) 28 | * 29 | */ 30 | 31 | describe("AVLTree", () => { 32 | 33 | describe("Given that I have no tree", () => { 34 | it("Should be able to create an empty tree", () => { 35 | // Arrange, Act 36 | const tree = new AVLTree(); 37 | // Assert 38 | expect(tree.root).toBeUndefined(); 39 | }); 40 | 41 | it("Should be able to create a tree with a root", () => { 42 | // Arrange, Act 43 | const tree = new AVLTree(10); 44 | // Assert 45 | expect(tree.root).toBeDefined(); 46 | expect(tree.root.value).toBe(10); 47 | }); 48 | }); 49 | 50 | describe("Given that I have an empty tree", () => { 51 | let tree; 52 | beforeEach(() => tree = new AVLTree()); 53 | it("Should be able to add an item that'll be its root", () => { 54 | // Arrange, Act 55 | tree.add(11); 56 | // Assert 57 | expect(tree.root).toBeDefined(); 58 | expect(tree.root.value).toBe(11); 59 | }); 60 | }); 61 | 62 | describe("Given that I have a tree with a root", () => { 63 | let tree; 64 | beforeEach(() => tree = new AVLTree(10)); 65 | describe("When adding an item smaller than the root", () => { 66 | it("Should be added as left child", () => { 67 | // Arrange, Act 68 | tree.add(5); 69 | // Assert 70 | expect(tree.root.left).toBeDefined(); 71 | expect(tree.root.left.value).toBe(5); 72 | }); 73 | }); 74 | 75 | describe("When adding an item bigger than the root", () => { 76 | it("Should be added as right child", () => { 77 | // Arrange, Act 78 | tree.add(50); 79 | // Assert 80 | expect(tree.root.right).toBeDefined(); 81 | expect(tree.root.right.value).toBe(50); 82 | }); 83 | }); 84 | 85 | describe("When adding an item equal than the root", () => { 86 | it("Should be added as right child", () => { 87 | // Arrange, Act 88 | tree.add(10); 89 | // Assert 90 | expect(tree.root.right).toBeDefined(); 91 | expect(tree.root.right.value).toBe(10); 92 | }); 93 | }); 94 | 95 | }); 96 | 97 | describe("Given that I have a tree with a root left and right children", () => { 98 | let tree; 99 | beforeEach(() => { 100 | tree = new AVLTree(10); 101 | tree.add(5); 102 | tree.add(15); 103 | /* 104 | * 105 | * 10 106 | * / \ 107 | * 5 15 108 | * 109 | */ 110 | }); 111 | 112 | describe("When adding an item smaller than 5", () => { 113 | it("Should be added as root-left-left child", () => { 114 | // Arrange, Act 115 | tree.add(1); 116 | // Assert 117 | expect(tree.root.left.left).toBeDefined(); 118 | expect(tree.root.left.left.value).toBe(1); 119 | }); 120 | }); 121 | 122 | describe("When adding an item between 5 and 10", () => { 123 | it("Should be added as root-left-right child", () => { 124 | // Arrange, Act 125 | tree.add(6); 126 | // Assert 127 | expect(tree.root.left.right).toBeDefined(); 128 | expect(tree.root.left.right.value).toBe(6); 129 | }); 130 | }); 131 | 132 | describe("When adding an item between 10 and 15", () => { 133 | it("Should be added as root-right-left child", () => { 134 | // Arrange, Act 135 | tree.add(11); 136 | // Assert 137 | expect(tree.root.right.left).toBeDefined(); 138 | expect(tree.root.right.left.value).toBe(11); 139 | }); 140 | }); 141 | 142 | describe("When adding an item greater than 15", () => { 143 | it("Should be added as root-right-right child", () => { 144 | // Arrange, Act 145 | tree.add(20); 146 | // Assert 147 | expect(tree.root.right.right).toBeDefined(); 148 | expect(tree.root.right.right.value).toBe(20); 149 | }); 150 | }); 151 | 152 | }); 153 | 154 | 155 | describe("Tree balancing", () => { 156 | describe("Given that I have a tree with a root and a right child", () => { 157 | let tree; 158 | beforeEach(() => { 159 | tree = new AVLTree(10); 160 | tree.add(15); 161 | /* 162 | * 163 | * 10 164 | * \ 165 | * 15 166 | * 167 | */ 168 | }); 169 | 170 | // Left Rotation 171 | describe("When I add an item greater than 15", () => { 172 | it("Should rebalance the tree", () => { 173 | // Arrange, Act 174 | tree.add(20); 175 | // Assert 176 | // 177 | // 15 178 | // / \ 179 | // 10 20 180 | // 181 | expect(tree.root.value).toBe(15); 182 | expect(tree.root.right.value).toBe(20); 183 | expect(tree.root.left.value).toBe(10); 184 | }); 185 | }); 186 | 187 | // Left Rotation 188 | describe("When I add 20, 30, 40", () => { 189 | it("Should rebalance the tree", () => { 190 | // Arrange, Act 191 | tree.add(20); 192 | tree.add(30); 193 | tree.add(40); 194 | // Assert 195 | // 196 | // 15 197 | // / \ 198 | // 10 30 199 | // / \ 200 | // 20 40 201 | // 202 | expect(tree.root.value).toBe(15); 203 | expect(tree.root.left.value).toBe(10); 204 | expect(tree.root.right.value).toBe(30); 205 | expect(tree.root.right.left.value).toBe(20); 206 | expect(tree.root.right.right.value).toBe(40); 207 | }); 208 | }); 209 | 210 | }); 211 | }); 212 | 213 | 214 | 215 | }); 216 | -------------------------------------------------------------------------------- /specs/Trees/BinarySearchTree.spec.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * Binary Search Tree 4 | * 5 | * A Tree where each node can have up 2 children and 6 | * where for each node: 7 | * - the left child node's value must be equal or smaller 8 | * - the right child node's value must be equal or greater. 9 | * 10 | * 11 | * - Create a tree 12 | * - Empty 13 | * - With one node 14 | * - Having an empty tree 15 | * - add item to tree which becomes the root 16 | * - Having a tree with a root 17 | * - add item smaller than root creates left child 18 | * - add item greater than root creates right child 19 | * - add item equal to root, try to add to left then try right, otherwise try under left child 20 | * - Having a tree with two leaves 21 | * - add item 22 | * - add item (implement recursively) 23 | * - Find 24 | * - find item 25 | * - throw error if item is not found 26 | * - Traversal 27 | * - pre-order 28 | * - in-order 29 | * - post-order 30 | */ 31 | 32 | describe("Binary Search Tree", () => { 33 | 34 | describe("Given that I have no tree", () => { 35 | 36 | it("I Should be able to create a empty tree with no root node", () => { 37 | // Arrange, Act 38 | let tree = Tree(); 39 | // Assert 40 | expect(tree.root).not.toBeDefined(); 41 | }); 42 | 43 | it("I Should be able to create a tree with a root node", () => { 44 | // Arrange, Act 45 | let tree = Tree(12); 46 | // Assert 47 | expect(tree.root).toBeDefined(); 48 | expect(tree.root.value).toBe(12); 49 | }); 50 | 51 | }); 52 | 53 | describe("Given that I have an empty tree", () => { 54 | let tree; 55 | 56 | beforeEach(() => tree = Tree()); 57 | 58 | describe("When I add an item to the tree", () => { 59 | it("Should become its root", () => { 60 | // Arrange, Act 61 | tree.add(10); 62 | // Assert 63 | expect(tree.root).toBeDefined(); 64 | expect(tree.root.value).toBe(10); 65 | }); 66 | }); 67 | }); 68 | 69 | describe("Given that I have a tree with a root", () => { 70 | let tree; 71 | 72 | beforeEach(() => tree = Tree(10)); 73 | 74 | describe("When I add an item greater than the root", () => { 75 | it("Should be added as its right child", () => { 76 | // Arrange, Act 77 | tree.add(50); 78 | // Assert 79 | // 80 | // 10 81 | // \ 82 | // 50 83 | // 84 | expect(tree.root.right).toBeDefined(); 85 | expect(tree.root.right.value).toBe(50); 86 | }); 87 | }); 88 | 89 | describe("When I add an item smaller than the root", () => { 90 | it("Should be added as its left child", () => { 91 | // Arrange, Act 92 | tree.add(2); 93 | // Assert 94 | // 95 | // 10 96 | // / 97 | // 2 98 | // 99 | expect(tree.root.left).toBeDefined(); 100 | expect(tree.root.left.value).toBe(2); 101 | }); 102 | }); 103 | 104 | describe("When I add an item equal than the root", () => { 105 | describe("When there's no node as left child", () => { 106 | it ("Should be added as its left child", () => { 107 | // Arrange, Act 108 | tree.add(10); 109 | // Assert 110 | // 111 | // 10 112 | // / 113 | // 10 114 | // 115 | expect(tree.root.left).toBeDefined(); 116 | expect(tree.root.left.value).toBe(10); 117 | }); 118 | }); 119 | 120 | describe("When there's a node as its left child", () => { 121 | beforeEach(() => tree.add(1)); 122 | it("should be added as its left child's right child", () => { 123 | // Arrange, Act 124 | tree.add(10); 125 | // Assert 126 | // 10 127 | // / 128 | // 1 129 | // \ 130 | // 10 131 | // 132 | // 133 | expect(tree.root.left.right).toBeDefined(); 134 | expect(tree.root.left.right.value).toBe(10); 135 | }); 136 | }); 137 | 138 | }); 139 | 140 | }); 141 | 142 | 143 | describe("Given that I have a tree with a node and two leaves", () => { 144 | let tree; 145 | 146 | beforeEach( () => { 147 | tree = Tree(10); 148 | tree.add(5); 149 | tree.add(50); 150 | }); 151 | 152 | describe("When I add an item smaller than the root", () => { 153 | describe("And smaller than its left child", () => { 154 | it("Should become the roots left child's child", () => { 155 | // Arrange, Act 156 | tree.add(1); 157 | // Assert 158 | expect(tree.root.left.left).toBeDefined(); 159 | expect(tree.root.left.left.value).toBe(1); 160 | }); 161 | }); 162 | }); 163 | 164 | describe("When I add an item smaller than the root", () => { 165 | describe("And smaller than its left child", () => { 166 | describe("And I want to solve this recursively", () => { 167 | it("Should become the roots left child's child", () => { 168 | // Arrange, Act 169 | tree.addRecursively(1); 170 | // Assert 171 | expect(tree.root.left.left).toBeDefined(); 172 | expect(tree.root.left.left.value).toBe(1); 173 | }); 174 | }); 175 | }); 176 | 177 | describe("And bigger than its right child", () => { 178 | describe("And I want to solve this recursively", () => { 179 | it("Should become the roots right child's child", () => { 180 | // Arrange, Act 181 | tree.addRecursively(6); 182 | // Assert 183 | expect(tree.root.left.right).toBeDefined(); 184 | expect(tree.root.left.right.value).toBe(6); 185 | }); 186 | }); 187 | }); 188 | }); 189 | 190 | }); 191 | 192 | describe("addMany", () => { 193 | describe("Given that I have an empty tree", () => { 194 | describe("When I add many items at once", () => { 195 | it("The first one should be the tree root and the rest should be ordered according to size" , () => { 196 | // Arrange 197 | let tree = Tree(); 198 | // Act 199 | tree.addMany(10, 1, 2, 20, 30); 200 | /* 201 | * 10 202 | * / \ 203 | * 1 20 204 | * \ \ 205 | * 2 30 206 | */ 207 | // Assert 208 | expect(tree.root.value).toBe(10); 209 | expect(tree.root.left.value).toBe(1); 210 | expect(tree.root.left.right.value).toBe(2); 211 | expect(tree.root.right.value).toBe(20); 212 | expect(tree.root.right.right.value).toBe(30); 213 | }); 214 | }); 215 | }); 216 | }); 217 | 218 | describe("BST Travesals", () => { 219 | describe("Given that I have a BST with some nodes", () => { 220 | let tree; 221 | 222 | beforeEach(() => { 223 | tree = Tree(); 224 | tree.addMany(10, 1, 2, 20, 30); 225 | /* 226 | * 10 227 | * / \ 228 | * 1 20 229 | * \ \ 230 | * 2 30 231 | */ 232 | }); 233 | 234 | it("I should be able to traverse it in Pre-order", () => { 235 | // Arrange 236 | let nodesTraversed = []; 237 | // Act 238 | tree.traversePreOrder((n) => nodesTraversed.push(n.value)); 239 | // Assert 240 | expect(nodesTraversed.join(',')).toBe('10,1,2,20,30'); 241 | }); 242 | 243 | it("I should be able to traverse it in Pre-order implemented iteratively", () => { 244 | // Arrange 245 | let nodesTraversed = []; 246 | // Act 247 | tree.traversePreOrderIteratively((n) => nodesTraversed.push(n.value)); 248 | // Assert 249 | expect(nodesTraversed.join(',')).toBe('10,1,2,20,30'); 250 | }); 251 | 252 | it("I should be able to traverse it in Pre-order implemented iteratively with another tree", () => { 253 | // Arrange 254 | let anotherTree = Tree(); 255 | anotherTree.addMany(10, 1, 0, 2, 20, 15, 14, 16, 30, 25, 35); 256 | /* 257 | * 10 258 | * / \ 259 | * 1 20 260 | * / \ / \ 261 | * 0 2 15 30 262 | * / \ / \ 263 | * 14 16 25 35 264 | * 265 | */ 266 | let nodesTraversed = []; 267 | // Act 268 | anotherTree.traversePreOrderIteratively((n) => nodesTraversed.push(n.value)); 269 | // Assert 270 | expect(nodesTraversed.join(',')).toBe('10,1,0,2,20,15,14,16,30,25,35'); 271 | }); 272 | 273 | 274 | it("I should be able to traverse it in In-order", () => { 275 | // Arrange 276 | let nodesTraversed = []; 277 | // Act 278 | tree.traverseInOrder((n) => nodesTraversed.push(n.value)); 279 | // Assert 280 | expect(nodesTraversed.join(',')).toBe('1,2,10,20,30'); 281 | }); 282 | 283 | it("I should be able to traverse it in Post-order", () => { 284 | // Arrange 285 | let nodesTraversed = []; 286 | // Act 287 | tree.traversePostOrder((n) => nodesTraversed.push(n.value)); 288 | // Assert 289 | expect(nodesTraversed.join(',')).toBe('2,1,30,20,10'); 290 | }); 291 | 292 | }); 293 | }); 294 | 295 | describe("find", () => { 296 | describe("Given that I have an BST with several nodes", () => { 297 | describe("When I try to find an item that is within the tree", () => { 298 | it("Should be able to find it" , () => { 299 | // Arrange 300 | let tree = Tree(); 301 | tree.addMany(10, 1, 2, 20, 30); 302 | /* 303 | * 10 304 | * / \ 305 | * 1 20 306 | * \ \ 307 | * 2 30 308 | */ 309 | // Act 310 | var node = tree.find(30); 311 | // Assert 312 | expect(node.value).toBe(30); 313 | }); 314 | }); 315 | 316 | describe("When I try to find an item that is NOT within the tree", () => { 317 | it("Should throw an error" , () => { 318 | // Arrange 319 | let tree = Tree(); 320 | tree.addMany(10, 1, 2, 20, 30); 321 | /* 322 | * 10 323 | * / \ 324 | * 1 20 325 | * \ \ 326 | * 2 30 327 | */ 328 | // Act, Assert 329 | expect(() => tree.find(10000)).toThrowError(); 330 | }); 331 | }); 332 | }); 333 | }); 334 | 335 | describe("findRecursively", () => { 336 | describe("Given that I have an BST with several nodes", () => { 337 | describe("When I try to find an item that is within the tree", () => { 338 | it("Should be able to find it" , () => { 339 | // Arrange 340 | let tree = Tree(); 341 | tree.addMany(10, 1, 2, 20, 30); 342 | /* 343 | * 10 344 | * / \ 345 | * 1 20 346 | * \ \ 347 | * 2 30 348 | */ 349 | // Act 350 | var node = tree.findRecursively(30); 351 | // Assert 352 | expect(node.value).toBe(30); 353 | }); 354 | }); 355 | 356 | describe("When I try to find an item that is NOT within the tree", () => { 357 | it("Should throw an error" , () => { 358 | // Arrange 359 | let tree = Tree(); 360 | tree.addMany(10, 1, 2, 20, 30); 361 | /* 362 | * 10 363 | * / \ 364 | * 1 20 365 | * \ \ 366 | * 2 30 367 | */ 368 | // Act, Assert 369 | expect(() => tree.findRecursively(10000)).toThrowError(); 370 | }); 371 | }); 372 | }); 373 | }); 374 | 375 | 376 | }); 377 | -------------------------------------------------------------------------------- /specs/Trees/Heap.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Binary Heap :) - Max-heap 3 | * 4 | * A Tree where each node can have up 2 children and 5 | * where for each node: 6 | * - The node value is larger or equal to that of its children (max-heap, for min-heap is smaller or equal). 7 | * 8 | * 9 | * - Heap 10 | * - Create new heap 11 | * - Create new heap with node 12 | * 13 | */ 14 | 15 | describe("Heap", () => { 16 | 17 | describe("Given that I have no heap", () => { 18 | it("should be able to create an empty one", () => { 19 | // Arrange, Act 20 | let heap = Heap(); 21 | // Assert 22 | expect(heap.root).toBeUndefined(); 23 | }); 24 | 25 | it("should be able to create a heap with a root", () => { 26 | // Arrange, Act 27 | let heap = Heap(100); 28 | // Assert 29 | expect(heap.root).toBeDefined(); 30 | expect(heap.root.value).toBe(100); 31 | }); 32 | }); 33 | 34 | describe("Given that I have an empty heap", () => { 35 | it("Should be able to add a root node", () => { 36 | // Arrange 37 | let heap = Heap(); 38 | // Act 39 | heap.add(10); 40 | // Assert 41 | expect(heap.root).toBeDefined(); 42 | expect(heap.root.value).toBe(10); 43 | }); 44 | }); 45 | 46 | describe("Given that I have an heap with a root node", () => { 47 | var heap; 48 | 49 | beforeEach(() => heap = Heap(10)); 50 | 51 | describe("When I add a smaller value", () => { 52 | it("Should add a new left child node to the root", () => { 53 | // Arrange, Act 54 | heap.add(1); 55 | // Assert 56 | expect(heap.root.left).toBeDefined(); 57 | expect(heap.root.left.value).toBe(1); 58 | }); 59 | }); 60 | 61 | describe("And a left child node", () => { 62 | 63 | beforeEach(() => heap.add(1)); 64 | 65 | describe("When I add a smaller value", () => { 66 | it("Should add a new right child node to the root", () => { 67 | // Arrange, Act 68 | heap.add(2); 69 | // Assert 70 | expect(heap.root.right).toBeDefined(); 71 | expect(heap.root.right.value).toBe(2); 72 | }); 73 | 74 | }); 75 | 76 | describe("When I add a bigger value", () => { 77 | it("Should become the root and the current value should go down the tree", () => { 78 | // Arrange, Act 79 | heap.add(1000); 80 | // Assert 81 | expect(heap.root.value).toBe(1000); 82 | expect(heap.root.right.value).toBe(10); 83 | }); 84 | }); 85 | }); 86 | 87 | describe("When I add a bigger value", () => { 88 | it("Should become the root node and the current value should go down the tree", () => { 89 | // Arrange, Act 90 | heap.add(1000); 91 | // Assert 92 | expect(heap.root.value).toBe(1000); 93 | expect(heap.root.left.value).toBe(10); 94 | }); 95 | }); 96 | 97 | }); 98 | 99 | }); 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /specs/Trees/readme.markdown: -------------------------------------------------------------------------------- 1 | # Tree Data Structures and Problems 2 | 3 | ## Binary Search Tree 4 | 5 | Binary Search Tree 6 | 7 | A Tree where each node can have up 2 children and 8 | where for each node: 9 | - the left child node's value must be equal or smaller 10 | - the right child node's value must be equal or greater. 11 | 12 | 13 | - Create a tree 14 | - Empty 15 | - With one node 16 | - Having an empty tree 17 | - add item to tree which becomes the root 18 | - Having a tree with a root 19 | - add item smaller than root creates left child 20 | - add item greater than root creates right child 21 | - add item equal to root, try to add to left then try right, otherwise try under left child 22 | - Having a tree with two leaves 23 | - add item 24 | - add item (implement recursively) 25 | - Find 26 | - find item 27 | - throw error if item is not found 28 | - Traversal 29 | - pre-order 30 | - in-order 31 | - post-order 32 | 33 | -------------------------------------------------------------------------------- /specs/euler/001-multiples-3-and-5.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * If we list all the natural numbers below 10 that are multiples 4 | * of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23. 5 | * 6 | * Find the sum of all the multiples of 3 or 5 below 1000. 7 | * 8 | * 9 | */ 10 | 11 | describe("Multiples of 3 and 5", () => { 12 | 13 | // TODO: How the heck do you do parameterized tests in jasmine? XD 14 | describe("Given the number 4", () => { 15 | it("should get the sum of all multiples of 3 and 5 under 4", () => { 16 | // Arrange 17 | var calculator = MultipleOf3And5Calculator(); 18 | // Act 19 | var sum = calculator.sumAllMultiplesBelow(4); 20 | // Assert 21 | expect(sum).toBe(3); 22 | }); 23 | }); 24 | 25 | describe("Given the number 6", () => { 26 | it("should get the sum of all multiples of 3 and 5 under 6", () => { 27 | // Arrange 28 | var calculator = MultipleOf3And5Calculator(); 29 | // Act 30 | var sum = calculator.sumAllMultiplesBelow(6); 31 | // Assert 32 | expect(sum).toBe(8); 33 | }); 34 | }); 35 | 36 | describe("Given the number 10", () => { 37 | it("should get the sum of all the multiples of 3 and 5 under 10", () => { 38 | // Arrange 39 | var calculator = MultipleOf3And5Calculator(); 40 | // Act 41 | var sum = calculator.sumAllMultiplesBelow(10); 42 | // Assert 43 | expect(sum).toBe(23); // 3 + 6 + 9 + 5 44 | }); 45 | }); 46 | 47 | describe("Given the number 11", () => { 48 | it("should get the sum of all the multiples of 3 and 5 under 11", () => { 49 | // Arrange 50 | var calculator = MultipleOf3And5Calculator(); 51 | // Act 52 | var sum = calculator.sumAllMultiplesBelow(11); 53 | // Assert 54 | expect(sum).toBe(33); 55 | }); 56 | }); 57 | 58 | describe("Given the number 13", () => { 59 | it("should get the sum of all the multiples of 3 and 5 under 13", () => { 60 | // Arrange 61 | var calculator = MultipleOf3And5Calculator(); 62 | // Act 63 | var sum = calculator.sumAllMultiplesBelow(13); 64 | // Assert 65 | expect(sum).toBe(45); // 3 + 6 + 9 + 5 + 10 + 12 => 45 66 | }); 67 | }); 68 | 69 | describe("Given the number 1000", () => { 70 | it("should get the sum of all the multiples of 3 and 5 under 1000", () => { 71 | // Arrange 72 | var calculator = MultipleOf3And5Calculator(); 73 | // Act 74 | var sum = calculator.sumAllMultiplesBelow(1000); 75 | // Assert 76 | expect(sum).toBe(233168); 77 | }); 78 | }); 79 | 80 | 81 | }); 82 | 83 | -------------------------------------------------------------------------------- /specs/euler/002-even-fibonacci-numbers.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * ## 002. Even Fibonacci numbers 3 | * 4 | * Each new term in the Fibonacci sequence is generated 5 | * by adding the previous two terms. By starting with 1 and 2, 6 | * the first 10 terms will be: 7 | * 8 | * 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ... 9 | * 10 | * By considering the terms in the Fibonacci sequence 11 | * whose values do not exceed four million, find the sum 12 | * of the even-valued terms. 13 | * 14 | */ 15 | 16 | describe("Even Fibonacci Numbers", () => { 17 | 18 | // BRUTE FORCE: iterate over all items and add them 19 | // 20 | describe("getFibonacciIterator", () => { 21 | 22 | describe("given a threshold lower than 1", () => { 23 | it("should not iterate over any number", () => { 24 | // Arrange 25 | let fibonacci = getFibonacciIterator(0); 26 | // Act 27 | let numbers = Array.from(fibonacci); 28 | // Assert 29 | expect(numbers.join(',')).toBe(''); 30 | }); 31 | }); 32 | 33 | describe("given a threshold of 1", () => { 34 | it("should iterate over only the number 1", () => { 35 | // Arrange 36 | let fibonacci = getFibonacciIterator(1); 37 | // Act 38 | let numbers = Array.from(fibonacci); 39 | // Assert 40 | expect(numbers.join(',')).toBe('1'); 41 | }); 42 | }); 43 | 44 | describe("given a threshold of 2", () => { 45 | it("should iterate over the numbers 1 and 2", () => { 46 | // Arrange 47 | let fibonacci = getFibonacciIterator(2); 48 | // Act 49 | let numbers = Array.from(fibonacci); 50 | // Assert 51 | expect(numbers.join(',')).toBe('1,2'); 52 | }); 53 | }); 54 | 55 | describe("given a threshold of 3", () => { 56 | it("should iterate over the numbers 1 and 2 and 3 (1+2)", () => { 57 | // Arrange 58 | let fibonacci = getFibonacciIterator(3); 59 | // Act 60 | let numbers = Array.from(fibonacci); 61 | // Assert 62 | expect(numbers.join(',')).toBe('1,2,3'); 63 | }); 64 | }); 65 | 66 | describe("given a threshold of 90", () => { 67 | it("should iterate over the numbers 1,2,3,etc)", () => { 68 | // Arrange 69 | let fibonacci = getFibonacciIterator(90); 70 | // Act 71 | let numbers = Array.from(fibonacci); 72 | // Assert 73 | expect(numbers.join(',')).toBe('1,2,3,5,8,13,21,34,55,89'); 74 | 75 | }); 76 | }); 77 | 78 | }); 79 | 80 | describe("FibonacciCalculator", () => { 81 | describe("SumEvenNumbersUnder", () => { 82 | describe("Given a number like 4000000", () => { 83 | it("Should add all even fibonacci numbers smaller than 4000000", () => { 84 | // Arrange 85 | let calculator = new FibonacciCalculator(); 86 | // Act 87 | let sum = calculator.sumEvenNumbersUnder(4000000); 88 | // Assert 89 | expect(sum).toBe(4613732); 90 | }); 91 | 92 | }); 93 | }); 94 | }); 95 | 96 | }); 97 | -------------------------------------------------------------------------------- /specs/graphs/undirectedGraph.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Undirected graph: A data structure consituted by nodes 4 | * connected by edges with no specific direction (they can 5 | * be traversed from one node to the other and vice versa) 6 | * 7 | * 8 | */ 9 | 10 | describe("Undirected Graph", () => { 11 | describe("Given that I have no graph", () => { 12 | it("I Should be able to create an empty one", () => { 13 | // Arrange, Assert 14 | const graph = UndirectedGraph(); 15 | // Act 16 | expect(graph).toBeDefined(); 17 | expect(graph.count()).toBe(0); 18 | }); 19 | }); 20 | 21 | describe("Given that I have an empty graph", () => { 22 | it("I Should be able to add a new node to the graph", () => { 23 | // Arrange 24 | const graph = UndirectedGraph(); 25 | // Act 26 | graph.addNode("Kevin Bacon"); 27 | // Assert 28 | expect(graph).toBeDefined(); 29 | expect(graph.count()).toBe(1); 30 | }); 31 | }); 32 | 33 | describe("Given that I have a graph with a node", () => { 34 | let graph; 35 | 36 | beforeEach(() => { 37 | graph = UndirectedGraph(); 38 | graph.addNode("Kevin Bacon"); 39 | }); 40 | 41 | it("I should be able to add another node", () => { 42 | // Arrange, Act 43 | graph.addNode("Tom Hanks"); 44 | // Assert 45 | expect(graph.count()).toBe(2); 46 | }); 47 | 48 | it("I should be able to find a node", () => { 49 | // Arrange, Act 50 | const node = graph.find("Kevin Bacon"); 51 | // Assert 52 | expect(node.value).toBe("Kevin Bacon"); 53 | }); 54 | 55 | describe("When I try to access an item that doesn't exist", () => { 56 | it("It should throw an error", () => { 57 | // Arrange, Act, Assert 58 | expect(() => graph.find("Yo")).toThrowError(); 59 | }); 60 | }); 61 | }); 62 | 63 | describe("Given that I have a graph with two nodes", () => { 64 | let graph; 65 | 66 | beforeEach(() => { 67 | graph = UndirectedGraph(); 68 | graph.addNode("Kevin Bacon"); 69 | graph.addNode("Tom Hanks"); 70 | }); 71 | 72 | it("I should be able to add an edge between the nodes", () => { 73 | // Arrange, Act 74 | graph.addEdge("Apollo 13", "Kevin Bacon", "Tom Hanks"); 75 | // Assert 76 | // Edge connects from kevin bacon to tom hanks 77 | const kevinBacon = graph.find("Kevin Bacon"); 78 | const node = Array.from(kevinBacon.nodes) 79 | .find(n => n.value === "Tom Hanks"); 80 | expect(node).toBeDefined(); 81 | // And vice versa 82 | const tomHanks = graph.find("Tom Hanks"); 83 | const nodeOtherSide = Array.from(tomHanks.nodes) 84 | .find(e => e.value === "Kevin Bacon"); 85 | expect(nodeOtherSide).toBeDefined(); 86 | }); 87 | }); 88 | 89 | describe("Given that I have a graph with three nodes", () => { 90 | let graph; 91 | beforeEach(() => { 92 | graph = UndirectedGraph(); 93 | graph.addNode("Kevin Bacon"); 94 | graph.addNode("Tom Hanks"); 95 | graph.addEdge("Apollo 13", "Kevin Bacon", "Tom Hanks"); 96 | graph.addNode("Catherine Z Jones"); 97 | graph.addEdge("The terminal", "Catherine Z Jones", "Tom Hanks"); 98 | }); 99 | 100 | describe("When I want to calculate the length of shortest path from and to the same node", () => { 101 | it("Should be length 0", () => { 102 | // Arrange, Act 103 | const length = graph.findLengthOfShortestPathBetween("Kevin Bacon", "Kevin Bacon"); 104 | // Assert 105 | expect(length).toBe(0); 106 | }); 107 | }); 108 | 109 | describe("When I want to calculate the length of shortest path between 2 adjacent nodes", () => { 110 | it("Should be length 1", () => { 111 | // Arrange, Act 112 | const length = graph.findLengthOfShortestPathBetween("Kevin Bacon", "Tom Hanks"); 113 | // Assert 114 | expect(length).toBe(1); 115 | }); 116 | }); 117 | 118 | describe("When I want to calculate the length of shortest path between 2 non adjacent nodes", () => { 119 | it("Should calculate it", () => { 120 | // Arrange, Act 121 | const length = graph.findLengthOfShortestPathBetween("Kevin Bacon", "Catherine Z Jones"); 122 | // Assert 123 | expect(length).toBe(2); 124 | }); 125 | }); 126 | }); 127 | 128 | 129 | describe("Given that I have a complex graph with 4 nodes and cycles", () => { 130 | let graph; 131 | beforeEach(() => { 132 | graph = UndirectedGraph(); 133 | graph.addNode("Kevin Bacon"); 134 | graph.addNode("Tom Hanks"); 135 | graph.addEdge("Apollo 13", "Kevin Bacon", "Tom Hanks"); 136 | graph.addNode("Catherine Z Jones"); 137 | graph.addEdge("The terminal", "Catherine Z Jones", "Tom Hanks"); 138 | graph.addNode("John Doe"); 139 | graph.addEdge("Some movie", "John Doe", "Tom Hanks"); 140 | graph.addEdge("Some movie 2", "John Doe", "Catherine Z Jones"); 141 | }); 142 | 143 | describe("When I want to calculate the length of shortest path between 2 non adjacent nodes", () => { 144 | it("Should calculate it", () => { 145 | // Arrange, Act 146 | const length = graph.findLengthOfShortestPathBetween("Kevin Bacon", "John Doe"); 147 | // Assert 148 | expect(length).toBe(2); 149 | }); 150 | }); 151 | 152 | }); 153 | 154 | }); 155 | 156 | describe("Visitor", () => { 157 | describe("Given that I have an start, end and next nodes", () => { 158 | it("I should be able to create a visitor", () => { 159 | // Arrange 160 | //const startNode = UndirectedGraphNode(1); 161 | //const endNode = UndirectedGraphNode(1); 162 | // Act 163 | // Assert 164 | }); 165 | }); 166 | 167 | 168 | }); 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /specs/sorting/BubbleSort.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * BubbleSort 4 | * 5 | * - while items to sort 6 | * - for each item 7 | * - compare with neighbor 8 | * - if bigger, swap 9 | * 10 | */ 11 | 12 | describe("BubbleSort", () => { 13 | 14 | describe("Given an empty array", () => { 15 | it("Should sort it all right", () => { 16 | // Arrange 17 | let emptyArray = []; 18 | // Act 19 | let sortedArray = bubbleSort(emptyArray); 20 | // Assert 21 | expect(sortedArray.join(',')).toBe(''); 22 | }); 23 | }); 24 | 25 | describe("Given an array with an item", () => { 26 | it("Should return it since it is already sorted", () => { 27 | // Arrange 28 | let array = [1]; 29 | // Act 30 | let sortedArray = bubbleSort(array); 31 | // Assert 32 | expect(sortedArray.join(',')).toBe('1'); 33 | }); 34 | }); 35 | 36 | describe("Given an array with two unordered items", () => { 37 | it("Should sort them", () => { 38 | // Arrange 39 | let array = [2,1]; 40 | // Act 41 | let sortedArray = bubbleSort(array); 42 | // Assert 43 | expect(sortedArray.join(',')).toBe('1,2'); 44 | }); 45 | }); 46 | 47 | describe("Given an array with many unordered items", () => { 48 | it ("Should sort them", () => { 49 | // Arrange 50 | let array = [2,1,8,4,55,22,0,29,11]; 51 | // Act 52 | let sortedArray = bubbleSort(array); 53 | // Assert 54 | expect(sortedArray.join(',')).toBe('0,1,2,4,8,11,22,29,55'); 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /specs/sorting/InsertionSort.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Insertion Sort 3 | * 4 | * - for each item in the array 5 | * - for each previous item in the array 6 | * - if item is smaller than previous item swap it 7 | */ 8 | 9 | describe("Insertion Sort", () => { 10 | 11 | describe("Given an empty array", () => { 12 | it("Should sort it all right", () => { 13 | // Arrange 14 | let emptyArray = []; 15 | // Act 16 | let sortedArray = insertionSort(emptyArray); 17 | // Assert 18 | expect(sortedArray.join(',')).toBe(''); 19 | }); 20 | }); 21 | 22 | describe("Given an array with an item", () => { 23 | it("Should return it since it is already sorted", () => { 24 | // Arrange 25 | let array = [1]; 26 | // Act 27 | let sortedArray = insertionSort(array); 28 | // Assert 29 | expect(sortedArray.join(',')).toBe('1'); 30 | }); 31 | }); 32 | 33 | describe("Given an array with two unordered items", () => { 34 | it("Should sort them", () => { 35 | // Arrange 36 | let array = [2,1]; 37 | // Act 38 | let sortedArray = insertionSort(array); 39 | // Assert 40 | expect(sortedArray.join(',')).toBe('1,2'); 41 | }); 42 | }); 43 | 44 | describe("Given an array with many unordered items", () => { 45 | it ("Should sort them", () => { 46 | // Arrange 47 | let array = [2,1,8,4,55,22,0,29,11]; 48 | // Act 49 | let sortedArray = insertionSort(array); 50 | // Assert 51 | expect(sortedArray.join(',')).toBe('0,1,2,4,8,11,22,29,55'); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /specs/sorting/MergeSort.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Merge Sort 3 | * 4 | * 5 | * 1. divide in half 6 | * 2. when array divided in subarrays of one element 7 | * 3. reconstruct (merge) in sort order 8 | * 9 | */ 10 | 11 | describe("Merge Sort", () => { 12 | 13 | describe("Given an empty array", () => { 14 | it("Should sort it all right", () => { 15 | // Arrange 16 | let emptyArray = []; 17 | // Act 18 | let sortedArray = mergeSort(emptyArray); 19 | // Assert 20 | expect(sortedArray.join(',')).toBe(''); 21 | }); 22 | }); 23 | 24 | describe("Given an array with an item", () => { 25 | it("Should return it since it is already sorted", () => { 26 | // Arrange 27 | let array = [1]; 28 | // Act 29 | let sortedArray = mergeSort(array); 30 | // Assert 31 | expect(sortedArray.join(',')).toBe('1'); 32 | }); 33 | }); 34 | 35 | describe("Given an array with two unordered items", () => { 36 | it("Should sort them", () => { 37 | // Arrange 38 | let array = [2,1]; 39 | // Act 40 | let sortedArray = mergeSort(array); 41 | // Assert 42 | expect(sortedArray.join(',')).toBe('1,2'); 43 | }); 44 | }); 45 | 46 | describe("Given an array with many unordered items", () => { 47 | it ("Should sort them", () => { 48 | // Arrange 49 | let array = [2,1,8,4,55,22,0,29,11]; 50 | // Act 51 | let sortedArray = mergeSort(array); 52 | // Assert 53 | expect(sortedArray.join(',')).toBe('0,1,2,4,8,11,22,29,55'); 54 | }); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /specs/sorting/QuickSort.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Quick Sort 3 | * 4 | * [5,3,1,4,2] 5 | * // pick 4 as pivot 6 | * [5,3,1,*4*,2] 7 | * // move elements so everything to the left is smaller than 4, everything to the right is larger than 4 8 | * [3,1,2,*4*,5] 9 | * // now we continue with the [3,1,2] subarray and pick 2 as pivot 10 | * [3,1,*2*] 11 | * [1,*2*,3] 12 | * // continue with the [5] subarray (it's sorted!) 13 | * // in fact all the array is sorted!! 14 | * [1,2,3,4,5] 15 | * 16 | */ 17 | 18 | describe("Quick Sort", () => { 19 | 20 | describe("Given an empty array", () => { 21 | it("Should sort it all right", () => { 22 | // Arrange 23 | let emptyArray = []; 24 | // Act 25 | let sortedArray = quickSort(emptyArray); 26 | // Assert 27 | expect(sortedArray.join(',')).toBe(''); 28 | }); 29 | }); 30 | 31 | describe("Given an array with an item", () => { 32 | it("Should return it since it is already sorted", () => { 33 | // Arrange 34 | let array = [1]; 35 | // Act 36 | let sortedArray = quickSort(array); 37 | // Assert 38 | expect(sortedArray.join(',')).toBe('1'); 39 | }); 40 | }); 41 | 42 | describe("Given an array with two unordered items", () => { 43 | it("Should sort them", () => { 44 | // Arrange 45 | let array = [2,1]; 46 | // Act 47 | let sortedArray = quickSort(array); 48 | // Assert 49 | expect(sortedArray.join(',')).toBe('1,2'); 50 | }); 51 | }); 52 | 53 | describe("Given an array with many unordered items", () => { 54 | it ("Should sort them", () => { 55 | // Arrange 56 | let array = [2,1,8,4,55,22,0,29,11]; 57 | // Act 58 | let sortedArray = quickSort(array); 59 | // Assert 60 | expect(sortedArray.join(',')).toBe('0,1,2,4,8,11,22,29,55'); 61 | }); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /specs/sorting/SelectionSort.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Selection Sort 3 | * 4 | * 1. Enumerate the array from the first unsorted item to the end 5 | * 2. Identify smallest item 6 | * 3. Swap smallest item with the first unsorted item 7 | */ 8 | 9 | describe("Selection Sort", () => { 10 | 11 | describe("Given an empty array", () => { 12 | it("Should sort it all right", () => { 13 | // Arrange 14 | let emptyArray = []; 15 | // Act 16 | let sortedArray = selectionSort(emptyArray); 17 | // Assert 18 | expect(sortedArray.join(',')).toBe(''); 19 | }); 20 | }); 21 | 22 | describe("Given an array with an item", () => { 23 | it("Should return it since it is already sorted", () => { 24 | // Arrange 25 | let array = [1]; 26 | // Act 27 | let sortedArray = selectionSort(array); 28 | // Assert 29 | expect(sortedArray.join(',')).toBe('1'); 30 | }); 31 | }); 32 | 33 | describe("Given an array with two unordered items", () => { 34 | it("Should sort them", () => { 35 | // Arrange 36 | let array = [2,1]; 37 | // Act 38 | let sortedArray = selectionSort(array); 39 | // Assert 40 | expect(sortedArray.join(',')).toBe('1,2'); 41 | }); 42 | }); 43 | 44 | describe("Given an array with many unordered items", () => { 45 | it ("Should sort them", () => { 46 | // Arrange 47 | let array = [2,1,8,4,55,22,0,29,11]; 48 | // Act 49 | let sortedArray = selectionSort(array); 50 | // Assert 51 | expect(sortedArray.join(',')).toBe('0,1,2,4,8,11,22,29,55'); 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /specs/strings/findStringMatches.spec.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 3 | * Find string matches in a arbitrary string 4 | * 5 | * 6 | */ 7 | 8 | describe("StringMatcher", () => { 9 | 10 | describe("Naive string matching", () => { 11 | describe("Given an arbitrary text", () => { 12 | const text = "Once upon a time there was a princess"; 13 | let matcher; 14 | 15 | beforeEach(() => matcher = new StringMatcher(new FindMatchesNaively)); 16 | 17 | describe("And an empty term to match", () => { 18 | it("Should return no matches", () => { 19 | // Arrange, Act 20 | const matches = matcher.findMatches(text, ''); 21 | // Assert 22 | expect(matches.length).toBe(0); 23 | }); 24 | }); 25 | 26 | describe("And a null term to match", () => { 27 | it("Should throw an error", () => { 28 | // Arrange, Act, Assert 29 | expect(() => matcher.findMatches(text, null)).toThrowError(); 30 | }); 31 | }); 32 | 33 | describe("And an undefined term to match", () => { 34 | it("Should throw an error", () => { 35 | // Arrange, Act, Assert 36 | expect(() => matcher.findMatches(text)).toThrowError(); 37 | }); 38 | }); 39 | 40 | describe("And a matching term", () => { 41 | it("Should return a match", () => { 42 | // Arrange, Act 43 | const matches = matcher.findMatches(text, 'time'); 44 | // Assert 45 | expect(matches.length).toBe(1); 46 | const match = matches[0]; 47 | expect(match.term).toBe('time'); 48 | expect(match.index).toBe(12); 49 | }); 50 | }); 51 | }); 52 | }); 53 | 54 | describe("BoyerMooreHoorst string matching", () => { 55 | 56 | describe("Given an arbitrary text", () => { 57 | const text = "Once upon a time there was a princess"; 58 | let matcher; 59 | 60 | beforeEach(() => matcher = new StringMatcher(new FindMatchesWithBoyerMooreHoorst())); 61 | 62 | describe("And an empty term to match", () => { 63 | it("Should return no matches", () => { 64 | // Arrange, Act 65 | const matches = matcher.findMatches(text, ''); 66 | // Assert 67 | expect(matches.length).toBe(0); 68 | }); 69 | }); 70 | 71 | describe("And a null term to match", () => { 72 | it("Should throw an error", () => { 73 | // Arrange, Act, Assert 74 | expect(() => matcher.findMatches(text, null)).toThrowError(); 75 | }); 76 | }); 77 | 78 | describe("And an undefined term to match", () => { 79 | it("Should throw an error", () => { 80 | // Arrange, Act, Assert 81 | expect(() => matcher.findMatches(text)).toThrowError(); 82 | }); 83 | }); 84 | 85 | describe("And a matching term", () => { 86 | beforeEach(() => l.isEnabled = true); 87 | it("Should return a match", () => { 88 | // Arrange, Act 89 | const matches = matcher.findMatches(text, 'time'); 90 | // Assert 91 | expect(matches.length).toBe(1); 92 | const match = matches[0]; 93 | expect(match.term).toBe('time'); 94 | expect(match.index).toBe(12); 95 | }); 96 | afterEach(() => l.isEnabled = false); 97 | }); 98 | }); 99 | 100 | }); 101 | }); 102 | -------------------------------------------------------------------------------- /utils/logger.js: -------------------------------------------------------------------------------- 1 | (function(logger){ 2 | 3 | Object.assign(logger, { 4 | isEnabled: false, 5 | log 6 | }); 7 | 8 | function log(...args){ 9 | if (this.isEnabled) console.log.apply(console, args); 10 | } 11 | 12 | }(window.l = window.l || {})); 13 | 14 | 15 | 16 | --------------------------------------------------------------------------------