├── .gitignore ├── LICENSE ├── README.md ├── binaryTrees ├── binaryTreeScript.md ├── index.js ├── index.test.js └── package.json ├── bubbleSort ├── index.js ├── index.test.js ├── package.json └── script.md ├── graphs ├── bfsScript.md ├── breadthFirstSearch.js ├── depthFirstSearch.js ├── dfsScript.md ├── index.js ├── index.test.js ├── package.json └── script.md ├── insertionSort ├── index.js ├── index.test.js ├── package.json └── script.md ├── linkedLists ├── index.js ├── index.test.js ├── package.json └── script.md ├── mergeSort ├── index.js ├── index.test.js ├── package.json └── script.md ├── package-lock.json ├── package.json ├── priorityQueue ├── package.json ├── priorityQueue.js ├── priorityQueue.test.js └── priorityQueueScript.md ├── queues ├── index.js ├── index.test.js ├── package.json ├── queues.js └── script.md ├── quickSort ├── index.js ├── index.test.js ├── package.json └── script.md ├── stacks ├── index.js ├── index.test.js ├── package.json ├── script.md └── yakstack.js ├── trees ├── index.js ├── index.test.js ├── package.json └── script.md └── utils └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Kyle Shevlin 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 | # Intro to Data Structures and Algorithms in JavaScript 2 | 3 | ![Image of Course Logo](https://d2eip9sf3oo6c2.cloudfront.net/series/square_covers/000/000/261/square_480/EGH_JSAlgorithms_Final.png) 4 | 5 | My introductory course to data structures and algorithms in JavaScript for [egghead](https://egghead.io). 6 | 7 | This course covers: 8 | 9 | * Queues 10 | * Stacks 11 | * Linked Lists 12 | * Graphs 13 | * Breadth-first Traversal 14 | * Depth-first Traversal 15 | * Trees 16 | * Binary Trees 17 | * Tree Traversal 18 | * Sorting 19 | * Bubble Sort 20 | * Insertion Sort 21 | * Merge Sort 22 | * Quick Sort 23 | 24 | Feel free to leave comments on the lessons or hit me up on [Twitter](https://twitter.com/kyleshevlin) with any questions you might have. 25 | -------------------------------------------------------------------------------- /binaryTrees/binaryTreeScript.md: -------------------------------------------------------------------------------- 1 | # Binary Trees and Tree Traversal 2 | 3 | Binary trees are trees whose nodes can only have up to two children, hence binary. We store this data as the left and right of a node. 4 | 5 | We'll use a function to create our binary node object. a binary node receives a key as an argument, has a left and right property set to null, as well as methods to addLeft and addRight. 6 | 7 | addLeft and addRight receive a key, create a new node, update left and right respectively, and then return the new node. 8 | 9 | Now we can create our binaryTree factory function. Since trees must have a root, our function receives a rootKey as an argument. We can then create the root node using our node factory function. We pass this to the object we return from the function. 10 | 11 | Binary trees have three specific types of traversals: in order, pre order and post order. Let's create a traversals enum. Each item in our enum will be named after the type of traversal it is. The value of each will be a function that we can use when traversing our tree. 12 | 13 | In order traversal always visits the left branch, then our current node, follwed by the right branch. Since these traversal functions are called recursively, we need to guard against a node that is null. So if the node is not null, we call our traversal down the left branch, then visit our current node, then traverse down the right branch. 14 | 15 | Pre order traversal is very similar, but we change the order so that we visit our current node first, then followed by the left branch, then the right branch. 16 | 17 | Post order traversal visits the left and right branches before visiting the current node. 18 | 19 | Now, let's add a print method to our tree that accepts an orderType as an argument. The default will be inorder, as that's the most common traversal. 20 | 21 | We'll keep our print function simple. As we visit each node, we'll add the key to the result and return a string. Using our enum, we can trigger the correct method call to start our traversal. 22 | 23 | Let's create a simple binary tree, I have one to copy paste in here, We can call print on it several times and compare the results. 24 | -------------------------------------------------------------------------------- /binaryTrees/index.js: -------------------------------------------------------------------------------- 1 | // Binary Trees and Tree Traversal 2 | 3 | // Binary trees are trees whose nodes can only have up to two children 4 | 5 | function createBinaryNode(key) { 6 | return { 7 | key, 8 | left: null, 9 | right: null, 10 | addLeft(leftKey) { 11 | const newLeft = createBinaryNode(leftKey) 12 | this.left = newLeft 13 | return newLeft 14 | }, 15 | addRight(rightKey) { 16 | const newRight = createBinaryNode(rightKey) 17 | this.right = newRight 18 | return newRight 19 | } 20 | } 21 | } 22 | 23 | const TRAVERSALS = { 24 | IN_ORDER: (node, visitFn) => { 25 | if (node !== null) { 26 | TRAVERSALS.IN_ORDER(node.left, visitFn) 27 | visitFn(node) 28 | TRAVERSALS.IN_ORDER(node.right, visitFn) 29 | } 30 | }, 31 | PRE_ORDER: (node, visitFn) => { 32 | if (node !== null) { 33 | visitFn(node) 34 | TRAVERSALS.PRE_ORDER(node.left, visitFn) 35 | TRAVERSALS.PRE_ORDER(node.right, visitFn) 36 | } 37 | }, 38 | POST_ORDER: (node, visitFn) => { 39 | if (node !== null) { 40 | TRAVERSALS.POST_ORDER(node.left, visitFn) 41 | TRAVERSALS.POST_ORDER(node.right, visitFn) 42 | visitFn(node) 43 | } 44 | } 45 | } 46 | 47 | function createBinaryTree(rootKey) { 48 | const root = createBinaryNode(rootKey) 49 | 50 | return { 51 | root, 52 | print(traversalType = 'IN_ORDER') { 53 | let result = '' 54 | 55 | const visit = node => { 56 | result += result.length === 0 ? node.key : ` => ${node.key}` 57 | } 58 | 59 | TRAVERSALS[traversalType](this.root, visit) 60 | 61 | return result 62 | } 63 | } 64 | } 65 | 66 | const tree = createBinaryTree('a') 67 | const b = tree.root.addLeft('b') 68 | const c = tree.root.addRight('c') 69 | const d = b.addLeft('d') 70 | const e = b.addRight('e') 71 | const f = c.addLeft('f') 72 | const g = c.addRight('g') 73 | const h = d.addLeft('h') 74 | const i = d.addRight('i') 75 | 76 | console.log('IN_ORDER: ', tree.print()) 77 | 78 | console.log('PRE_ORDER: ', tree.print('PRE_ORDER')) 79 | 80 | console.log('POST_ORDER: ', tree.print('POST_ORDER')) 81 | 82 | exports.createBinaryNode = createBinaryNode 83 | exports.createBinaryTree = createBinaryTree 84 | -------------------------------------------------------------------------------- /binaryTrees/index.test.js: -------------------------------------------------------------------------------- 1 | const { createBinaryNode, createBinaryTree } = require('./index') 2 | 3 | describe('BinaryNode', () => { 4 | let node 5 | beforeEach(() => { 6 | node = createBinaryNode('a') 7 | }) 8 | 9 | test('existence', () => { 10 | expect(node).toBeDefined() 11 | }) 12 | 13 | test('key', () => { 14 | expect(node.key).toEqual('a') 15 | }) 16 | 17 | test('left', () => { 18 | expect(node.left).toBeNull() 19 | }) 20 | 21 | test('right', () => { 22 | expect(node.right).toBeNull() 23 | }) 24 | 25 | test('addLeft', () => { 26 | node.addLeft('b') 27 | 28 | expect(node.left).toBeDefined() 29 | expect(node.left.key).toEqual('b') 30 | }) 31 | 32 | test('addRight', () => { 33 | node.addRight('c') 34 | 35 | expect(node.right).toBeDefined() 36 | expect(node.right.key).toEqual('c') 37 | }) 38 | }) 39 | 40 | describe('BinaryTree', () => { 41 | let binaryTree 42 | beforeEach(() => { 43 | binaryTree = createBinaryTree('a') 44 | }) 45 | 46 | test('existence', () => { 47 | expect(binaryTree).toBeDefined() 48 | }) 49 | 50 | test('root', () => { 51 | expect(binaryTree.root).toBeDefined() 52 | expect(binaryTree.root.key).toEqual('a') 53 | }) 54 | 55 | describe('print', () => { 56 | test('IN_ORDER', () => { 57 | const b = binaryTree.root.addLeft('b') 58 | const c = binaryTree.root.addRight('c') 59 | const d = b.addLeft('d') 60 | const e = b.addRight('e') 61 | const f = c.addLeft('f') 62 | const g = c.addRight('g') 63 | const h = f.addLeft('h') 64 | 65 | expect(binaryTree.print()).toEqual(`d => b => e => a => h => f => c => g`) 66 | }) 67 | 68 | test('PRE_ORDER', () => { 69 | const b = binaryTree.root.addLeft('b') 70 | const c = binaryTree.root.addRight('c') 71 | const d = b.addLeft('d') 72 | const e = b.addRight('e') 73 | const f = c.addLeft('f') 74 | const g = c.addRight('g') 75 | const h = f.addLeft('h') 76 | 77 | expect(binaryTree.print('PRE_ORDER')).toEqual( 78 | `a => b => d => e => c => f => h => g` 79 | ) 80 | }) 81 | 82 | test('POST_ORDER', () => { 83 | const b = binaryTree.root.addLeft('b') 84 | const c = binaryTree.root.addRight('c') 85 | const d = b.addLeft('d') 86 | const e = b.addRight('e') 87 | const f = c.addLeft('f') 88 | const g = c.addRight('g') 89 | const h = f.addLeft('h') 90 | 91 | expect(binaryTree.print('POST_ORDER')).toEqual( 92 | `d => e => b => h => f => g => c => a` 93 | ) 94 | }) 95 | }) 96 | }) 97 | -------------------------------------------------------------------------------- /binaryTrees/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "intro-to-data-structures-and-algorithms", 3 | "version": "1.0.0", 4 | "description": "An egghead course to introduce data structures and algorithms in JavaScript", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest", 8 | "test:watch": "jest --watch" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms.git" 13 | }, 14 | "author": "Kyle Shevlin (http://kyleshevlin.com/)", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms/issues" 18 | }, 19 | "homepage": "https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms#readme", 20 | "devDependencies": { 21 | "jest": "^23.6.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /bubbleSort/index.js: -------------------------------------------------------------------------------- 1 | const { printArray } = require('../utils') 2 | 3 | function bubbleSort(array) { 4 | let swapped = false 5 | let i 6 | let count = 0 7 | 8 | do { 9 | swapped = false 10 | 11 | array.forEach((item, index) => { 12 | printArray(array) 13 | count++ 14 | 15 | if (item > array[index + 1]) { 16 | const temporary = item 17 | 18 | array[index] = array[index + 1] 19 | array[index + 1] = temporary 20 | 21 | swapped = true 22 | } 23 | }) 24 | } while (swapped) 25 | 26 | printArray(array) 27 | console.log(`Iterations: ${count}`) 28 | 29 | return array 30 | } 31 | 32 | let numbers = [10, 5, 6, 3, 2, 8, 9, 4, 7, 1] 33 | 34 | bubbleSort(numbers) 35 | 36 | exports.bubbleSort = bubbleSort 37 | -------------------------------------------------------------------------------- /bubbleSort/index.test.js: -------------------------------------------------------------------------------- 1 | const { bubbleSort } = require('./index') 2 | 3 | test('Bubble Sort', () => { 4 | const nums = [10, 8, 4, 3, 6, 2, 1, 9, 7, 5] 5 | expect(bubbleSort(nums)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) 6 | }) 7 | -------------------------------------------------------------------------------- /bubbleSort/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "intro-to-data-structures-and-algorithms", 3 | "version": "1.0.0", 4 | "description": "An egghead course to introduce data structures and algorithms in JavaScript", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest", 8 | "test:watch": "jest --watch" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms.git" 13 | }, 14 | "author": "Kyle Shevlin (http://kyleshevlin.com/)", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms/issues" 18 | }, 19 | "homepage": "https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms#readme", 20 | "devDependencies": { 21 | "jest": "^23.6.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /bubbleSort/script.md: -------------------------------------------------------------------------------- 1 | To write the bubble sort algorithm, let's create a function that accepts an array and returns that array. We'll be making mutations to this array in our algorithm. 2 | 3 | Bubble sort works by looping over the array many times. Each time we iterate over the array, we check if the current item is greater than the next item. If it is, we swap it in place and we indicate that we have made a swap in this loop. Then, if we've made a swap, we loop through the array again. We continue looping until we make a pass where no items have been swapped. 4 | 5 | In order for this algorithm to work, we need to always do at least 1 pass through the array, so we will use a do while loop. 6 | 7 | We need to maintain some mutable state in our function, so let's setup variables for swapped. 8 | 9 | We want to loop through our array so long as it was swapped, so we can setup our while condition right away. 10 | 11 | Now, when we start our do block, we want to reset swapped to false. 12 | 13 | Then, for each item, we want to compare it to the next item in the array. If the item is greater than the next one, store it temporarily, and mutate their indices in the array. If we swap them, update the swapped variable. 14 | 15 | To help us visualize this algorithm, I want to do two things. First, I want to keep track of how many times we looped through the array, so I'll set up a count variable and we'll increment each time we run the do block. Second, i'm going to pull in a utility function that can print an array. We'll place this next to our count incrememnt so we can visually see the swapping in the terminal as it loops through. 16 | 17 | Now, let's create an unsorted array of numbers and pass it to our function to see our algorithm in action. 18 | -------------------------------------------------------------------------------- /graphs/bfsScript.md: -------------------------------------------------------------------------------- 1 | # Breadth First Search 2 | 3 | As the name implies, breadth first search is a graph search algorithm that starts at one node, explores as widely as it can first, before going further down adjacent nodes. 4 | 5 | We'll add a breadthFirstSearch method to our graph object. We want to accept two arguments, a startingNodeKey to find which node in our graph to start from, and a visitFunction to call when we "visit" each node. 6 | 7 | We'll start by getting our starting node using the this.getNode method. 8 | 9 | Next, we need to keep track of which nodes we have visited and which ones we haven't. There are several ways to do this, but I'm going to do it through an object. I'm going to reduce our nodes down to an object where each key is the current node's key, and the value is set to false. 10 | 11 | Next, we need to keep track, in order, which nodes we need to visit. We'll use a createQueue function I've imported from another lesson. The first node we need to visit is our startingNode. 12 | 13 | Now, we want to start at our startingNode, so we will dequeue it from the queue and label it our current node. If we haven't visited this node before, we want to run the visiting function on it, and mark it as visited in our visited hash. Since this is our first node, we know that we'll hit this condition. 14 | 15 | Next, if our currentNode has any neighbors, we want to add them to our queue. While the queue is not empty, we want to continue dequeueing items and performing this algorithm, so we'll use a while loop. The while loop stops when we've visited every node in our graph. 16 | 17 | Now that we've implemented breadth first search, let's try it out on a graph. 18 | 19 | I've premade a graph for us to use that looks like this that I'l copy paste into here. I want to use breadthFirstSearch to print out the node keys as we arrive at each one into the console. 20 | 21 | If I bring the graph back onto the screen and compare it to the result in the terminal, we can see how we branched out from node A and visited all of its neighbors before proceeding down node B's neighbors. 22 | -------------------------------------------------------------------------------- /graphs/breadthFirstSearch.js: -------------------------------------------------------------------------------- 1 | const { createQueue } = require('../queues/index.js') 2 | 3 | function createNode(key) { 4 | const children = [] 5 | 6 | return { 7 | key, 8 | children, 9 | addChild(node) { 10 | children.push(node) 11 | } 12 | } 13 | } 14 | 15 | function createGraph(directed = false) { 16 | const nodes = [] 17 | const edges = [] 18 | 19 | return { 20 | directed, 21 | nodes, 22 | edges, 23 | 24 | addNode(key) { 25 | nodes.push(createNode(key)) 26 | }, 27 | 28 | getNode(key) { 29 | return nodes.find(n => n.key === key) 30 | }, 31 | 32 | addEdge(node1Key, node2Key) { 33 | const node1 = this.getNode(node1Key) 34 | const node2 = this.getNode(node2Key) 35 | 36 | node1.addChild(node2) 37 | 38 | if (!directed) { 39 | node2.addChild(node1) 40 | } 41 | 42 | edges.push(`${node1Key}${node2Key}`) 43 | }, 44 | 45 | print() { 46 | return nodes 47 | .map(({ children, key }) => { 48 | let result = `${key}` 49 | 50 | if (children.length) { 51 | result += ` => ${children.map(node => node.key).join(' ')}` 52 | } 53 | 54 | return result 55 | }) 56 | .join('\n') 57 | }, 58 | 59 | bfs(startingNodeKey, visitFn) { 60 | const startingNode = this.getNode(startingNodeKey) 61 | const visitedHash = nodes.reduce((acc, cur) => { 62 | acc[cur.key] = false 63 | return acc 64 | }, {}) 65 | const queue = createQueue() 66 | queue.enqueue(startingNode) 67 | 68 | while (!queue.isEmpty()) { 69 | const currentNode = queue.dequeue() 70 | 71 | if (!visitedHash[currentNode.key]) { 72 | visitFn(currentNode) 73 | visitedHash[currentNode.key] = true 74 | } 75 | 76 | currentNode.children.forEach(node => { 77 | if (!visitedHash[node.key]) { 78 | queue.enqueue(node) 79 | } 80 | }) 81 | } 82 | }, 83 | 84 | dfs(startingNodeKey, visitFn) { 85 | const startingNode = this.getNode(startingNodeKey) 86 | const visitedHash = nodes.reduce((acc, cur) => { 87 | acc[cur.key] = false 88 | return acc 89 | }, {}) 90 | 91 | function explore(node) { 92 | if (visitedHash[node.key]) { 93 | return 94 | } 95 | 96 | visitFn(node) 97 | visitedHash[node.key] = true 98 | 99 | node.children.forEach(child => { 100 | explore(child) 101 | }) 102 | } 103 | 104 | explore(startingNode) 105 | } 106 | } 107 | } 108 | 109 | const graph = createGraph(true) 110 | const nodes = ['a', 'b', 'c', 'd', 'e', 'f'] 111 | const edges = [ 112 | ['a', 'b'], 113 | ['a', 'e'], 114 | ['a', 'f'], 115 | ['b', 'd'], 116 | ['b', 'e'], 117 | ['c', 'b'], 118 | ['d', 'c'], 119 | ['d', 'e'] 120 | ] 121 | 122 | nodes.forEach(node => { 123 | graph.addNode(node) 124 | }) 125 | 126 | edges.forEach(nodes => { 127 | graph.addEdge(...nodes) 128 | }) 129 | 130 | graph.bfs('a', node => { 131 | console.log(node.key) 132 | }) 133 | 134 | exports.createNode = createNode 135 | exports.createGraph = createGraph 136 | -------------------------------------------------------------------------------- /graphs/depthFirstSearch.js: -------------------------------------------------------------------------------- 1 | const { createQueue } = require('../queues/index.js') 2 | 3 | function createNode(key) { 4 | const children = [] 5 | 6 | return { 7 | key, 8 | children, 9 | addChild(node) { 10 | children.push(node) 11 | } 12 | } 13 | } 14 | 15 | function createGraph(directed = false) { 16 | const nodes = [] 17 | const edges = [] 18 | 19 | return { 20 | directed, 21 | nodes, 22 | edges, 23 | 24 | addNode(key) { 25 | nodes.push(createNode(key)) 26 | }, 27 | 28 | getNode(key) { 29 | return nodes.find(n => n.key === key) 30 | }, 31 | 32 | addEdge(node1Key, node2Key) { 33 | const node1 = this.getNode(node1Key) 34 | const node2 = this.getNode(node2Key) 35 | 36 | node1.addChild(node2) 37 | 38 | if (!directed) { 39 | node2.addChild(node1) 40 | } 41 | 42 | edges.push(`${node1Key}${node2Key}`) 43 | }, 44 | 45 | print() { 46 | return nodes 47 | .map(({ children, key }) => { 48 | let result = `${key}` 49 | 50 | if (children.length) { 51 | result += ` => ${children.map(node => node.key).join(' ')}` 52 | } 53 | 54 | return result 55 | }) 56 | .join('\n') 57 | }, 58 | 59 | bfs(startingNodeKey, visitFn) { 60 | const startingNode = this.getNode(startingNodeKey) 61 | const visitedHash = nodes.reduce((acc, cur) => { 62 | acc[cur.key] = false 63 | return acc 64 | }, {}) 65 | const queue = createQueue() 66 | queue.enqueue(startingNode) 67 | 68 | while (!queue.isEmpty()) { 69 | const currentNode = queue.dequeue() 70 | 71 | if (!visitedHash[currentNode.key]) { 72 | visitFn(currentNode) 73 | visitedHash[currentNode.key] = true 74 | } 75 | 76 | currentNode.children.forEach(node => { 77 | if (!visitedHash[node.key]) { 78 | queue.enqueue(node) 79 | } 80 | }) 81 | } 82 | }, 83 | 84 | dfs(startingNodeKey, visitFn) { 85 | const startingNode = this.getNode(startingNodeKey) 86 | const visitedHash = nodes.reduce((acc, cur) => { 87 | acc[cur.key] = false 88 | return acc 89 | }, {}) 90 | 91 | function explore(node) { 92 | if (visitedHash[node.key]) { 93 | return 94 | } 95 | 96 | visitFn(node) 97 | visitedHash[node.key] = true 98 | 99 | node.children.forEach(child => { 100 | explore(child) 101 | }) 102 | } 103 | 104 | explore(startingNode) 105 | } 106 | } 107 | } 108 | 109 | const graph = createGraph(true) 110 | const nodes = ['a', 'b', 'c', 'd', 'e', 'f'] 111 | const edges = [ 112 | ['a', 'b'], 113 | ['a', 'e'], 114 | ['a', 'f'], 115 | ['b', 'd'], 116 | ['b', 'e'], 117 | ['c', 'b'], 118 | ['d', 'c'], 119 | ['d', 'e'] 120 | ] 121 | 122 | nodes.forEach(node => { 123 | graph.addNode(node) 124 | }) 125 | 126 | edges.forEach(nodes => { 127 | graph.addEdge(...nodes) 128 | }) 129 | 130 | graph.dfs('a', node => { 131 | console.log(node.key) 132 | }) 133 | 134 | exports.createNode = createNode 135 | exports.createGraph = createGraph 136 | -------------------------------------------------------------------------------- /graphs/dfsScript.md: -------------------------------------------------------------------------------- 1 | # Depth First Search 2 | 3 | As the name implies, depth first search is a graph search algorithm that explores as far as it can down a particular path before climbing back up working down another one. 4 | 5 | we'll add a depthFirstSearch method to our graph object. This method will receive two arguments, a startingNodeKey to find which node in the graph to start the search from, and a visiting function to be called as we visit each node for the first time. 6 | 7 | First, we need to get the starting node by using the getNode method 8 | 9 | Next, we need to keep track of which nodes we have visited and which ones we havent. There are several ways to do this, but I'm going to do it by keeping a visited object, where each key corresponds with a node's key, and the value starts as false. 10 | 11 | Now, depth first search involves a recursive algorithm, essentially if there's another level to go, explore that one, until we reach a dead end. 12 | 13 | I'm going to create an explore function that we will call on nodes. If we've visited this node, then return from the function immediately. Otherwise, call the visiting function on the node, mark it as visited, and then for each of its neighbors we need to explore deeper. Because we have our guard statement at the start of our function, we can safely call it on each node, knowing that if we've visited it, nothing will be returned. 14 | 15 | We then start our algorithm by calling explore on our starting node. 16 | 17 | Now that we've created our depth first search algorithm, let's use it. 18 | 19 | I've premade a graph that I will copy paste into the file that looks like this. 20 | 21 | I want to call depthFirstSearch on our graph, starting at node 'a' and logging out each key as we go. If I bring the graph back onto the screen and we call our searchin the terminal, we can see that went as far as we could down the path of A before coming back up and following another path. 22 | -------------------------------------------------------------------------------- /graphs/index.js: -------------------------------------------------------------------------------- 1 | const { createQueue } = require('../queues/index.js') 2 | 3 | function createNode(key) { 4 | const children = [] 5 | 6 | return { 7 | key, 8 | children, 9 | addChild(node) { 10 | children.push(node) 11 | } 12 | } 13 | } 14 | 15 | function createGraph(directed = false) { 16 | const nodes = [] 17 | const edges = [] 18 | 19 | return { 20 | directed, 21 | nodes, 22 | edges, 23 | 24 | addNode(key) { 25 | nodes.push(createNode(key)) 26 | }, 27 | 28 | getNode(key) { 29 | return nodes.find(n => n.key === key) 30 | }, 31 | 32 | addEdge(node1Key, node2Key) { 33 | const node1 = this.getNode(node1Key) 34 | const node2 = this.getNode(node2Key) 35 | 36 | node1.addChild(node2) 37 | 38 | if (!directed) { 39 | node2.addChild(node1) 40 | } 41 | 42 | edges.push(`${node1Key}${node2Key}`) 43 | }, 44 | 45 | print() { 46 | return nodes 47 | .map(({ children, key }) => { 48 | let result = `${key}` 49 | 50 | if (children.length) { 51 | result += ` => ${children 52 | .map(node => node.key) 53 | .join(' ')}` 54 | } 55 | 56 | return result 57 | }) 58 | .join('\n') 59 | }, 60 | 61 | bfs(startingNodeKey, visitFn) { 62 | const startingNode = this.getNode( 63 | startingNodeKey 64 | ) 65 | const visitedHash = nodes.reduce( 66 | (acc, cur) => { 67 | acc[cur.key] = false 68 | return acc 69 | }, 70 | {} 71 | ) 72 | const queue = createQueue() 73 | queue.enqueue(startingNode) 74 | 75 | while (!queue.isEmpty()) { 76 | const currentNode = queue.dequeue() 77 | 78 | if (!visitedHash[currentNode.key]) { 79 | visitFn(currentNode) 80 | visitedHash[currentNode.key] = true 81 | } 82 | 83 | currentNode.children.forEach(node => { 84 | if (!visitedHash[node.key]) { 85 | queue.enqueue(node) 86 | } 87 | }) 88 | } 89 | }, 90 | 91 | dfs(startingNodeKey, visitFn) { 92 | const startingNode = this.getNode( 93 | startingNodeKey 94 | ) 95 | const visitedHash = nodes.reduce( 96 | (acc, cur) => { 97 | acc[cur.key] = false 98 | return acc 99 | }, 100 | {} 101 | ) 102 | 103 | function explore(node) { 104 | if (visitedHash[node.key]) { 105 | return 106 | } 107 | 108 | visitFn(node) 109 | visitedHash[node.key] = true 110 | 111 | node.children.forEach(child => { 112 | explore(child) 113 | }) 114 | } 115 | 116 | explore(startingNode) 117 | } 118 | } 119 | } 120 | 121 | const graph = createGraph(true) 122 | 123 | graph.addNode('Kyle') 124 | graph.addNode('Anna') 125 | graph.addNode('Krios') 126 | graph.addNode('Tali') 127 | 128 | 129 | graph.addEdge('Kyle', 'Anna') 130 | graph.addEdge('Anna', 'Kyle') 131 | graph.addEdge('Kyle', 'Krios') 132 | graph.addEdge('Kyle', 'Tali') 133 | graph.addEdge('Anna', 'Krios') 134 | graph.addEdge('Anna', 'Tali') 135 | graph.addEdge('Krios', 'Anna') 136 | graph.addEdge('Tali', 'Kyle') 137 | 138 | console.log(graph.print()) 139 | 140 | exports.createNode = createNode 141 | exports.createGraph = createGraph 142 | -------------------------------------------------------------------------------- /graphs/index.test.js: -------------------------------------------------------------------------------- 1 | const { createGraph, createNode } = require('./index') 2 | 3 | describe('Node', () => { 4 | let node 5 | beforeEach(() => { 6 | node = createNode('a') 7 | }) 8 | 9 | test('existence', () => { 10 | expect(node).toBeDefined() 11 | }) 12 | 13 | test('key', () => { 14 | expect(node.key).toEqual('a') 15 | }) 16 | 17 | test('children', () => { 18 | expect(node.children).toBeDefined() 19 | expect(Array.isArray(node.children)) 20 | }) 21 | 22 | test('addChild', () => { 23 | node.addChild('b') 24 | 25 | expect(node.children.length).toEqual(1) 26 | expect(node.children.includes('b')).toBe(true) 27 | }) 28 | }) 29 | 30 | describe('Graph', () => { 31 | let graph 32 | beforeEach(() => { 33 | graph = createGraph() 34 | }) 35 | 36 | test('existence', () => { 37 | expect(graph).toBeDefined() 38 | }) 39 | 40 | test('directed', () => { 41 | expect(graph.directed).toBe(false) 42 | 43 | graph = createGraph(true) 44 | expect(graph.directed).toBe(true) 45 | }) 46 | 47 | test('addNode', () => { 48 | graph.addNode('a') 49 | 50 | expect(graph.nodes.map(n => n.key).includes('a')).toBe(true) 51 | }) 52 | 53 | test('getNode', () => { 54 | graph.addNode('a') 55 | 56 | expect(graph.getNode('a')).toBeDefined() 57 | }) 58 | 59 | describe('addEdge', () => { 60 | test('undirected', () => { 61 | graph.addNode('a') 62 | graph.addNode('b') 63 | graph.addEdge('a', 'b') 64 | 65 | expect( 66 | graph 67 | .getNode('a') 68 | .children.map(node => node.key) 69 | .includes('b') 70 | ).toBe(true) 71 | expect( 72 | graph 73 | .getNode('b') 74 | .children.map(node => node.key) 75 | .includes('a') 76 | ).toBe(true) 77 | }) 78 | 79 | test('directed', () => { 80 | graph = createGraph(true) 81 | graph.addNode('a') 82 | graph.addNode('b') 83 | graph.addEdge('a', 'b') 84 | 85 | expect( 86 | graph 87 | .getNode('a') 88 | .children.map(node => node.key) 89 | .includes('b') 90 | ).toBe(true) 91 | expect( 92 | graph 93 | .getNode('b') 94 | .children.map(node => node.key) 95 | .includes('a') 96 | ).toBe(false) 97 | }) 98 | }) 99 | 100 | test('nodes', () => { 101 | graph.addNode('a') 102 | graph.addNode('b') 103 | 104 | expect(graph.nodes).toBeDefined() 105 | expect(graph.nodes.length).toEqual(2) 106 | }) 107 | 108 | test('edges', () => { 109 | graph.addNode('a') 110 | graph.addNode('b') 111 | graph.addNode('c') 112 | 113 | graph.addEdge('a', 'b') 114 | graph.addEdge('a', 'c') 115 | graph.addEdge('b', 'c') 116 | 117 | expect(graph.edges).toBeDefined() 118 | expect(graph.edges.length).toEqual(3) 119 | }) 120 | 121 | describe('print', () => { 122 | test('undirected', () => { 123 | const nodes = ['a', 'b', 'c', 'd', 'e'] 124 | nodes.forEach(node => { 125 | graph.addNode(node) 126 | }) 127 | 128 | const edges = [['a', 'b'], ['a', 'c'], ['a', 'e'], ['b', 'd'], ['c', 'd']] 129 | edges.forEach(edge => { 130 | graph.addEdge(...edge) 131 | }) 132 | 133 | expect(graph.print()).toEqual( 134 | `a => b c e 135 | b => a d 136 | c => a d 137 | d => b c 138 | e => a` 139 | ) 140 | }) 141 | 142 | test('directed', () => { 143 | graph = createGraph(true) 144 | 145 | const nodes = ['a', 'b', 'c', 'd', 'e'] 146 | nodes.forEach(node => { 147 | graph.addNode(node) 148 | }) 149 | 150 | const edges = [['a', 'b'], ['a', 'c'], ['a', 'e'], ['b', 'd'], ['c', 'd']] 151 | edges.forEach(edge => { 152 | graph.addEdge(...edge) 153 | }) 154 | 155 | expect(graph.print()).toEqual( 156 | `a => b c e 157 | b => d 158 | c => d 159 | d 160 | e` 161 | ) 162 | }) 163 | }) 164 | 165 | test('bfs', () => { 166 | const nodes = ['a', 'b', 'c', 'd', 'e'] 167 | nodes.forEach(node => { 168 | graph.addNode(node) 169 | }) 170 | 171 | const edges = [['a', 'b'], ['a', 'c'], ['a', 'e'], ['b', 'd'], ['c', 'd']] 172 | edges.forEach(edge => { 173 | graph.addEdge(...edge) 174 | }) 175 | 176 | let result = '' 177 | 178 | function visit(node) { 179 | result += result.length === 0 ? node.key : ` => ${node.key}` 180 | } 181 | 182 | graph.bfs('a', visit) 183 | 184 | expect(result).toEqual('a => b => c => e => d') 185 | }) 186 | 187 | test('dfs', () => { 188 | const nodes = ['a', 'b', 'c', 'd', 'e'] 189 | nodes.forEach(node => { 190 | graph.addNode(node) 191 | }) 192 | 193 | const edges = [['a', 'b'], ['a', 'c'], ['a', 'e'], ['b', 'd'], ['c', 'd']] 194 | edges.forEach(edge => { 195 | graph.addEdge(...edge) 196 | }) 197 | 198 | let result = '' 199 | 200 | function visit(node) { 201 | result += result.length === 0 ? node.key : ` => ${node.key}` 202 | } 203 | 204 | graph.dfs('a', visit) 205 | 206 | expect(result).toEqual('a => b => d => c => e') 207 | }) 208 | }) 209 | -------------------------------------------------------------------------------- /graphs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "intro-to-data-structures-and-algorithms", 3 | "version": "1.0.0", 4 | "description": "An egghead course to introduce data structures and algorithms in JavaScript", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest", 8 | "test:watch": "jest --watch" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms.git" 13 | }, 14 | "author": "Kyle Shevlin (http://kyleshevlin.com/)", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms/issues" 18 | }, 19 | "homepage": "https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms#readme", 20 | "devDependencies": { 21 | "jest": "^23.6.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /graphs/script.md: -------------------------------------------------------------------------------- 1 | # Graphs 2 | 3 | A graph is a collection made up of nodes, also known as vertices, that may or may not be connected to other nodes. These connections are called edges. 4 | 5 | To build our graph, we're going to start by creating our node factory function. 6 | 7 | When we create a node, we need to give it a value that we can use to identify it, we'll call it a key. We'll describe adjacent nodes in our graph as neighbors, as there is no hierarchy implied by the data structure. Lastly, we need a way to add neighbors to our node, so we'll add an addNeighbor method that pushes a node into our neighbors array. 8 | 9 | Now, we can create our graph factory function. createGraph receives on argument, directed. A directed graph's edges point in one direction from a node to another. In order for two nodes to have symmetric edges, they must both point to each other. 10 | 11 | In an undirected graph, we assume this symmetry of edges, that when one node points to the other, then the opposite is true as well. 12 | 13 | We'll set directed to false by default, and we'll return in it the object created by our factory. 14 | 15 | Now, a graph is a collection of nodes and a collection of edges, so we'll create arrays for both in closure. We'll pass these references to our returned object as well. 16 | 17 | Now we'll start adding methods to our graph. The first will be the addNode method. Add node receives a key as an argument and uses our createNode function to add a node to the nodes array. 18 | 19 | We also want to be able to get nodes from our graph, so we'll add a getNode method. getNode will search for a matching key and return the first one, so we'll use array's find method. 20 | 21 | Next, we need to be able to add edges between nodes, so we'll create an addEdge method. addEdge receives two arguments, node1Key, and node2Key. We use our getNode function to get these two nodes. We then will add node2 as a neighbor to node1. This happens regardless of whether the graph is directed or undirected. The same goes for pushing our edge into the edges array. We'll simply pass a string of our two keys, adding a delimiter between them. 22 | 23 | Now, If the graph is undirected, we also want to add node1 as a neighbor of node2. We don't worry about adding a second edge because we don't want the number of edges in our graph to be misrepresented. If we were to add an edge here, we'd have to implement a way of resolving both edges as one were we ever to count them and provide a edgesLength property. 24 | 25 | Now that we can add nodes and edges, we'd probably like to be able to visualize our graph. We'll create a print method that will print out our nodes and neighbors in the console. 26 | 27 | We want to return a string derived from our nodes. So I'm going to map over our nodes, gather some data about them and return a result. 28 | 29 | I'll destructure the key and neighbors from each node. The string we'll return will begin with the key. Then if there are any neighbors, we want to concatenate that to our current result. We'll map over each neighbor and add its key. Lastly, we'll return the result and call a join with a newline on our array of strings. 30 | 31 | Now, we can create a graph and try it out. 32 | 33 | I'm going to create a directed graph of the Shevlin family, me my wife and our two cats Krios and Tali. This graph we'll describe who loves whom in this household. 34 | 35 | Now, my wife and I love each other, so we can add an edge between us going in both directions. 36 | 37 | And we love our cats, her a tad more than me, I'm a dog person, but we have no way of weighting our edges. So we'll add edges from me to both our cats and from Anna to both our cats. 38 | 39 | Now, our cats have very little love for each other. They tolerate one another, but they do fight from time to time, no edges there, BUT Krios loves Anna, and Tali displays some affection towards me. 40 | 41 | Now, if we call print on our graph, we'll get a read out of our relationships to each other. 42 | -------------------------------------------------------------------------------- /insertionSort/index.js: -------------------------------------------------------------------------------- 1 | const { printArray } = require('../utils') 2 | 3 | function insertionSort(array) { 4 | let i 5 | let j 6 | let count = 0 7 | 8 | for (i = 1; i < array.length; i++) { 9 | for (j = 0; j < i; j++) { 10 | printArray(array) 11 | count++ 12 | 13 | if (array[i] < array[j]) { 14 | const [item] = array.splice(i, 1) 15 | array.splice(j, 0, item) 16 | } 17 | } 18 | } 19 | 20 | printArray(array) 21 | console.log(`Iterations: ${count}`) 22 | 23 | return array 24 | } 25 | 26 | let numbers = [10, 5, 6, 3, 2, 8, 9, 4, 7, 1] 27 | 28 | insertionSort(numbers) 29 | 30 | exports.insertionSort = insertionSort 31 | -------------------------------------------------------------------------------- /insertionSort/index.test.js: -------------------------------------------------------------------------------- 1 | const { insertionSort } = require('./index') 2 | 3 | test('Insertion Sort', () => { 4 | const nums = [10, 8, 4, 3, 6, 2, 1, 9, 7, 5] 5 | expect(insertionSort(nums)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) 6 | }) 7 | -------------------------------------------------------------------------------- /insertionSort/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "intro-to-data-structures-and-algorithms", 3 | "version": "1.0.0", 4 | "description": "An egghead course to introduce data structures and algorithms in JavaScript", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest", 8 | "test:watch": "jest --watch" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms.git" 13 | }, 14 | "author": "Kyle Shevlin (http://kyleshevlin.com/)", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms/issues" 18 | }, 19 | "homepage": "https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms#readme", 20 | "devDependencies": { 21 | "jest": "^23.6.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /insertionSort/script.md: -------------------------------------------------------------------------------- 1 | # Insertion Sort 2 | 3 | To create our insertion sort algorithm, we'll create a function that accepts the array, sorts it, and returns it. 4 | 5 | Insertion sort works by using a nested loop. We'll take the first item in the list and assume we have a sorted list of length 1. We'll then compare the next item to it and place it before or after it depending on which item is greater. 6 | 7 | Our first loop will use the iterator i and starts at the 2nd element in the array, index 1. 8 | 9 | Our inner loop starts at the first item in the list, and only iterates up to the current iteration of our outer loop. Thus, in the first pass, it will only compare the first item with the second. In the second outer loop, it'll compare the first item with the 3rd, and the second item with the third. So on and so forth. If the item in the outer loop is less than our item in the inner loop, we'll remove the item from the array, and splice it in the location of the item at index j. 10 | -------------------------------------------------------------------------------- /linkedLists/index.js: -------------------------------------------------------------------------------- 1 | function createNode(value) { 2 | return { 3 | value, 4 | next: null 5 | } 6 | } 7 | 8 | function createLinkedList() { 9 | return { 10 | head: null, 11 | tail: null, 12 | length: 0, 13 | 14 | push(value) { 15 | const node = createNode(value) 16 | 17 | if (this.head === null) { 18 | this.head = node 19 | this.tail = node 20 | this.length++ 21 | return node 22 | } 23 | 24 | this.tail.next = node 25 | this.tail = node 26 | this.length++ 27 | 28 | return node 29 | }, 30 | 31 | pop() { 32 | if (this.isEmpty()) { 33 | return null 34 | } 35 | 36 | const node = this.tail 37 | 38 | if (this.head === this.tail) { 39 | this.head = null 40 | this.tail = null 41 | this.length-- 42 | return node 43 | } 44 | 45 | let current = this.head 46 | let penultimate 47 | while (current) { 48 | if (current.next === this.tail) { 49 | penultimate = current 50 | break 51 | } 52 | 53 | current = current.next 54 | } 55 | 56 | penultimate.next = null 57 | this.tail = penultimate 58 | this.length-- 59 | 60 | return node 61 | }, 62 | 63 | get(index) { 64 | if (index < 0 || index > this.length - 1) { 65 | return null 66 | } 67 | 68 | if (index === 0) { 69 | return this.head 70 | } 71 | 72 | let current = this.head 73 | let i = 0 74 | while (i < index) { 75 | i++ 76 | current = current.next 77 | } 78 | 79 | return current 80 | }, 81 | 82 | delete(index) { 83 | if (index < 0 || index > this.length - 1) { 84 | return null 85 | } 86 | 87 | if (index === 0) { 88 | const deleted = this.head 89 | 90 | this.head = this.head.next 91 | this.length-- 92 | 93 | return deleted 94 | } 95 | 96 | let current = this.head 97 | let previous 98 | let i = 0 99 | 100 | while (i < index) { 101 | i++ 102 | previous = current 103 | current = current.next 104 | } 105 | 106 | const deleted = current 107 | previous.next = current.next 108 | 109 | if (previous.next === null) { 110 | this.tail = previous 111 | } 112 | 113 | this.length-- 114 | 115 | return deleted 116 | }, 117 | 118 | isEmpty() { 119 | return this.length === 0 120 | }, 121 | 122 | print() { 123 | const values = [] 124 | let current = this.head 125 | 126 | while (current) { 127 | values.push(current.value) 128 | current = current.next 129 | } 130 | 131 | return values.join(' => ') 132 | } 133 | } 134 | } 135 | 136 | const list = createLinkedList() 137 | const values = ['a', 'b', 'c', 'd', 'e'] 138 | const nodes = values.map(val => list.push(val)) 139 | 140 | list.pop() 141 | console.log(list.delete(1)) 142 | console.log(list.print()) 143 | 144 | exports.createNode = createNode 145 | exports.createLinkedList = createLinkedList 146 | -------------------------------------------------------------------------------- /linkedLists/index.test.js: -------------------------------------------------------------------------------- 1 | const { createLinkedList, createNode } = require('./index') 2 | 3 | describe('Node', () => { 4 | let node 5 | beforeEach(() => { 6 | node = createNode('a') 7 | }) 8 | 9 | test('instantiation', () => { 10 | expect(node).toBeDefined() 11 | expect(node.value).toEqual('a') 12 | }) 13 | }) 14 | 15 | describe('Linked List', () => { 16 | let linkedList 17 | beforeEach(() => { 18 | linkedList = createLinkedList() 19 | }) 20 | 21 | test('existence', () => { 22 | expect(linkedList).toBeDefined() 23 | }) 24 | 25 | test('length', () => { 26 | expect(linkedList.length).toEqual(0) 27 | }) 28 | 29 | test('head', () => { 30 | expect(linkedList.head).toBeNull() 31 | 32 | const a = linkedList.push('a') 33 | expect(linkedList.head).toEqual(a) 34 | }) 35 | 36 | test('tail', () => { 37 | expect(linkedList.tail).toBeNull() 38 | 39 | const a = linkedList.push('a') 40 | expect(linkedList.tail).toEqual(a) 41 | }) 42 | 43 | test('push', () => { 44 | const a = linkedList.push('a') 45 | expect(linkedList.head.value).toEqual('a') 46 | expect(linkedList.tail.value).toEqual('a') 47 | expect(linkedList.length).toEqual(1) 48 | 49 | const b = linkedList.push('b') 50 | 51 | expect(linkedList.head.next).toEqual(b) 52 | expect(linkedList.tail).toEqual(b) 53 | expect(linkedList.length).toEqual(2) 54 | }) 55 | 56 | test('pop', () => { 57 | // empty list 58 | expect(linkedList.pop()).toEqual(null) 59 | expect(linkedList.length).toEqual(0) 60 | 61 | // List of length 1 62 | linkedList.push(1) 63 | expect(linkedList.length).toEqual(1) 64 | const node = linkedList.pop() 65 | expect(node.value).toEqual(1) 66 | expect(linkedList.head).toEqual(null) 67 | expect(linkedList.tail).toEqual(null) 68 | expect(linkedList.length).toEqual(0) 69 | 70 | // List of length < 1 71 | const values = ['a', 'b', 'c', 'd', 'e'] 72 | const nodes = values.map(val => linkedList.push(val)) 73 | 74 | expect(linkedList.pop().value).toEqual('e') 75 | expect(linkedList.tail.value).toEqual('d') 76 | expect(linkedList.length).toEqual(4) 77 | }) 78 | 79 | test('get', () => { 80 | const values = ['a', 'b', 'c', 'd', 'e'] 81 | const nodes = values.map(val => linkedList.push(val)) 82 | 83 | const badIndex = linkedList.get(7) 84 | expect(badIndex).toBeNull() 85 | 86 | const head = linkedList.get(0) 87 | expect(head).toEqual(nodes[0]) 88 | 89 | const atIndexThree = linkedList.get(3) 90 | expect(atIndexThree).toEqual(nodes[3]) 91 | }) 92 | 93 | describe('delete', () => { 94 | const values = ['a', 'b', 'c', 'd', 'e'] 95 | let nodes 96 | 97 | beforeEach(() => { 98 | linkedList = createLinkedList() 99 | nodes = values.map(val => linkedList.push(val)) 100 | }) 101 | 102 | test('bad index', () => { 103 | // the length of the list is the first index outside of 104 | // the acceptable range of values 105 | const index = values.length 106 | const badIndex = linkedList.delete(index) 107 | expect(badIndex).toBeNull() 108 | }) 109 | 110 | test('head', () => { 111 | const head = linkedList.delete(0) 112 | expect(head).toEqual(nodes[0]) 113 | }) 114 | 115 | test('middlish index', () => { 116 | const index = 2 117 | const c = linkedList.delete(index) 118 | 119 | expect(c).toEqual(nodes[index]) 120 | expect(linkedList.length).toEqual(values.length - 1) 121 | }) 122 | 123 | test('tail', () => { 124 | const tail = linkedList.delete(values.length - 1) 125 | expect(tail).toEqual(nodes[nodes.length - 1]) 126 | expect(linkedList.tail).not.toEqual(tail) 127 | expect(linkedList.tail).toEqual(nodes[nodes.length - 2]) 128 | expect(linkedList.tail.next).toBeNull() 129 | }) 130 | }) 131 | 132 | test('isEmpty', () => { 133 | expect(linkedList.isEmpty()).toBe(true) 134 | linkedList.push('a') 135 | expect(linkedList.isEmpty()).toBe(false) 136 | }) 137 | 138 | test('print', () => { 139 | const values = ['a', 'b', 'c', 'd', 'e'] 140 | values.forEach(val => { 141 | linkedList.push(val) 142 | }) 143 | 144 | expect(linkedList.print()).toEqual('a => b => c => d => e') 145 | }) 146 | }) 147 | -------------------------------------------------------------------------------- /linkedLists/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "intro-to-data-structures-and-algorithms", 3 | "version": "1.0.0", 4 | "description": "An egghead course to introduce data structures and algorithms in JavaScript", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest", 8 | "test:watch": "jest --watch" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms.git" 13 | }, 14 | "author": "Kyle Shevlin (http://kyleshevlin.com/)", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms/issues" 18 | }, 19 | "homepage": "https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms#readme", 20 | "devDependencies": { 21 | "jest": "^23.6.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /linkedLists/script.md: -------------------------------------------------------------------------------- 1 | # Linked Lists 2 | 3 | A linked list is a collection of items where each item has a connection to the next item in the list, hence the word "linked" in the name. 4 | 5 | To make our linked list, we first want to create a Node object for each item in our list. Our node has two properties, the value stored at this node, and a "next" property which gets pointed to the next item in our list. 6 | 7 | Next, we want to make our list data structure. Our list will have several properties we want to keep track of, a head, a tail, and a length. We'll also create several methods for our list: push, pop, get, delete, and isEmpty. 8 | 9 | Our head and tail will both start as null and our length is 0. And we can add the isEmpty method. It's simple to implement as it returns whether our length is 0 or not. 10 | 11 | Now, we want to add our push method. When we want to push a value onto our list, we need to first turn that value into a node using our factory function. Push places a new node at the end of our list, so we can use logic to determine the actions necessary to place the node in the correct spot. 12 | 13 | No matter what state our list is in, we know that eventually we need to update our tail property to this node. We also know that we will need to increment our length property. So let's write that, but I'm going to add some whitespace above it for the other steps we need to take first. 14 | 15 | If our list does not have a head, then we can also deduce it doesn't have a tail because our list is empty. The opposite is also true, if our list has a head, then we know that we have a tail as well. So, if we don't currently have a head, our list's head property is set to our new node. However, if we do have a head, then we know we have a current tail, we need to set our current tail's next property to our new node. 16 | 17 | Moving on to the pop method, things get a bit more complicated. We need to reason out a few scenarios, how do we pop items when our list is empty, when our list has a length of 1, and when our list has many items. An empty list is easy, there is nothing to pop, so we will return null. Our check could be against our length or if we do not have a head for our list. 18 | 19 | Now, for our remaining scenarios, we're always going to return the tail node, so let's store that in a variable. 20 | 21 | A list of length 1 means that our head and our tail are the same node, so we set both our head and tail back to null. Our length will need to be reset to 0 or decremented, either way gets us to a length of 0. Then we return the node we stored. 22 | 23 | Now, for all other scenarios, we need to set the item before our tail, the penultimate item, as our new tail with a next value of null. How do we do this? We have to start at the head and search each item one by one until we get to our item. This is very inefficient and is one of the tradeoffs made with linked lists. 24 | 25 | We'll set a current variable to our head, and a penultimate variable undefined. While current exists, check if the current next property equals our tail, if it does, we can set our penultimate value and break our loop. Otherwise, set current to current.next and continue 26 | 27 | Now we can assume we have our penultimate, what we want to do is set penultimate's next property to null, then our tail to the penultimate node. Finally, we decrement the length and return the node we stored long ago. 28 | 29 | Now we can move on to get. The get method receives an index as an argument. If the index given is less than 0 or greater than our length, we want to return null, as the request is out of the bounds of our list. Otherwise, in order to return that node, we will loop through each item, incrementing an iterator and when our iterator matches our passed in index, we'll return that item. 30 | 31 | Lastly, we can implement delete. The delete method receives an index as an argument. Similar to get, if the index is less than 0 or greater than our length, we return null because such an item does not exist in our list. 32 | 33 | Now, if the index is 0, we need to handle this situation uniquely. We need to return the head node, while setting the head's next node as the new head. 34 | 35 | Now, in any other scenario, we're going to loop through each item, incrementing an iterator with each loop. However, we'll want to store our previous node on each iteration as well. When we get to our matching item, we'll simply set our previous node's next property to equal our current node's next property, effectively slicing out and returning that current node. 36 | 37 | I want to quickly make one more method, a print method, so we can visualize our list. We'll create an array of values, and a current variable. While we have a current node, push it's value into the values array. When we're done. We'll print out the to console our values joined by an arrow symbol. 38 | 39 | Now we have our list, let's try it out. We'll create a new list, we can check that it's indeed empty. 40 | 41 | Now, let's push some values on to it. If I pop off the last one, we can see that our value was the last one we pushed, and that the tail of our list is now the penultimate item. 42 | 43 | Let's try get out and get the item at index 1, great that worked. Now let's delete it and see that our head's next node was previously our third item. 44 | -------------------------------------------------------------------------------- /mergeSort/index.js: -------------------------------------------------------------------------------- 1 | const { printArray } = require('../utils') 2 | 3 | let count = 0 4 | 5 | function merge(left, right) { 6 | count++ 7 | const sorted = [] 8 | 9 | while (left.length && right.length) { 10 | if (left[0] <= right[0]) { 11 | sorted.push(left.shift()) 12 | } else { 13 | sorted.push(right.shift()) 14 | } 15 | } 16 | 17 | const results = [...sorted, ...left, ...right] 18 | 19 | console.log(results) 20 | console.log(count) 21 | 22 | return results 23 | } 24 | 25 | function mergeSort(array) { 26 | console.log(array) 27 | count++ 28 | const { length } = array 29 | 30 | if (length < 2) { 31 | return array 32 | } 33 | 34 | const middle = Math.floor(length / 2) 35 | const left = array.slice(0, middle) 36 | const right = array.slice(middle) 37 | 38 | return merge(mergeSort(left), mergeSort(right)) 39 | } 40 | 41 | let numbers = [10, 5, 6, 3, 2, 8, 9, 4, 7, 1] 42 | 43 | mergeSort(numbers) 44 | 45 | exports.mergeSort = mergeSort 46 | -------------------------------------------------------------------------------- /mergeSort/index.test.js: -------------------------------------------------------------------------------- 1 | const { mergeSort } = require('./index') 2 | 3 | test('Merge Sort', () => { 4 | const nums = [10, 8, 4, 3, 6, 2, 1, 9, 7, 5] 5 | expect(mergeSort(nums)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) 6 | }) 7 | -------------------------------------------------------------------------------- /mergeSort/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "intro-to-data-structures-and-algorithms", 3 | "version": "1.0.0", 4 | "description": "An egghead course to introduce data structures and algorithms in JavaScript", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest", 8 | "test:watch": "jest --watch" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms.git" 13 | }, 14 | "author": "Kyle Shevlin (http://kyleshevlin.com/)", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms/issues" 18 | }, 19 | "homepage": "https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms#readme", 20 | "devDependencies": { 21 | "jest": "^23.6.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /mergeSort/script.md: -------------------------------------------------------------------------------- 1 | # Merge Sort 2 | 3 | Merge sort is a recursive sorting algorithm. If you don't understand recursion, I recommend finding a resource to learn it, but in brief, recursion is the act of a function calling itself. Thus, mergeSort is accomplished by the algorithm calling itself to provide a solution. 4 | 5 | Merge sort divides the given array into two halves, a left and right array, and calls merge sort on these sub arrays. We continue to split our sub arrays until we get arrays whose length is less than two. We then begin to stitch our small arrays back together, sorting them on the way up. This is an efficient algorithm because we start by sorting very small arrays, and by the time we reach our larger ones, they are already mostly sorted, saving us the need for expensive loops. 6 | 7 | To create our algorithm, we'll actually need two functions: our `mergeSort` function, and our `merge` function that actually combines the sub arrays back together. 8 | 9 | We'll create a function mergeSort which receives an array. Since this will be called recursively, we want to start with our base case scenario, a guard statement that prevents us from causing stack overflows. In this case, if the array's length is less than two, return the array. There is no further splitting that we need to do. 10 | 11 | Otherwise, we continue on by dividing the array into left and right halves, and we return the merging of calling mergeSort on both halves. 12 | 13 | Next, we write our merge function. This function receives two arrays as arguments and sorts them. We create an array to place our sorted items into. Now, we compare the items in our array and proceed to push them onto our sorted array until we run out of items in one of the arrays. Using the length property of these arrays to coerce booleans is very helpful. Once one array is empty, the other array can be spread onto the end of the array. The ES6 spread operator makes this very easy to do. 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "intro-to-data-structures-and-algorithms", 3 | "version": "1.0.0", 4 | "description": "An egghead course to introduce data structures and algorithms in JavaScript", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest", 8 | "test:watch": "jest --watch" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms.git" 13 | }, 14 | "author": "Kyle Shevlin (http://kyleshevlin.com/)", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms/issues" 18 | }, 19 | "homepage": "https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms#readme", 20 | "devDependencies": { 21 | "jest": "^25.5.2" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /priorityQueue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "intro-to-data-structures-and-algorithms", 3 | "version": "1.0.0", 4 | "description": "An egghead course to introduce data structures and algorithms in JavaScript", 5 | "main": "priorityQueue.js", 6 | "scripts": { 7 | "test": "jest", 8 | "test:watch": "jest --watch" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms.git" 13 | }, 14 | "author": "Kyle Shevlin (http://kyleshevlin.com/)", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms/issues" 18 | }, 19 | "homepage": "https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms#readme", 20 | "devDependencies": { 21 | "jest": "^23.6.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /priorityQueue/priorityQueue.js: -------------------------------------------------------------------------------- 1 | const { createQueue } = require('../queues/index') 2 | 3 | function createPriorityQueue() { 4 | const highPriorityQueue = createQueue() 5 | const lowPriorityQueue = createQueue() 6 | 7 | return { 8 | enqueue(item, isHighPriority = false) { 9 | const queue = isHighPriority ? highPriorityQueue : lowPriorityQueue 10 | queue.enqueue(item) 11 | }, 12 | dequeue() { 13 | if (!highPriorityQueue.isEmpty()) { 14 | return highPriorityQueue.dequeue() 15 | } 16 | 17 | return lowPriorityQueue.dequeue() 18 | }, 19 | peek() { 20 | if (!highPriorityQueue.isEmpty()) { 21 | return highPriorityQueue.peek() 22 | } 23 | 24 | return lowPriorityQueue.peek() 25 | }, 26 | get length() { 27 | return highPriorityQueue.length + lowPriorityQueue.length 28 | }, 29 | isEmpty() { 30 | return highPriorityQueue.isEmpty() && lowPriorityQueue.isEmpty() 31 | } 32 | } 33 | } 34 | 35 | const q = createPriorityQueue() 36 | 37 | q.enqueue('A fix here') 38 | q.enqueue('A bug there') 39 | q.enqueue('A new feature') 40 | 41 | q.dequeue() 42 | q.enqueue('Emergency task!', true) 43 | console.log(q.dequeue()) 44 | console.log(q.peek()) 45 | 46 | exports.createPriorityQueue = createPriorityQueue 47 | -------------------------------------------------------------------------------- /priorityQueue/priorityQueue.test.js: -------------------------------------------------------------------------------- 1 | const { createPriorityQueue } = require('./priorityQueue') 2 | 3 | describe('Priority Queue', () => { 4 | let queue 5 | beforeEach(() => { 6 | queue = createPriorityQueue() 7 | }) 8 | 9 | test('existence', () => { 10 | expect(queue).toBeDefined() 11 | }) 12 | 13 | test('enqueue', () => { 14 | queue.enqueue('foo') 15 | expect(queue.length).toEqual(1) 16 | expect(queue.peek()).toEqual('foo') 17 | 18 | queue.enqueue('bar', true) 19 | expect(queue.length).toEqual(2) 20 | expect(queue.peek()).toEqual('bar') 21 | }) 22 | 23 | test('dequeue', () => { 24 | queue.enqueue('foo') 25 | queue.enqueue('bar', true) 26 | 27 | const first = queue.dequeue() 28 | expect(first).toEqual('bar') 29 | 30 | const second = queue.dequeue() 31 | expect(second).toEqual('foo') 32 | }) 33 | 34 | test('peek', () => { 35 | queue.enqueue('foo') 36 | queue.enqueue('bar', true) 37 | 38 | expect(queue.peek()).toEqual('bar') 39 | }) 40 | 41 | test('length', () => { 42 | expect(queue.length).toEqual(0) 43 | 44 | queue.enqueue('foo') 45 | expect(queue.length).toEqual(1) 46 | 47 | queue.enqueue('bar', true) 48 | expect(queue.length).toEqual(2) 49 | }) 50 | 51 | test('isEmpty', () => { 52 | expect(queue.isEmpty()).toEqual(true) 53 | 54 | queue.enqueue('foo', true) 55 | 56 | expect(queue.isEmpty()).toEqual(false) 57 | 58 | queue.enqueue('bar') 59 | queue.dequeue() 60 | 61 | expect(queue.isEmpty()).toEqual(false) 62 | 63 | queue.dequeue() 64 | 65 | expect(queue.isEmpty()).toEqual(true) 66 | }) 67 | }) 68 | -------------------------------------------------------------------------------- /priorityQueue/priorityQueueScript.md: -------------------------------------------------------------------------------- 1 | # Priority Queue 2 | 3 | We can easily make a priority queue by using two queues, a high priority queue and a low priority queue. 4 | 5 | Our API will be the same as a normal queue: enqueue, dequeue, peek, length, isEmpty, but we'll make some modifications. 6 | 7 | To start, our enqueue method receives a second argument to indicate an item as high priority. If an item is deemed high priority, we'll put it the high priority queue, otherwise in the low priority queue. 8 | 9 | Next, our dequeue method will make sure that the high priority queue is empty before it dequeues from the low priority queue. This ensures that all high priority items are dequeued first. 10 | 11 | Next, our peek method receives a small change. Since we dequeue from the high priority queue first, we need to also peek from there first. If the high priority queue has items, peek there, otherwise peek the low priority queue. 12 | 13 | Next, our length method is just the addition of the two queues lengths. 14 | 15 | Lastly, our isEmpty method is the conjunction of the two queues isEmpty property. 16 | 17 | Now that we've built our priority queue, let's try it out. Let's imagine your manager has given you a few tasks. A fix here, a bug there, a new feature to build. Now, seemingly out of nowhere, a new bug has been added to your queue that's high priority. Company is losing money, systems are failing. We can insert that new bug in as high priority, and now if we peek() it's the next thing to come out of our queue, we have to get it done before we can continue with our other tasks. 18 | -------------------------------------------------------------------------------- /queues/index.js: -------------------------------------------------------------------------------- 1 | function createQueue() { 2 | const queue = [] 3 | 4 | return { 5 | enqueue(x) { 6 | queue.unshift(x) 7 | }, 8 | dequeue() { 9 | if (queue.length === 0) { 10 | return undefined 11 | } 12 | return queue.pop() 13 | }, 14 | peek() { 15 | if (queue.length === 0) { 16 | return undefined 17 | } 18 | return queue[queue.length - 1] 19 | }, 20 | get length() { 21 | return queue.length 22 | }, 23 | isEmpty() { 24 | return queue.length === 0 25 | } 26 | } 27 | } 28 | 29 | exports.createQueue = createQueue 30 | -------------------------------------------------------------------------------- /queues/index.test.js: -------------------------------------------------------------------------------- 1 | const { createQueue } = require('./index') 2 | 3 | describe('Queue', () => { 4 | let queue 5 | beforeEach(() => { 6 | queue = createQueue() 7 | }) 8 | 9 | test('length', () => { 10 | expect(queue.length).toEqual(0) 11 | 12 | queue.enqueue(1) 13 | expect(queue.length).toEqual(1) 14 | 15 | queue.enqueue(2) 16 | expect(queue.length).toEqual(2) 17 | 18 | queue.enqueue(3) 19 | expect(queue.length).toEqual(3) 20 | }) 21 | 22 | test('isEmpty', () => { 23 | expect(queue.isEmpty()).toEqual(true) 24 | 25 | queue.enqueue(1) 26 | expect(queue.isEmpty()).toEqual(false) 27 | 28 | queue.dequeue() 29 | expect(queue.isEmpty()).toEqual(true) 30 | }) 31 | 32 | test('enqueue', () => { 33 | expect(queue.length).toEqual(0) 34 | 35 | const value = 'foo' 36 | queue.enqueue(value) 37 | 38 | expect(queue.length).toEqual(1) 39 | expect(queue.peek()).toEqual(value) 40 | }) 41 | 42 | test('dequeue', () => { 43 | queue.enqueue(1) 44 | queue.enqueue(2) 45 | queue.enqueue(3) 46 | 47 | expect(queue.length).toEqual(3) 48 | 49 | const first = queue.dequeue() 50 | expect(first).toEqual(1) 51 | expect(queue.length).toEqual(2) 52 | 53 | const second = queue.dequeue() 54 | expect(second).toEqual(2) 55 | expect(queue.length).toEqual(1) 56 | 57 | const third = queue.dequeue() 58 | expect(third).toEqual(3) 59 | expect(queue.length).toEqual(0) 60 | 61 | const fourth = queue.dequeue() 62 | expect(fourth).toEqual(undefined) 63 | }) 64 | 65 | test('peek', () => { 66 | queue.enqueue(1) 67 | queue.enqueue(2) 68 | 69 | expect(queue.peek()).toEqual(1) 70 | 71 | queue.dequeue() 72 | expect(queue.peek()).toEqual(2) 73 | 74 | queue.dequeue() 75 | expect(queue.peek()).toEqual(undefined) 76 | }) 77 | }) 78 | -------------------------------------------------------------------------------- /queues/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "intro-to-data-structures-and-algorithms", 3 | "version": "1.0.0", 4 | "description": "An egghead course to introduce data structures and algorithms in JavaScript", 5 | "main": "queues.js", 6 | "scripts": { 7 | "test": "jest", 8 | "test:watch": "jest --watch" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms.git" 13 | }, 14 | "author": "Kyle Shevlin (http://kyleshevlin.com/)", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms/issues" 18 | }, 19 | "homepage": "https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms#readme", 20 | "devDependencies": { 21 | "jest": "^23.6.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /queues/queues.js: -------------------------------------------------------------------------------- 1 | function createQueue() { 2 | const queue = [] 3 | 4 | return { 5 | enqueue(x) { 6 | queue.unshift(x) 7 | }, 8 | dequeue() { 9 | if (queue.length === 0) { 10 | return undefined 11 | } 12 | return queue.pop() 13 | }, 14 | peek() { 15 | if (queue.length === 0) { 16 | return undefined 17 | } 18 | return queue[queue.length - 1] 19 | }, 20 | get length() { 21 | return queue.length 22 | }, 23 | isEmpty() { 24 | return queue.length === 0 25 | } 26 | } 27 | } 28 | 29 | 30 | const q = createQueue() 31 | 32 | q.enqueue('Make an egghead lesson') 33 | q.enqueue('Help others learn') 34 | q.enqueue('Be happy') 35 | 36 | q.dequeue() 37 | q.dequeue() 38 | console.log(q.peek()) 39 | 40 | exports.createQueue = createQueue 41 | -------------------------------------------------------------------------------- /queues/script.md: -------------------------------------------------------------------------------- 1 | # Queues 2 | 3 | To create our queue data structure, we're going to use a factory function that returns our queue as a plain JavaScript object. 4 | 5 | A queue is a collection of items that obey the principle of "first in, first out". When we place an item into the queue, we can only get it out after all items that have been added before it have been removed. This data structure is great for handling things that have to happen sequentially. 6 | 7 | Our queue will have several methods and properties: add or enqueue, remove or dequeue, peek to look at what's next to be removed, length, and isEmpty. 8 | 9 | We'll use an array held in closure to store our items. 10 | 11 | Let's create our enqueue method. We want to keep our collection in the correct order, so we always want to add to the array from one side of it and remove from the other. We'll add items to the front of our array for enqueue with the Array.unshift() method. 12 | 13 | We'll create our dequeue method using array.pop() to remove the final item from the array. This ensures we maintain order in our queue. Every good queue is orderly. 14 | 15 | We'll create our peek method by returning the item in the end position of our array. 16 | 17 | We'll then create a length property by using a getter on the array's length. If we don't use a getter, when our queue object is created, our queue's length will be set to the static value of 0, the queue's length, rather than stored as a reference to retrieve the current queue's length. 18 | 19 | Lastly, we'll add the isEmpty() method to quickly tell if a queue has any items or not. 20 | 21 | Now we can try our queue out. A great use for a queue is a plan. A plan is a collection of steps that need to happen in a particular order. Let's use a queue to make a plan. 22 | 23 | We'll create a queue. Just for good measure, let's test out our isEmpty method right away. It works. Now, let's add a few items to our plan. "Make egghead lesson," "Help others learn," and "Be happy." 24 | 25 | Now if we peek into our queue, we should see "Make egghead lesson". Yep, that works. 26 | 27 | Now, I've made the lesson, you're watching it, so let's dequeue it. If we check the length, we see we have 2 items in our queue. Let's peek again. We're near the end of the lesson, so I think I've helped you learn, so we can dequeue that. We'll take a final peek, it says "be happy", and we've learned queues so we're happy. Let's dequeue that. Check that our queue isEmpty, it is! 28 | -------------------------------------------------------------------------------- /quickSort/index.js: -------------------------------------------------------------------------------- 1 | const { printArray } = require('../utils') 2 | 3 | function quickSort(array) { 4 | printArray(array) 5 | 6 | if (array.length < 2) { 7 | return array 8 | } 9 | 10 | const pivotIndex = array.length - 1 11 | const pivot = array[pivotIndex] 12 | const left = [] 13 | const right = [] 14 | 15 | for (let i = 0; i < pivotIndex; i++) { 16 | const item = array[i] 17 | item < pivot ? left.push(item) : right.push(item) 18 | } 19 | 20 | return [...quickSort(left), pivot, ...quickSort(right)] 21 | } 22 | 23 | let numbers = [10, 5, 6, 3, 2, 8, 9, 4, 7, 1] 24 | 25 | quickSort(numbers) 26 | 27 | exports.quickSort = quickSort 28 | -------------------------------------------------------------------------------- /quickSort/index.test.js: -------------------------------------------------------------------------------- 1 | const { quickSort } = require('./index') 2 | 3 | test('Quick Sort', () => { 4 | const nums = [10, 8, 4, 3, 6, 2, 1, 9, 7, 5] 5 | expect(quickSort(nums)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) 6 | }) 7 | -------------------------------------------------------------------------------- /quickSort/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "intro-to-data-structures-and-algorithms", 3 | "version": "1.0.0", 4 | "description": "An egghead course to introduce data structures and algorithms in JavaScript", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest", 8 | "test:watch": "jest --watch" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms.git" 13 | }, 14 | "author": "Kyle Shevlin (http://kyleshevlin.com/)", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms/issues" 18 | }, 19 | "homepage": "https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms#readme", 20 | "devDependencies": { 21 | "jest": "^23.6.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /quickSort/script.md: -------------------------------------------------------------------------------- 1 | # Quick Sort 2 | 3 | Quick Sort is a recursive sorting algorithm. We take our original array, breaking it down into smaller arrays to sort. In particular, quick sort uses the concept of a "pivot". We pick one item, it could be at the head or the tail of the array, so long as it's consistent. We then compare each item to that pivot, if it's less than the pivot, we push it to a left array, if it's more we push it to the right array. We then return the array that is the quick sort called on the left, the pivot, and quick sort called on the right. 4 | 5 | We'll make a function to create our algorithm. Our function takes an array as an argument and will return an array. In this case, we'll return a new array of sorted items, so for now we'll return an empty array. 6 | 7 | Next, since we know we will call quickSort recursively, we need to establish our base case to prevent a stack overflow. If the array length is less than two, we want to return that array. 8 | 9 | Now, we want to establish our pivot, we'll use the last item in the array as our pivot. Since I'll need the pivot index later in the algorithm, I'm going to store that as a variable and derive the pivot from that. 10 | 11 | Now, I'll create empty arrays for what will be our left and right arrays. 12 | 13 | Now, we want to loop through every item in the array up to the pivot, hence why we stored the pivotIndex. We'll store the current item in a variable. Now, if the currentItem is less than the pivot, push it into the left array, otherwise into the right array. 14 | 15 | When our loop is done, we now want to call quick sort recursively on our left and right arrays, placing our pivot in the middle. 16 | -------------------------------------------------------------------------------- /stacks/index.js: -------------------------------------------------------------------------------- 1 | function createStack() { 2 | const stack = [] 3 | 4 | return { 5 | push(x) { 6 | stack.push(x) 7 | }, 8 | pop() { 9 | if (stack.length === 0) { 10 | return undefined 11 | } 12 | return stack.pop() 13 | }, 14 | peek() { 15 | if (stack.length === 0) { 16 | return undefined 17 | } 18 | return stack[stack.length - 1] 19 | }, 20 | get length() { 21 | return stack.length 22 | }, 23 | isEmpty() { 24 | return stack.length === 0 25 | } 26 | } 27 | } 28 | 29 | const lowerBodyStack = createStack() 30 | 31 | lowerBodyStack.push('underwear') 32 | lowerBodyStack.push('socks') 33 | lowerBodyStack.push('pants') 34 | lowerBodyStack.push('shoes') 35 | 36 | lowerBodyStack.pop() 37 | lowerBodyStack.pop() 38 | console.log(lowerBodyStack.peek()) 39 | console.log(lowerBodyStack.length) 40 | 41 | exports.createStack = createStack 42 | -------------------------------------------------------------------------------- /stacks/index.test.js: -------------------------------------------------------------------------------- 1 | const { createStack } = require('./index') 2 | 3 | describe('Stacks', () => { 4 | let stack 5 | beforeEach(() => { 6 | stack = createStack() 7 | }) 8 | 9 | test('length', () => { 10 | expect(stack.length).toEqual(0) 11 | 12 | stack.push(1) 13 | expect(stack.length).toEqual(1) 14 | 15 | stack.push(2) 16 | expect(stack.length).toEqual(2) 17 | 18 | stack.push(3) 19 | expect(stack.length).toEqual(3) 20 | }) 21 | 22 | test('isEmpty', () => {}) 23 | 24 | test('push', () => { 25 | expect(stack.length).toEqual(0) 26 | 27 | const value = 'foo' 28 | stack.push(value) 29 | 30 | expect(stack.length).toEqual(1) 31 | expect(stack.peek()).toEqual(value) 32 | }) 33 | 34 | test('pop', () => { 35 | stack.push(1) 36 | stack.push(2) 37 | stack.push(3) 38 | 39 | expect(stack.length).toEqual(3) 40 | 41 | const first = stack.pop() 42 | expect(first).toEqual(3) 43 | expect(stack.length).toEqual(2) 44 | 45 | const second = stack.pop() 46 | expect(second).toEqual(2) 47 | expect(stack.length).toEqual(1) 48 | 49 | const third = stack.pop() 50 | expect(third).toEqual(1) 51 | expect(stack.length).toEqual(0) 52 | 53 | const fourth = stack.pop() 54 | expect(fourth).toEqual(undefined) 55 | }) 56 | 57 | test('peek', () => { 58 | stack.push(1) 59 | stack.push(2) 60 | 61 | expect(stack.peek()).toEqual(2) 62 | 63 | stack.pop() 64 | expect(stack.peek()).toEqual(1) 65 | 66 | stack.pop() 67 | expect(stack.peek()).toEqual(undefined) 68 | }) 69 | }) 70 | -------------------------------------------------------------------------------- /stacks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "intro-to-data-structures-and-algorithms", 3 | "version": "1.0.0", 4 | "description": "An egghead course to introduce data structures and algorithms in JavaScript", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest", 8 | "test:watch": "jest --watch" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms.git" 13 | }, 14 | "author": "Kyle Shevlin (http://kyleshevlin.com/)", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms/issues" 18 | }, 19 | "homepage": "https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms#readme", 20 | "devDependencies": { 21 | "jest": "^23.6.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /stacks/script.md: -------------------------------------------------------------------------------- 1 | # Stacks 2 | 3 | To create our stack data structure, we're going to use a factory function that returns our stack as a plain JavaScript object. 4 | 5 | A stack is a collection of items that obey the principle of "last in, first out". When we place a new item onto the stack, we have to remove it first before we get any other item in the stack. This data structure is used for handling things like nested function calls in JavaScript, hence why it's called the call stack. 6 | 7 | Our stack will have several methods and properties: push, pop, peek to look at what's next to be removed, length, and isEmpty. 8 | 9 | We'll use an array held in closure to store our items. 10 | 11 | Let's create our push method. We want to keep our collection in the correct order, so we always want to add and remove from the same side of the array. We'll use the end of our array to make this happen and thus, we'll use the array methods that match our stack methods, push and pop to do this. Using push, we place new items at the end of the array. 12 | 13 | We'll create our pop method using array.pop() to remove the final item from the array. This ensures we maintain order in our stack. 14 | 15 | We'll create our peek method by returning the item in the end position of our array. 16 | 17 | We'll then create a length property by using a getter on the array's length. If we don't use a getter, when our stack object is created, our stack's length will be set to the static value of 0, the array's length at instantiation, rather than stored as a reference to retrieve the current stack's length. 18 | 19 | Lastly, we'll add the isEmpty() method to quickly tell if a stack has any items or not. 20 | 21 | Now we can try our stack out. A strange, but everyday example, of a stack is the act of getting dressed. We have to put clothing on in a particular order, and in order to change what we wear, we typically have to remove them in the reverse order. 22 | 23 | Let's create a lowerBodyClothingStack. We could create another stack for our upper body, but we'll save that for another time. Don't know about you, but first thing I put on when getting dressed is some underwear, followed by socks, then I throw on my pants, followed by my shoes. 24 | 25 | If we peek at the stack, the first thing I'll need to remove is my shoes. Let's say it's the end of the day, time to get ready for bed. I pop my shoes off, what's next? My pants. Great. There's more disrobing to do, but I'll save you the details. Looks like our stack is working as intended. 26 | -------------------------------------------------------------------------------- /stacks/yakstack.js: -------------------------------------------------------------------------------- 1 | // Yak Shaving Stack 2 | 3 | const { createStack } = require('./index') 4 | 5 | const yakStack = createStack() 6 | 7 | yakStack.push('small bug') 8 | yakStack.push('fix models') 9 | yakStack.push( 10 | 'fork framework, fix, make pull request' 11 | ) 12 | yakStack.push( 13 | 'fundamental problem in your programming language' 14 | ) 15 | yakStack.pop() 16 | yakStack.pop() 17 | yakStack.pop() 18 | 19 | console.log(yakStack.peek()) 20 | -------------------------------------------------------------------------------- /trees/index.js: -------------------------------------------------------------------------------- 1 | function createNode(key) { 2 | const children = [] 3 | 4 | return { 5 | key, 6 | children, 7 | addChild(childKey) { 8 | const childNode = createNode(childKey) 9 | children.push(childNode) 10 | return childNode 11 | } 12 | } 13 | } 14 | 15 | function createTree(rootKey) { 16 | const root = createNode(rootKey) 17 | 18 | return { 19 | root, 20 | print() { 21 | let result = '' 22 | 23 | function traverse(node, visitFn, depth) { 24 | visitFn(node, depth) 25 | 26 | if (node.children.length) { 27 | node.children.forEach(n => traverse(n, visitFn, depth + 1)) 28 | } 29 | } 30 | 31 | function addKeyToResult(node, depth) { 32 | result += 33 | result.length === 0 34 | ? node.key 35 | : `\n${' '.repeat(depth * 2)}${node.key}` 36 | } 37 | 38 | traverse(root, addKeyToResult, 0) 39 | 40 | return result 41 | } 42 | } 43 | } 44 | 45 | const dom = createTree('html') 46 | const head = dom.root.addChild('head') 47 | const body = dom.root.addChild('body') 48 | const title = head.addChild('title - egghead Tree Lesson') 49 | const header = body.addChild('header') 50 | const main = body.addChild('main') 51 | const footer = body.addChild('footer') 52 | const h1 = header.addChild('h1 - Tree Lesson') 53 | const p = main.addChild('p - Learn about trees!') 54 | const copyright = footer.addChild(`Copyright ${new Date().getFullYear()}`) 55 | 56 | console.log(dom.print()) 57 | 58 | exports.createNode = createNode 59 | exports.createTree = createTree 60 | -------------------------------------------------------------------------------- /trees/index.test.js: -------------------------------------------------------------------------------- 1 | const { createTree, createNode } = require('./index') 2 | 3 | describe('Node', () => { 4 | let node 5 | beforeEach(() => { 6 | node = createNode('a') 7 | }) 8 | 9 | test('existence', () => { 10 | expect(node).toBeDefined() 11 | }) 12 | 13 | test('key', () => { 14 | expect(node.key).toEqual('a') 15 | }) 16 | 17 | test('children', () => { 18 | expect(node.children).toBeDefined() 19 | expect(node.children.length).toEqual(0) 20 | }) 21 | 22 | test('addChild', () => { 23 | node.addChild('b') 24 | 25 | expect(node.children.length).toEqual(1) 26 | expect(node.children.map(n => n.key).includes('b')).toBe(true) 27 | }) 28 | }) 29 | 30 | describe('Tree', () => { 31 | let tree 32 | beforeEach(() => { 33 | tree = createTree('a') 34 | }) 35 | 36 | test('existence', () => { 37 | expect(tree).toBeDefined() 38 | }) 39 | 40 | test('root', () => { 41 | expect(tree.root).toBeDefined() 42 | expect(tree.root.key).toEqual('a') 43 | }) 44 | 45 | test('print', () => { 46 | const b = tree.root.addChild('b') 47 | const c = tree.root.addChild('c') 48 | const d = b.addChild('d') 49 | const e = b.addChild('e') 50 | const f = c.addChild('f') 51 | const g = c.addChild('g') 52 | const h = f.addChild('h') 53 | 54 | expect(tree.print()).toEqual( 55 | ` 56 | a 57 | b 58 | d 59 | e 60 | c 61 | f 62 | h 63 | g 64 | `.trim() 65 | ) 66 | }) 67 | }) 68 | -------------------------------------------------------------------------------- /trees/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "intro-to-data-structures-and-algorithms", 3 | "version": "1.0.0", 4 | "description": "An egghead course to introduce data structures and algorithms in JavaScript", 5 | "main": "tree.js", 6 | "scripts": { 7 | "test": "jest", 8 | "test:watch": "jest --watch" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms.git" 13 | }, 14 | "author": "Kyle Shevlin (http://kyleshevlin.com/)", 15 | "license": "MIT", 16 | "bugs": { 17 | "url": "https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms/issues" 18 | }, 19 | "homepage": "https://github.com/kyleshevlin/intro-to-data-structures-and-algorithms#readme", 20 | "devDependencies": { 21 | "jest": "^23.6.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /trees/script.md: -------------------------------------------------------------------------------- 1 | # Trees 2 | 3 | A tree is a graph without any cycles. A cycle is when three or more nodes are connected in a circuitous path. 4 | 5 | Tree nodes, instead of having neighbors and no hierarchy, might be thought of having children and are hierarchical. Each node can contain many children, no children may have an edge between them and they may not be connected to any other parent node. 6 | 7 | To write our tree data structure, we'll start by creating a function to make our nodes. 8 | 9 | Each node receives a key as an argument. We'll return that key in the object returned by the factory. Next, a tree node can have many children, so we'll create an array and return that reference, lastly, we'll add a method called addChild so that we can add children to this node. Add child will receive a key as an argument, create the node, push it to children and return it. 10 | 11 | Now we can create our tree. Trees must have a root node, so we'll expect the rootKey to be passed as an argument when the tree is created. We'll use this key to create a node we'll assign to root and return in our tree object. 12 | 13 | This tiny object is all we need to begin to use our tree, since each node contains in it the ability to addNodes to it. 14 | 15 | Perhaps the most common tree structure web developers deal with on a regular basis is the DOM tree. The HTML of every page on the web is a tree structure. We can create the basic layout of a page quickly with our tree. 16 | 17 | The root of our tree is the 'html' element. The html element has two children, head and body. the head will have a child, title. The body may have many children, we'll give it a header, a main and a footer. Inside the header, we can give it an h1, our main can receive a paragraph of text, and our footer may have a copyright. 18 | 19 | We have our tree structure, it might be nice to visualize it. Let's create a print method that traverses our graph and logs out the keys. 20 | 21 | I want to return a string from our print method, and I will be concatenating on to this string with each level we traverse through our tree. 22 | 23 | I'm going to create a traverse function that we'll call recursively on our nodes. Traverse will accept three arguments, the node it's operating on, a visitFn to fire on that node, and a depth argument I'm going to use for my output. Depth isn't necessary in this implementation and could be derived from the data, but this will be useful for the lesson. 24 | 25 | Now, we start by visiting our current node. Then, if the node has any children, we want to traverse each one of those, passing the same visitFn, but increasing the depth by 1. 26 | 27 | Now I'll create a function to pass as our visiting function. This function will mutate our result string. For the very first node key, I only want to return the key, for every subsequent key, I want to create a new line and add twice as many spaces as the depth before the node key. This will give a nice nested layout to our output. 28 | 29 | Next, I want to traverse, starting from the root, passing our visiting function and a depth of 1. 30 | 31 | Lastly, we return our result. We print it into the terminal, and we can see the output of our DOM tree. 32 | -------------------------------------------------------------------------------- /utils/index.js: -------------------------------------------------------------------------------- 1 | function printArray(array) { 2 | console.log(array.join(' ')) 3 | } 4 | 5 | exports.printArray = printArray 6 | --------------------------------------------------------------------------------