diff --git a/package.json b/package.json index 60e89dc..3c19e50 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,8 @@ "description": "LambdaSchool's Data Structures II", "main": "index.js", "scripts": { + "test:nolint": "jest --verbose", + "mytest": "npm run test:nolint -- --watch", "test": "eslint tests/*.js && eslint src/*.js && jest --verbose", "lint": "eslint", "test:watch": "npm test -- --watch" diff --git a/src/binary-search-tree.js b/src/binary-search-tree.js index 262a838..919be1e 100644 --- a/src/binary-search-tree.js +++ b/src/binary-search-tree.js @@ -2,6 +2,8 @@ /* eslint-disable global-require */ /* eslint-disable no-unused-vars */ /* eslint-disable no-trailing-spaces */ +const Queue = require('./queue-helper'); + class BinarySearchTree { constructor(value) { this.value = value; @@ -12,26 +14,70 @@ class BinarySearchTree { // assigns it to either the left or right subtree, // depending on its value insert(value) { - + if (this.value === null) { + this.value = new BinarySearchTree(value); + return; + } + if (value > this.value) { + if (!this.right) { + this.right = new BinarySearchTree(value); + return; + } + return this.right.insert(value); + } + if (value <= this.value) { + if (!this.left) { + this.left = new BinarySearchTree(value); + return; + } + return this.left.insert(value); + } } + // Checks the binary search tree for the input target // Can be written recursively or iteratively contains(target) { - + if (this.value === target) return true; + if (target > this.value && this.right !== null) { + return this.right.contains(target); + } + if (target <= this.value && this.left !== null) { + return this.left.contains(target); + } + return false; } // Traverses the tree in a depth-first manner, i.e. from top to bottom // Applies the given callback to each tree node in the process depthFirstForEach(cb) { - + cb(this.value); + if (this.left) { + this.left.depthFirstForEach(cb); + } + if (this.right) { + this.right.depthFirstForEach(cb); + } } - // Traverses the tree in a breadth-first manner, i.e. in layers, starting + // Traverses the tree in a breadth-first manner, i.e. in layers, starting // at the root node, going down to the root node's children, and iterating // through all those nodes first before moving on to the next layer of nodes // Applies the given callback to each tree node in the process // You'll need the queue-helper file for this. Or could you roll your own queue // again. Whatever floats your boat. breadthFirstForEach(cb) { - + const q = new Queue(); + if (this.value === null) { return; } + q.enqueue(this); + let currentNode = this; + while (!q.isEmpty()) { + currentNode = q.dequeue(); + if (currentNode.left) { + q.enqueue(currentNode.left); + } + if (currentNode.right) { + q.enqueue(currentNode.right); + } + cb(currentNode.value); + } } } diff --git a/src/graph.js b/src/graph.js index 80eeb07..37595d1 100644 --- a/src/graph.js +++ b/src/graph.js @@ -35,45 +35,104 @@ class Graph { this.vertices = []; } // Wraps the input value in a new GraphNode and adds it to the array of vertices - // If there are only two nodes in the graph, they need to be automatically + // If there are only two nodes in the graph, they need to be automatically // connected via an edge // Optionally accepts an array of other GraphNodes for the new vertex to be connected to // Returns the newly-added vertex addVertex(value, edges = []) { - + const ngn = new GraphNode({ value, edges }); + for (let i = 0; i < edges.length; i++) { + const edgeIndex = this.vertices.indexOf(edges[i]); + this.vertices[edgeIndex].pushToEdges(ngn); + } + this.vertices.push(ngn); + if (this.vertices.length === 2) { + this.vertices[0].pushToEdges(ngn); + this.vertices[1].pushToEdges(this.vertices[0]); + } + return ngn; } + // Checks all the vertices of the graph for the target value // Returns true or false contains(value) { - + if (this.vertices.length === 0) return false; + for (let i = 0; i < this.vertices.length; i++) { + if (this.vertices[i].value === value) { + return true; + } + return false; + } } - // Checks the graph to see if a GraphNode with the specified value exists in the graph + // Checks the graph to see if a GraphNode with the specified value exists in the graph // and removes the vertex if it is found // This function should also handle the removing of all edge references for the removed vertex - removeVertex(value) { + // CRC: removeVertex with reduce. + // this.vertices = this.vertices.reduce( (acc, vertex) ) => { + // if (vertex.value === value) { + // vertex.edges.forEach( (edge) => { + // this.removeEdge(item, edge); + // }) + // return acc; + // } + // acc.push(vertex); + // return acc; + // } + removeVertex(value) { + let removed; + if (!this.contains(value)) return; + if (this.contains(value)) { + for (let i = 0; i < this.vertices.length; i++) { + if (this.vertices[i].value === value) { + removed = this.vertices[i]; + const newVertices = this.vertices.filter(vert => vert.value !== value); + this.vertices = newVertices; + } + } + if (removed.edges.length > 0) { + removed.edges.forEach((edge) => { + this.removeEdge(removed, edge); + }); + } + } } + // Checks the two input vertices to see if each one references the other in their respective edges array // Both vertices must reference each other for the edge to be considered valid // If only one vertex references the other but not vice versa, should not return true - // Note: You'll need to store references to each vertex's array of edges so that you can use + // Note: You'll need to store references to each vertex's array of edges so that you can use // array methods on said arrays. There is no method to traverse the edge arrays built into the GraphNode class + // CRC: Can also use includes method on array checkIfEdgeExists(fromVertex, toVertex) { - + this.contains(fromVertex); // + if (fromVertex.edges.indexOf(toVertex) === -1 || toVertex.edges.indexOf(fromVertex) === -1) { + return false; + } + return true; } // Adds an edge between the two given vertices if no edge already exists between them - // Again, an edge means both vertices reference the other + // Again, an edge means both vertices reference the other addEdge(fromVertex, toVertex) { - + if (this.checkIfEdgeExists(fromVertex, toVertex)) return; + fromVertex.pushToEdges(toVertex); + toVertex.pushToEdges(fromVertex); } // Removes the edge between the two given vertices if an edge already exists between them // After removing the edge, neither vertex should be referencing the other // If a vertex would be left without any edges as a result of calling this function, those // vertices should be removed as well + // CRC: Solution for getting rid of edge-less vertices using reduce + // this.vertices = this.vertices.reduce( (acc, vertex) => { + // if (vertex.edges.length !== 0) acc.push(vertex); + // return acc; + // }, []) removeEdge(fromVertex, toVertex) { - + fromVertex.edges = fromVertex.edges.filter(edge => edge.value !== toVertex.value); + toVertex.edges = toVertex.edges.filter(edge => edge.value !== fromVertex.value); + if (fromVertex.numberOfEdges === 0) this.removeVertex(fromVertex.value); + if (toVertex.numberOfEdges === 0) this.removeVertex(toVertex.value); } } module.exports = Graph; - diff --git a/src/heap.js b/src/heap.js index b317fe1..d6e4c20 100644 --- a/src/heap.js +++ b/src/heap.js @@ -7,38 +7,75 @@ class Heap { // Inserts the given value in the heap // Calls bubbleUp in order to put the newly-inserted element in the right place in the heap insert(val) { - + this.storage.push(val); + this.size++; + let index = this.storage.length - 1; + this.bubbleUp(index); } // Deletes the element located at the front of the heap (the max if a max heap, or a min if a min heap) // Calls siftDown in order to reorganize the heap with a new max/min // In some specifications, this method is also called `poll` delete() { - + const deleted = this.getMax(); + this.storage = [null].concat(this.storage.slice(2)); + // this.storage = this.storage.splice(1,1); Why doesn't this work!? + this.size--; + let index = 1; + while (index <= this.size) { + this.siftDown(index); + index++; + } + return deleted; } // Returns the maximum value in the heap in constant time getMax() { + return this.storage[1]; } // Returns the size of the heap getSize() { - + return this.size; } // Returns the storage array getStorage() { - + return this.storage; } // Moves the element at the specified index "up" by swapping it with its parent // if its parent value is less than the value located at the input index // This method is only used by the heap itself in order to maintain the heap property bubbleUp(index) { - + while (this.storage[index] > this.storage[Math.ceil(index/2)]) { + if (index > 1) { + [this.storage[Math.ceil(index/2)], this.storage[index]] = [this.storage[index], this.storage[Math.ceil(index/2)]]; + if (Math.ceil(index/2) > 1) { + index = Math.ceil(index/2) + } else { break; } + } + } } // First grabs the indices of this element's children and determines which of the children are larger // If the larger of the child elements is larger than the parent, the child element is swapped with the parent // This method is only used by the heap itself in order to maintain the heap property siftDown(index) { - + let child1 = this.storage[Math.ceil(index*2)]; + let c1index = Math.ceil(index*2); + let child2 = this.storage[Math.ceil(index*2 + 1)]; + let c2index = Math.ceil(index*2 + 1); + let greaterChild; + let gcIndex; + if (child1 >= child2 || child2 === undefined) { + greaterChild = child1; + gcIndex = c1index; + } else { + greaterChild = child2; + gcIndex = c2index; // not needed to pass test, but I assume we need? + } + if (greaterChild > this.storage[index]) { + [this.storage[gcIndex], this.storage[index]] = [this.storage[index], this.storage[gcIndex]]; + } } + } + module.exports = Heap; diff --git a/src/tree.js b/src/tree.js index b6b7908..265bf9b 100644 --- a/src/tree.js +++ b/src/tree.js @@ -5,15 +5,21 @@ class Tree { this.value = value; this.children = []; } - // Adds a new Tree node with the input value to the current Tree node + // Adds a new Tree node with the input value to the current Tree node addChild(value) { - + const newChild = new Tree(value); + this.children.push(newChild); } // Checks this node's children to see if any of them matches the given value // Continues recursively until the value has been found or all of the children // have been checked + // CC - This can also use forEach and call recursively. contains(value) { - + if (this.value === value) return true; + for (let i = 0; i < this.children.length; i++) { + if (this.children[i].contains(value)) return true; + } + return false; } } diff --git a/tests/heap.test.js b/tests/heap.test.js index 0ff6902..81f12d7 100644 --- a/tests/heap.test.js +++ b/tests/heap.test.js @@ -1,64 +1,64 @@ -// /* eslint-disable no-undef */ -// const Heap = require('../src/heap'); +/* eslint-disable no-undef */ +const Heap = require('../src/heap'); -// describe('Heap', () => { -// let heap; +describe('Heap', () => { + let heap; -// beforeEach(() => { -// heap = new Heap(); -// }); + beforeEach(() => { + heap = new Heap(); + }); -// it('should have methods named "insert", "delete", "getMax", "getSize", "bubbleUp", and "siftDown"', () => { -// expect(typeof heap.insert).toBe('function'); -// expect(typeof heap.delete).toBe('function'); -// expect(typeof heap.getMax).toBe('function'); -// expect(typeof heap.getSize).toBe('function'); -// expect(typeof heap.bubbleUp).toBe('function'); -// expect(typeof heap.siftDown).toBe('function'); -// }); + it('should have methods named "insert", "delete", "getMax", "getSize", "bubbleUp", and "siftDown"', () => { + expect(typeof heap.insert).toBe('function'); + expect(typeof heap.delete).toBe('function'); + expect(typeof heap.getMax).toBe('function'); + expect(typeof heap.getSize).toBe('function'); + expect(typeof heap.bubbleUp).toBe('function'); + expect(typeof heap.siftDown).toBe('function'); + }); -// it('should get the max value of the heap elements', () => { -// heap.insert(6); -// heap.insert(8); -// heap.insert(10); -// heap.insert(9); -// heap.insert(1); -// heap.insert(9); -// heap.insert(9); -// heap.insert(5); -// expect(heap.getMax()).toEqual(10); -// }); + it('should get the max value of the heap elements', () => { + heap.insert(6); + heap.insert(8); + heap.insert(10); + heap.insert(9); + heap.insert(1); + heap.insert(9); + heap.insert(9); + heap.insert(5); + expect(heap.getMax()).toEqual(10); + }); -// it('should properly get the new max after the old max is deleted', () => { -// heap.insert(6); -// heap.insert(8); -// heap.insert(10); -// heap.insert(9); -// heap.insert(1); -// heap.insert(9); -// heap.insert(9); -// heap.insert(5); + it('should properly get the new max after the old max is deleted', () => { + heap.insert(6); + heap.insert(8); + heap.insert(10); + heap.insert(9); + heap.insert(1); + heap.insert(9); + heap.insert(9); + heap.insert(5); -// heap.delete(); -// expect(heap.getMax()).toEqual(9); -// }); + heap.delete(); + expect(heap.getMax()).toEqual(9); + }); -// it('should delete the elements from greatest to least', () => { -// heap.insert(6); -// heap.insert(7); -// heap.insert(5); -// heap.insert(8); -// heap.insert(10); -// heap.insert(1); -// heap.insert(2); -// heap.insert(5); + it('should delete the elements from greatest to least', () => { + heap.insert(6); + heap.insert(7); + heap.insert(5); + heap.insert(8); + heap.insert(10); + heap.insert(1); + heap.insert(2); + heap.insert(5); -// const descendingOrder = []; -// while (heap.getSize() > 0) { -// descendingOrder.push(heap.delete()); -// } + const descendingOrder = []; + while (heap.getSize() > 0) { + descendingOrder.push(heap.delete()); + } -// expect(descendingOrder).toEqual([10, 8, 7, 6, 5, 5, 2, 1]); -// }); -// }); + expect(descendingOrder).toEqual([10, 8, 7, 6, 5, 5, 2, 1]); + }); +});