diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..565fa8c --- /dev/null +++ b/.travis.yml @@ -0,0 +1,6 @@ +language: node_js +node_js: + - "9" + +script: + - npm test \ No newline at end of file diff --git a/README.md b/README.md index 6596982..3f2d5ba 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # data-structures-and-algorithms +* [travis](https://www.travis-ci.com/MeStock/data-structures-and-algorithms) +

Overview

This repo is a place to organize completed code challenges. Within the code challenges folder, you can find examples of different data structure and algorithm problems with their optimzed solutions.

@@ -159,3 +161,87 @@ Add in some conditionals for edge cases: ## Solution ![whiteboard fifo animal shelter](/assets/fifo-animal-shelter.jpg) + +# Trees +Trees are a type of data structure made up of `nodes`. A binary tree start with a root node that has a pointer for a left child and right child node. Each child node also has a set of pointers toward a left child and right child node. A binary search tree is a tree that adds nodes to the left of the parent node its value is less than the parent - or to the right of the parent node if its value is greater than the parent. + +## Challenge +Create a Binary Tree class with preorder, inorder, and post order methods. +Create a Binary Search Tree class with add and contain methods. + +## Approach & Efficiency +Binary Tree: Traversing the tree will have a O(n) in time, where n is the number of nodes in the tree and O(1) in space. Similarly, inserting into the tree will have O(n) in time. + +BinarySearch Tree: Travering the tree will have an O(n) in time, where n is the number of nodes in the tree and O(1) in space. However inserting into the tree will have O(h) in time, where h is the height of the tree. + +## API +Binary Trees: +preOrder(): returns an array of values stored in the tree in the order: root, left right. +inOrder(): returns an array of values stored in the tree in the order: left, root, right. +postOrder(): returns an array of values stored in the tree in the order: left, right, root. + +Binary Search Trees: +add(value): traverses through the tree - going right if the new value is less than the current nodes value or going left if the new value is greater than the current nodes value - until a leaf is found. Then inserts the new value either to the right or left of the leaf node depending on its value. + +contains(value): traverses through the tree and returns true if the tree contains the value. returns false if tree is empty, an invalid value is given, or the value was not found. + +# Fizzbuzz Tree +Create a function called FizzbuzzTree that takes in the root of a tree. It traverses the tree and replaces values divisible by 3 with the string 'fizz', values divisible by 5 are replaced with 'buzz', and values divisible by 3 and 5 are replaced with 'fizzbuzz'. + +## Challenge +* traverse tree, checking the type of the value within each node. If the value is a number - check the conditions listed above. Modify tree in place. + +## Approach & Efficiency +To modify the tree in place, I used recursion to traverse the tree. +Time: O(n) - where n is the number of nodes in the tree +Space: O(1) - Modify the tree in place - however, O(n) where n is the call stack since this function uses recursion. + +Add in some conditionals for edge cases: +* Do not traverse is the tree is empty +* Do not check if values are divisible if they are not numbers + +## Solution +![whiteboard fizzbuzz tree](/assets/fizzbuzzTree.jpeg) + +# Find the depth of a value within a Binary Tree +Create a function called depth of value that takes in the root of a tree and a value. It will search the tree for a value and if found return the depth at which it was found. + +## Challenge +* traverse tree, checking values of each node and tracking the depth. Using recursion to keep track of the depth going down the tree and passing the depth back through the call stack when the value is found. + +## Approach & Efficiency +Time: O(n) - where n is the number of nodes within the tree. +Space: O(H) - where H is the height of the tree. + +Add in some conditionals for edge cases: +* Do not traverse is the tree is empty +* Return null if no value is found + +## Solution +![whiteboard depth of value](/assets/depthOfValue.jpg) + +# Reverse an Array +The reverse array function will take in an array as a parameter and return the elements of the array in reverser order. + +## Challenge +Modify array in place + +## Approach & Efficiency +I knew that I needed some sort of looping mechanism, so I started by visualizing the input and output of the array.

+ I: [1,2,3,4,5]
+ O: [5,4,3,2,1]

+Keeping track of the front index and back index and making swaps was the optimal solution. + +LOOP 1: +[5,2,3,4,1] +

+LOOP 2: +[5,4,3,2,1] +

+LOOP 3: reached the middle of the array so return the current state of the array. + +The time complexity for this is O(n) where n is half the length of the list and space complexity is O(1) because we are modifying in place. +You can also solve this problem using built in array methods (reverse, push, pop) or recursively but the space/time complexities are not as efficient. + +## Solution +![whiteboard array reverse](/assets/more-array-reverse.jpg) diff --git a/__tests__/append-insert-before-after.test.js b/__tests__/append-insert-before-after.test.js index b4b355d..d28ae1c 100644 --- a/__tests__/append-insert-before-after.test.js +++ b/__tests__/append-insert-before-after.test.js @@ -10,35 +10,35 @@ describe('Linked list', () => { const test = new LinkedList(); expect(test.append('test')).toEqual(expect.objectContaining(test)); }); - xit('should properly insert before the first node of the list', () => { - const test = new LinkedList(); - test.append('head'); - test.append('first node'); - test.append('second node'); - test.insertBefore('head','inserted before node'); - expect(test.value).toBe('inserted before node'); - }); - xit('should properly insert before i in the middle of the list', () => { - const test = new LinkedList(); - test.append('head'); - test.append('first node'); - test.append('second node'); - test.insertBefore('first node','inserted node'); - expect(test.next.value).toEqual('inserted node'); - }); - xit('should properly insert after the last node of the list', () => { - const test = new LinkedList(); - test.append('head'); - test.append('first node'); - test.insertAfter('first node', 'inserted node'); - expect(test.next.next.value).toEqual('inserted node'); - }); - xit('should properly insert in the middle of the list', () => { - const test = new LinkedList(); - test.append('head'); - test.append('first node'); - test.append('second node'); - test.insertAfter('first node', 'inserted node'); - expect(test.next.next.value).toEqual('inserted node'); - }); -}); \ No newline at end of file + // xit('should properly insert before the first node of the list', () => { + // const test = new LinkedList(); + // test.append('head'); + // test.append('first node'); + // test.append('second node'); + // test.insertBefore('head','inserted before node'); + // expect(test.value).toBe('inserted before node'); + // }); + // xit('should properly insert before i in the middle of the list', () => { + // const test = new LinkedList(); + // test.append('head'); + // test.append('first node'); + // test.append('second node'); + // test.insertBefore('first node','inserted node'); + // expect(test.next.value).toEqual('inserted node'); + // }); + // xit('should properly insert after the last node of the list', () => { + // const test = new LinkedList(); + // test.append('head'); + // test.append('first node'); + // test.insertAfter('first node', 'inserted node'); + // expect(test.next.next.value).toEqual('inserted node'); + // }); + // xit('should properly insert in the middle of the list', () => { + // const test = new LinkedList(); + // test.append('head'); + // test.append('first node'); + // test.append('second node'); + // test.insertAfter('first node', 'inserted node'); + // expect(test.next.next.value).toEqual('inserted node'); + // }); +}); diff --git a/__tests__/array-reverse.test.js b/__tests__/array-reverse.test.js new file mode 100644 index 0000000..43f0393 --- /dev/null +++ b/__tests__/array-reverse.test.js @@ -0,0 +1,31 @@ +'use strict'; + +const arrayReverse1 = require('../code-challenges/array-reverse/array-reverse.js').reverseArray; +const arrayReverse2 = require('../code-challenges/array-reverse/array-reverse.js').stretchGoal; +const arrayReverse3 = require('../code-challenges/array-reverse/more-array-reverse.js'); +const reversedTest = ['d', 'c', 'b', 'a']; + + +describe('Array Reverse', () => { + it('should reverse an array with one element', () => { + const test = ['a']; + expect(arrayReverse1(test)).toEqual(['a']); + expect(arrayReverse2(test)).toEqual(['a']); + expect(arrayReverse3(test)).toEqual(['a']); + }); + + it('should reverse any array of any size', () => { + const test1 = ['a', 'b', 'c', 'd']; + const test2 = ['a', 'b', 'c', 'd']; + const test3 = ['a', 'b', 'c', 'd']; + expect(arrayReverse1(test1)).toEqual(reversedTest); + expect(arrayReverse2(test2)).toEqual(reversedTest); + expect(arrayReverse3(test3)).toEqual(reversedTest); + }); + + it('should return null if no array is given', () => { + expect(arrayReverse1()).toBeNull(); + expect(arrayReverse2()).toBeNull(); + expect(arrayReverse3()).toBeNull(); + }); +}); diff --git a/__tests__/depth-of-tree.test.js b/__tests__/depth-of-tree.test.js new file mode 100644 index 0000000..933d854 --- /dev/null +++ b/__tests__/depth-of-tree.test.js @@ -0,0 +1,38 @@ +'use strict'; + +const Node = require('../code-challenges/tree/node.js'); +const BinaryTree = require('../code-challenges/tree/tree.js'); +const depthOfValue = require('../code-challenges/depth-of-tree/depth-of-tree.js'); + + +describe('Depth of value in tree', () => { + it('should create new trees', () => { + const test = new BinaryTree(); + expect(test).toBeInstanceOf(BinaryTree); + }); + it('should successfully create empty trees', () => { + const test = new BinaryTree(); + expect(test.root.value).toBeUndefined(); + }); + it('should successfully return null if tree is empty', () => { + const test = new BinaryTree(); + expect(depthOfValue(test.root, 5)).toBeNull(); + }); + it('should successfully return null if the value was not found', () => { + const test = new BinaryTree(1); + test.root.left = new Node(4); + test.root.right = new Node(5); + expect(depthOfValue(test.root, 3)).toBeNull(); + }); + it('should return null if the inputs are invalide', () => { + const test = new BinaryTree(); + expect(depthOfValue(test)).toBeNull(); + expect(depthOfValue()).toBeNull(); + }) + it('should successfully return the depth of tree', () => { + const test = new BinaryTree(1); + test.root.left = new Node(7); + test.root.right = new Node(15); + expect(depthOfValue(test.root, 15)).toBe(1); + }); +}); diff --git a/__tests__/fizzbuzz-tree.test.js b/__tests__/fizzbuzz-tree.test.js new file mode 100644 index 0000000..51beef4 --- /dev/null +++ b/__tests__/fizzbuzz-tree.test.js @@ -0,0 +1,50 @@ +'use strict'; + +const Node = require('../code-challenges/tree/node.js'); +const BinaryTree = require('../code-challenges/tree/tree.js'); +const fizzbuzzTree = require('../code-challenges/fizzbuzz-tree/fizzbuzz-tree.js'); + + +describe('Fizzbuzz Tree', () => { + it('should create new trees', () => { + const test = new BinaryTree(); + expect(test).toBeInstanceOf(BinaryTree); + }); + it('should successfully create empty trees', () => { + const test = new BinaryTree(); + expect(test.root.value).toBeUndefined(); + }); + it('should successfully change numbers divisible by 3 to fizz', () => { + const test = new BinaryTree(1); + test.root.left = new Node(3); + test.root.right = new Node(4); + fizzbuzzTree(test.root); + expect(test.root.left.value).toBe('fizz'); + expect(test.root.right.value).toBe(4); + }); + it('should successfully change numbers divisible by 5 to buzz', () => { + const test = new BinaryTree(1); + test.root.left = new Node(4); + test.root.right = new Node(5); + fizzbuzzTree(test.root); + expect(test.root.left.value).toBe(4); + expect(test.root.right.value).toBe('buzz'); + }); + it('should successfully change numbers divisible by 5 and 3 to fizzbuzz', () => { + const test = new BinaryTree(1); + test.root.left = new Node(7); + test.root.right = new Node(15); + fizzbuzzTree(test.root); + expect(test.root.left.value).toBe(7); + expect(test.root.right.value).toBe('fizz buzz'); + }); + it('should not change anything that isn\'t divisible by 3 or 5', () => { + const test = new BinaryTree(1); + test.root.left = new Node(4); + test.root.right = new Node(7); + fizzbuzzTree(test.root); + expect(test.root.value).toBe(1); + expect(test.root.left.value).toBe(4); + expect(test.root.right.value).toBe(7); + }); +}); diff --git a/__tests__/tree.test.js b/__tests__/tree.test.js new file mode 100644 index 0000000..570001c --- /dev/null +++ b/__tests__/tree.test.js @@ -0,0 +1,89 @@ +'use strict'; + + +/* + +Can successfully instantiate an empty tree +Can successfully instantiate a tree with a single root node +Can successfully add a left child and right child to a single root node +Can successfully return a collection from a preorder traversal +Can successfully return a collection from an inorder traversal +Can successfully return a collection from a postorder traversal + +*/ + +const Node = require('../code-challenges/tree/node.js'); +const BinaryTree = require('../code-challenges/tree/tree.js'); +const BinarySearchTree = require('../code-challenges/tree/searchTree.js'); + +describe('Trees', () => { + + describe('Nodes', () => { + it('should create new nodes', () => { + expect(new Node()).toBeInstanceOf(Node); + }); + }); + + describe('Binary Trees', () => { + it('should create new trees', () => { + const test = new BinaryTree(); + expect(test).toBeInstanceOf(BinaryTree); + }); + it('should successfully create empty trees', () => { + const test = new BinaryTree(); + expect(test.root.value).toBeUndefined(); + }); + it('should successfully add a left child and right child to a root', () => { + const test = new BinaryTree('root'); + test.root.left = new Node('left'); + test.root.right = new Node('right'); + expect(test.root.left.value).toBe('left'); + expect(test.root.right.value).toBe('right'); + }); + it('should successfully return a collection of preordered values', () => { + const test = new BinaryTree('root'); + test.root.left = new Node('left'); + test.root.right = new Node('right'); + const result = test.preOrder(); + expect(result).toEqual(['root', 'left', 'right']); + }); + it('should successfully return a collection of inordered values', () => { + const test = new BinaryTree('root'); + test.root.left = new Node('left'); + test.root.right = new Node('right'); + const result = test.inOrder(); + expect(result).toEqual(['left', 'root', 'right']); + }); + it('should successfully return a collection of postordered values', () => { + const test = new BinaryTree('root'); + test.root.left = new Node('left'); + test.root.right = new Node('right'); + const result = test.postOrder(); + expect(result).toEqual(['left', 'right', 'root']); + }); + }); + + describe('Binary Search Trees', () => { + it('should create new search tree', () => { + const test = new BinarySearchTree(); + expect(test).toBeInstanceOf(BinarySearchTree); + }); + + it('should successfully add to the tree in the correct order', () => { + const test = new BinarySearchTree(100); + test.add(50); //first node to the left + test.add(10); //node to the left of 50 + test.add(60); //node to the right of 50 + test.add(200); //first node to the right + test.add(150); //node to the left of 200 + test.add(300); //node to the right of 200 + expect(test.root.left.value).toEqual(50); + expect(test.root.left.left.value).toEqual(10); + expect(test.root.left.right.value).toEqual(60); + expect(test.root.right.value).toEqual(200); + expect(test.root.right.left.value).toEqual(150); + expect(test.root.right.right.value).toEqual(300); + + }); + }); +}); diff --git a/assets/depthOfValue.jpg b/assets/depthOfValue.jpg new file mode 100644 index 0000000..75be681 Binary files /dev/null and b/assets/depthOfValue.jpg differ diff --git a/assets/fizzbuzzTree.jpeg b/assets/fizzbuzzTree.jpeg new file mode 100644 index 0000000..4b0fc64 Binary files /dev/null and b/assets/fizzbuzzTree.jpeg differ diff --git a/assets/more-array-reverse.jpg b/assets/more-array-reverse.jpg new file mode 100644 index 0000000..35046e0 Binary files /dev/null and b/assets/more-array-reverse.jpg differ diff --git a/code-challenges/array-reverse/array-reverse.js b/code-challenges/array-reverse/array-reverse.js index e42dc16..08b6f0d 100644 --- a/code-challenges/array-reverse/array-reverse.js +++ b/code-challenges/array-reverse/array-reverse.js @@ -1,6 +1,8 @@ // Write a function called reverseArray which takes an array as an argument. Without utilizing any of the built-in methods available to your language, return an array with elements in reversed order. function reverseArray(arr){ + if(!arr) return null; + if(arr.length === 1) return arr; let result = []; for(let i = arr.length - 1; i >= 0; i--){ result.push(arr[i]); @@ -8,12 +10,13 @@ function reverseArray(arr){ return result; } -console.log(reverseArray([1,2,3,4,5])); // STRETCH GOAL // Once you’ve achieved a working solution, implement the same feature with a different methodology. (Hint: what different techniques do you have when working with arrays? Recursion, loops, indexes, modifying the array input directly…) function stretchGoal(arr){ + if(!arr) return null; + if(arr.length === 1) return arr; let result = []; let length = arr.length; for(let i = 0; i < length; i++){ @@ -23,4 +26,5 @@ function stretchGoal(arr){ return result; } -console.log(stretchGoal([1,2,3,4,5])); +module.exports = {reverseArray, stretchGoal}; + diff --git a/code-challenges/array-reverse/more-array-reverse.js b/code-challenges/array-reverse/more-array-reverse.js new file mode 100644 index 0000000..c3d8ed6 --- /dev/null +++ b/code-challenges/array-reverse/more-array-reverse.js @@ -0,0 +1,17 @@ +'use strict'; + +function reverseAnArray(array){ + if(!array) return null; + if(array.length === 1) return array; + const middleOfArray = Math.floor(array.length / 2); + for(let i = 0; i < middleOfArray; i++){ + let front = array[i]; + let back = array[(array.length - 1) - i]; + + array[(array.length - 1) - i] = front; + array[i] = back; + } + return array; +} + +module.exports = reverseAnArray; diff --git a/code-challenges/depth-of-tree/depth-of-tree.js b/code-challenges/depth-of-tree/depth-of-tree.js new file mode 100644 index 0000000..3329460 --- /dev/null +++ b/code-challenges/depth-of-tree/depth-of-tree.js @@ -0,0 +1,17 @@ +'use strict'; + +function depthOfValue(tree, value, depth = 0){ + if(!tree) tree = null; + if(!value) value = null; + + if(tree === null || value === undefined) return null; + if(tree.value === value){ + return depth; + } + const left = depthOfValue(tree.left, value, depth + 1); + const right = depthOfValue(tree.right, value, depth + 1); + + return left || right; +} + +module.exports = depthOfValue; diff --git a/code-challenges/fizzbuzz-tree/fizzbuzz-tree.js b/code-challenges/fizzbuzz-tree/fizzbuzz-tree.js new file mode 100644 index 0000000..e6a2958 --- /dev/null +++ b/code-challenges/fizzbuzz-tree/fizzbuzz-tree.js @@ -0,0 +1,36 @@ +'use strict'; + +/* + +Write a function called FizzBuzzTree which takes a tree as an argument. +Without utilizing any of the built-in methods available to your language, +determine weather or not the value of each node is divisible by 3, 5 or both, and change the value of each of the nodes: +If the value is divisible by 3, replace the value with “Fizz” +If the value is divisible by 5, replace the value with “Buzz” +If the value is divisible by 3 and 5, replace the value with “FizzBuzz” +Return the tree with its new values. + +For explicitly-typed languages: Ensure your node values are of type Object, to hold either strings or integers. + +*/ + +function fizzBuzzTree(tree){ + if(tree === null) return; + if(typeof tree.value === 'number'){ + if(tree.value % 3 === 0){ + if(tree.value % 5 === 0) { + tree.value = 'fizz buzz'; + }else{ + tree.value = 'fizz'; + console.log(tree.value); + } + } + if(tree.value % 5 ===0){ + tree.value = 'buzz'; + } + } + fizzBuzzTree(tree.left); + fizzBuzzTree(tree.right); +} + +module.exports = fizzBuzzTree; diff --git a/code-challenges/tree/node.js b/code-challenges/tree/node.js new file mode 100644 index 0000000..8948751 --- /dev/null +++ b/code-challenges/tree/node.js @@ -0,0 +1,12 @@ +'use strict'; + +class Node{ + constructor(value){ + this.value = value; + this.left = null; + this.right = null; + } + +} + +module.exports = Node; diff --git a/code-challenges/tree/searchTree.js b/code-challenges/tree/searchTree.js new file mode 100644 index 0000000..86f2de9 --- /dev/null +++ b/code-challenges/tree/searchTree.js @@ -0,0 +1,57 @@ +'use strict'; + + +/* + +Create a BinarySearchTree class +Define a method named add that accepts a value, and adds a new node with that value in the correct location in the binary search tree. +Define a method named contains that accepts a value, and returns a boolean indicating whether or not the value is in the tree at least once. + +*/ + +const BinaryTree = require('./tree'); +const Node = require('./node'); + +class BinarySearchTree extends BinaryTree{ + add(value){ + if(!this.root){ + this.root = new Node(value); + } + let current = this.root; + while(current){ + if(value < current.value){ + if(!current.left){ + current.left = new Node(value); + return; + } + current = current.left; + } + if(value > current.value){ + if(!current.right){ + current.right = new Node(value); + return; + } + current = current.right; + } + } + } + + contains(value){ + if(!this.root || typeof value !== 'number'){ + return false; + } + let current = this.root; + while(current){ + if(current.value === value){ + return true; + }else if(value < current.value){ + current = current.left; + }else{ + current = current.right; + } + } + return false; + } +} + +module.exports = BinarySearchTree; diff --git a/code-challenges/tree/tree.js b/code-challenges/tree/tree.js new file mode 100644 index 0000000..3f0fbfc --- /dev/null +++ b/code-challenges/tree/tree.js @@ -0,0 +1,60 @@ +'use strict'; + +/* + +Create a Node class that has properties for the value stored in the node, the left child node, and the right child node. +Create a BinaryTree class +Define a method for each of the depth first traversals called preOrder, inOrder, and postOrder which returns an array of the values, ordered appropriately. + + +At no time should an exception or stack trace be shown to the end user. Catch and handle any such exceptions and return a printed value or operation which cleanly represents the state and either stops execution cleanly, or provides the user with clear direction and output. + +*/ + +const Node = require('./node.js'); + +class BinaryTree{ + constructor(value){ + this.root = new Node(value); + } + preOrder(node = this.root, values = []){ + if(node){ + values.push(node.value); + if(node.left !== null){ + this.preOrder(node.left, values); + } + if(node.right !== null){ + this.preOrder(node.right, values); + } + } + return values; + } + + inOrder(node = this.root, values = []){ + if(node){ + if(node.left !== null){ + this.inOrder(node.left, values); + } + values.push(node.value); + if(node.right !== null){ + this.inOrder(node.right, values); + } + } + return values; + } + + postOrder(node = this.root, values = []){ + if(node){ + if(node.left !== null){ + this.postOrder(node.left, values); + } + if(node.right !== null){ + this.postOrder(node.right, values); + } + values.push(node.value); + } + return values; + } +} + +module.exports = BinaryTree;