UNPKG

js-data-structures-and-algorithms

Version:
1,579 lines (1,468 loc) 54.8 kB
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); } function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); } function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } /** * Binary Search * * Starts at the midpoint and continues to cut the array into halves * Only can be used for sorted arrays * * Best case performance: Ω(1) (the element you're looking for is located at the array's midpoint) * Average case performance: 0(log n) * Worst case performance: O(log n) (the element is located near the beginning or end of the array) */ var binarySearch = function binarySearch(haystack, needle, showLogs) { if (!(haystack instanceof Array) || typeof needle === 'undefined' || needle === null) { return -1; } var searchableHaystack = _toConsumableArray(haystack); var i = 1; var fullHaystackMidpointIndex = 0; while (searchableHaystack.length > 0) { var midpointIndex = Math.floor(searchableHaystack.length / 2); fullHaystackMidpointIndex += midpointIndex; /* istanbul ignore next */ showLogs && console.log("iteration ".concat(i, ": midpoint index: ").concat(fullHaystackMidpointIndex, "; array to search: ").concat(searchableHaystack.join(', '), "; ").concat(needle, " === ").concat(searchableHaystack[midpointIndex], " ? ... ").concat(needle === searchableHaystack[midpointIndex], "!")); if (searchableHaystack[midpointIndex] === needle) { return fullHaystackMidpointIndex; } else if (searchableHaystack[midpointIndex] > needle) { fullHaystackMidpointIndex -= midpointIndex; searchableHaystack = searchableHaystack.slice(0, midpointIndex); } else { fullHaystackMidpointIndex += 1; searchableHaystack = searchableHaystack.slice(midpointIndex + 1); } i++; } return -1; }; /** * Linear Search * * Starts at the beginning and iterates through the whole array * * Best case performance: Ω(1) (the element you're looking for is the first in the array) * Average case performance: 0(n) * Worst case performance: O(n) (the element you're looking for is the last in the array or not in the array at all) */ var linearSearch = function linearSearch(haystack, needle, showLogs) { if (!(haystack instanceof Array) || typeof needle === 'undefined' || needle === null) { return -1; } for (var i = 0; i < haystack.length; i++) { /* istanbul ignore next */ showLogs && console.log("iteration ".concat(i + 1, ": ").concat(needle, " === ").concat(haystack[i], " ? ... ").concat(needle === haystack[i], "!")); if (needle === haystack[i]) { return i; } } return -1; }; /** * Boyer-Moore-Horspool Search * * Tries to be more efficient by skipping characters when it can. * * You go through the haystack from left to right, but you start * matching from the end of the string to find (the needle). * * If the last character of the string in the current selection in the * haystack isn’t found anywhere in the needle, you can move where you’re * searching forward by the entire length of the needle. * * If the last character of the string in the current selection in the * haystack IS found somewhere in the needle, but it’s not a match with the * last character in the needle, then you can move where you’re searching * forward by the number of indexes that character is from the end of your * needle string. * * If the last character of the string in the current selection in the * haystack matches the last character in the needle string, the you * iterate backwards from right of left over the needle string, checking * for matches. If they all match, you found it! If you get a mismatch, * you move where you’re searching forward again. * * Performance improves with the length of the search string, because * that means you can potentially skip more characters each time a bad * match occurs. * * This algorithm is appropriate as a general purpose string search algorithm. * * Best case performance: Ω(n/m), where n is the length of the string to search and m is the length of the string to find * Average case performance: 0(n) * Worst case performance: O(n*m), where n is the length of the string to search and m is the length of the string to find */ var boyerMooreHorspoolSearch = function boyerMooreHorspoolSearch(haystack, needle, showLogs) { if (typeof haystack !== 'string' || typeof needle !== 'string') { /* istanbul ignore next */ showLogs && console.log('bad input, exiting early'); return -1; } var needleLength = needle.length; var haystackRemainingLength = haystack.length; if (needleLength > haystackRemainingLength) { /* istanbul ignore next */ showLogs && console.log('needle is longer than the haystack, exiting early'); return -1; } // first loop through the needle once to // create the mismatch table so you know how much // to offset by for each character on mismatch var lastIndexOfNeedle = needleLength - 1; var mismatchTable = {}; for (var i = 0; i < needleLength; i++) { mismatchTable[needle[i]] = lastIndexOfNeedle - i; } var jumpAmount; var haystackOffset = 0; var currentIndex = 0; // loop through the haystack from beginning to end while (haystackRemainingLength >= needleLength) { // loop through the needle, starting at the end and moving backward for (currentIndex = lastIndexOfNeedle; haystack[haystackOffset + currentIndex] === needle[currentIndex]; currentIndex--) { // if you've gotten all the way to the front of the needle, // then you've found an exact match. you're done! if (currentIndex === 0) { return haystackOffset; } } // if you're here, that means we got a mismatch and can jump further down the haystack jumpAmount = mismatchTable[haystack[haystackOffset + lastIndexOfNeedle]] || needleLength; haystackRemainingLength -= jumpAmount; haystackOffset += jumpAmount; } // needle was not found in the haystack return -1; }; /** * Naive Search * * Starts at the beginning and iterates through the whole string * * Best case performance: Ω(1) (the substring you're looking for is the start of the string) * Average case performance: 0(n+m) * Worst case performance: O(n*m), where the length of the pattern is m and the length of the search string is n * (the substring you're looking for is the end of the string or not in the string at all) */ var naiveSearch = function naiveSearch(haystack, needle, showLogs) { if (typeof haystack !== 'string' || typeof needle !== 'string') { /* istanbul ignore next */ showLogs && console.log('bad input, exiting early'); return -1; } if (needle.length > haystack.length) { /* istanbul ignore next */ showLogs && console.log('needle is longer than the haystack, exiting early'); return -1; } for (var i = 0; i < haystack.length; i++) { for (var j = 0; j < needle.length; j++) { if (haystack[i + j] !== needle[j]) { break; } if (j === needle.length - 1) { return i; } } } return -1; }; /** * Set - A collection of unique items (no duplicates allowed) * * Methods and properties: * * - add: Constant — O(1) * - remove: Linear — O(n) * - has: Linear — O(n) * - isEmpty: Constant — O(1) * - size: Constant — O(1) * - enumerate: Linear - O(n) * - clear: Constant - O(1) */ var Set = /*#__PURE__*/function () { function Set() { _classCallCheck(this, Set); this.items = []; this.length = 0; } _createClass(Set, [{ key: "add", value: function add(val) { if (this.items.indexOf(val) === -1) { this.items.push(val); this.length++; } return val; } }, { key: "remove", value: function remove(val) { var index = this.items.indexOf(val); if (index !== -1) { this.items.splice(index, 1); this.length--; } return val; } }, { key: "has", value: function has(val) { return this.items.indexOf(val) !== -1; } }, { key: "isEmpty", value: function isEmpty() { return this.length === 0; } }, { key: "size", value: function size() { return this.length; } }, { key: "enumerate", value: function enumerate() { return this.items; } }, { key: "clear", value: function clear() { this.length = 0; this.items = []; return this.items; } }]); return Set; }(); /** * Intersection * * Compares two sets and produces a third set that contains all of the * intersecting/matching values that are found in both sets * * Ex. Intersection of { 1, 2, 3 } and { 2, 3, 4 } is { 2, 3 } * * Performance: Quadratic - O(n * m), where n is the length of set 1 and m is the length of set 2 * Note: this is sort of like O(n^2) performance */ var intersection = function intersection(set1, set2) { var intersectionSet = new Set(); set1.enumerate().forEach(function (val) { if (set2.has(val)) { intersectionSet.add(val); } }); set2.enumerate().forEach(function (val) { if (set1.has(val)) { intersectionSet.add(val); } }); return intersectionSet; }; /** * Set Difference * * Compares two sets and returns all items of the first set that are not members of the second set * * Ex. Set difference of { 2, 3, 4 } and { 3, 4, 5 } is { 2 } * * Performance: Quadratic - O(n * m), where n is the length of set 1 and m is the length of set 2 * Note: this is sort of like O(n^2) performance */ var setDifference = function setDifference(set1, set2) { var setDifferenceSet = new Set(); set1.enumerate().forEach(function (val) { if (!set2.has(val)) { setDifferenceSet.add(val); } }); return setDifferenceSet; }; /** * Union * * Compares two sets and produces a third set that contains all unique values found in either set * * Ex. Union of { 1, 2, 3 } and { 3, 4, 5 } is { 1, 2, 3, 4, 5 } * * Performance: Linear - O(n + m), where n is the length of set 1 and m is the length of set 2 */ var union = function union(set1, set2) { var unionSet = new Set(); set1.enumerate().forEach(function (val) { return unionSet.add(val); }); set2.enumerate().forEach(function (val) { return unionSet.add(val); }); return unionSet; }; /** * Symmetric Difference * * Compares two sets and returns all of the items that exist in only one of the two sets * The symmetric difference is the set difference of the union and intersection of the input sets * * Ex. Symmetric difference of { 2, 3, 4 } and { 3, 4, 5 } is { 2, 5 } * * Performance: Quadratic - O(n * m), where n is the length of set 1 and m is the length of set 2 * Note: this is sort of like O(n^2) performance */ var symmetricDifference = function symmetricDifference(set1, set2) { var intersectionSet = intersection(set1, set2); var unionSet = union(set1, set2); return setDifference(unionSet, intersectionSet); }; /** * Bubble Sort * * Compares each pair of adjacent items and swaps them if they are in the wrong order * Continues looping through the array until no more swaps are needed * Not appropriate for large unsorted data sets * * Best case performance: Ω(n) (only one element is out of place, so only one iteration through the array) * Average case performance: θ(n^2) (array is mostly unsorted, have to do a loop nested within a loop) * Worst case performance: O(n^2) (array is entirely unsorted, have to do a loop nested within a loop) * * Space required: O(n) (because it operates directly on the input array) */ var bubbleSort = function bubbleSort(arr, showLogs) { var sortedArr = _toConsumableArray(arr); var didSwapSomething = false; var iteration = 0; /* istanbul ignore next */ showLogs && console.log("starting array: ".concat(sortedArr.join(' '))); do { didSwapSomething = false; iteration++; for (var i = 0; i < sortedArr.length - 1; i++) { if (sortedArr[i] > sortedArr[i + 1]) { var firstPosition = sortedArr[i]; sortedArr[i] = sortedArr[i + 1]; sortedArr[i + 1] = firstPosition; didSwapSomething = true; } } /* istanbul ignore next */ showLogs && console.log("iteration ".concat(iteration, ": array: ").concat(sortedArr.join(' '))); } while (didSwapSomething); return sortedArr; }; /** * Counting Sort * * Counts the occurrence of each element in the input array and uses that to create a sorted output array * * Best case performance: Ω(n + k) (always loops through the input array, count array, and output array) * Average case performance: θ(n + k) (always loops through the input array, count array, and output array) * Worst case performance: O(n + k) (always loops through the input array, count array, and output array) * * Space required: O(n + k) */ var countingSort = function countingSort(arr, minValue, maxValue, showLogs) { var sortedArr = []; var countArr = []; var min = minValue; var max = maxValue; /* istanbul ignore next */ showLogs && console.log("starting array: ".concat(arr.join(' '))); if (typeof min === 'undefined' || typeof max === 'undefined') { /* istanbul ignore next */ showLogs && console.log('getting min and max values of the input array'); for (var i = 0; i < arr.length; i++) { var currentElement = arr[i]; if (i === 0) { min = currentElement; max = currentElement; } if (currentElement < min) { min = currentElement; } if (currentElement > max) { max = currentElement; } } } /* istanbul ignore next */ showLogs && console.log("min value: ".concat(min, "; max value: ").concat(max)); var countRange = max - min + 1; for (var _i = 0; _i < countRange; _i++) { countArr.push(0); } /* istanbul ignore next */ showLogs && console.log("initialized count array: ".concat(countArr.join(' '))); for (var _i2 = 0; _i2 < arr.length; _i2++) { var _currentElement = arr[_i2]; countArr[_currentElement - min]++; } /* istanbul ignore next */ showLogs && console.log("populated count array: ".concat(countArr.join(' '))); for (var _i3 = 0; _i3 < countArr.length; _i3++) { while (countArr[_i3] > 0) { sortedArr.push(_i3 + min); countArr[_i3]--; } } /* istanbul ignore next */ showLogs && console.log("sorted output array: ".concat(sortedArr.join(' '))); return sortedArr; }; /** * Insertion Sort * * Inserts each element into its correct place in the array, one element at a time * Loops through the array for every element * * Everything to the left of the current item is known to be sorted * Everything to the right of the current item is unsorted * Not appropriate for large unsorted data sets * * Best case performance: Ω(n) (only one element is out of place, so only one iteration through the array) * Average case performance: 0(n^2) (array is mostly unsorted, have to do a loop nested within a loop) * Worst case performance: O(n^2) (array is entirely unsorted, have to do a loop nested within a loop) * * Space required: O(n) (because it operates directly on the input array) */ var insertionSort = function insertionSort(arr, showLogs) { var sortedArr = _toConsumableArray(arr); /* istanbul ignore next */ showLogs && console.log("starting array: ".concat(sortedArr.join(' '))); for (var i = 1; i < sortedArr.length; i++) { var valueToInsert = sortedArr.splice(i, 1)[0]; var didInsertValue = false; for (var j = i; j >= 1; j--) { /* istanbul ignore next */ showLogs && console.log(" comparing ".concat(valueToInsert, " with ").concat(sortedArr[j - 1])); if (valueToInsert > sortedArr[j - 1]) { /* istanbul ignore next */ showLogs && console.log(" inserting ".concat(valueToInsert, " after ").concat(sortedArr[j - 1])); sortedArr = [].concat(_toConsumableArray(sortedArr.slice(0, j)), [valueToInsert], _toConsumableArray(sortedArr.slice(j))); didInsertValue = true; break; } } if (!didInsertValue) { /* istanbul ignore next */ showLogs && console.log(" inserting ".concat(valueToInsert, " at first index")); sortedArr = [valueToInsert].concat(_toConsumableArray(sortedArr)); } /* istanbul ignore next */ showLogs && console.log("iteration ".concat(i + 1, ": array: ").concat(sortedArr.join(' '))); } return sortedArr; }; /** * Merge Sort * * The array is recursively split in half * When the array is in groups of 1, it is reconstructed in sort order * Each reconstructed array is merged with the other half * * So if you had an array of 8 items, it’s broken down into two groups of 4, * then four groups of 2, then eight groups of 1 * Then it’s built back up where you combine the eight groups of 1 into four groups of 2, * but you sort within each group of 2. Then you combine the four groups of 2 into two * groups of 4, and sort those groups of 4. Then you combine the two groups of 4 into * one group of 8, and you sort that group. * * Appropriate for large data sets * Data splitting means that the algorithm can be done in parallel * * Best case performance: Ω(n log n) * Average case performance: 0(n log n) * Worst case performance: O(n log n) * * Space required: O(n) (merge sort can be performed in-place, but it’s often not) * * Merge sort is a predictable algorithm since the time complexity is the same for the worst, average, and best case * If you don’t do it in-place, then you take up extra memory allocations for the temporary arrays */ var mergeSort = function mergeSort(arr, showLogs) { /* istanbul ignore next */ showLogs && console.log("current array: ".concat(arr.join(' '))); if (arr.length < 2) { /* istanbul ignore next */ showLogs && console.log(' array only has 0 or 1 items, so nothing to sort!'); return arr; } // Divide the array into two sub-arrays, right down the middle /* istanbul ignore next */ showLogs && console.log(' splitting the array down the middle!'); var midpointIndex = Math.floor(arr.length / 2); var leftSubArray = mergeSort(arr.slice(0, midpointIndex), showLogs); var rightSubArray = mergeSort(arr.slice(midpointIndex), showLogs); return merge(leftSubArray, rightSubArray, showLogs); }; var merge = function merge(arr1, arr2, showLogs) { /* istanbul ignore next */ showLogs && console.log("merging arrays [".concat(arr1.join(' '), "] and [").concat(arr2.join(' '), "]")); var result = []; while (arr1.length > 0 && arr2.length > 0) { // The two sub-arrays are always sorted, so in order to combine the two sub-arrays, // we need to pick off the first value from whichever array currently has // a lower value as its first element result.push(arr1[0] < arr2[0] ? arr1.shift() : arr2.shift()); /* istanbul ignore next */ showLogs && console.log(" current merge result from the two sub-arrays: ".concat(result.join(' '))); } // The while loop stops once one sub-array no longer has any elements, // so this combines the result array with whichever sub-array still // has elements in it var finalResult = result.concat(arr1.length ? arr1 : arr2); /* istanbul ignore next */ showLogs && console.log(" final merge result from the two sub-arrays: ".concat(finalResult.join(' '))); return finalResult; }; /** * Quick Sort * * Pick a pivot value and partition the array * Put all values smaller than the pivot to the left and all values larger * than the pivot to the right * Then continue to perform the pivot and partition algorithm on the left * and right partitions, and repeat until it’s all sorted * * Not appropriate for large inverse-sorted data sets * You are doing recursion, so be aware that it puts a new function call * on the stack the deeper you go * * Best case performance: Ω(n log n) * Average case performance: 0(n log n) * Worst case performance: O(n^2) * * Space required: O(n) (because it operates directly on the input array) */ var quickSort = function quickSort(arr) { var left = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; var right = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : arr.length - 1; var showLogs = arguments.length > 3 ? arguments[3] : undefined; /* istanbul ignore next */ showLogs && console.log("current array: ".concat(arr.join(' '))); if (arr.length < 2) { /* istanbul ignore next */ showLogs && console.log(' array only has 0 or 1 items, so nothing to sort!'); return arr; } var pivotIndex = partition(arr, left, right, showLogs); // index returned from partition if (left < pivotIndex - 1) { // more elements on the left side of the pivot quickSort(arr, left, pivotIndex - 1, showLogs); } if (pivotIndex < right) { // more elements on the right side of the pivot quickSort(arr, pivotIndex, right, showLogs); } return arr; }; // swaps the position of two elements in an array var swap = function swap(arr, leftIndex, rightIndex, showLogs) { /* istanbul ignore next */ showLogs && console.log(" swapping elements with values ".concat(arr[leftIndex], " and ").concat(arr[rightIndex])); var temp = arr[leftIndex]; arr[leftIndex] = arr[rightIndex]; arr[rightIndex] = temp; }; var partition = function partition(arr, leftIndex, rightIndex, showLogs) { var pivotIndex = Math.floor((rightIndex + leftIndex) / 2); var pivotElement = arr[pivotIndex]; /* istanbul ignore next */ showLogs && console.log(" pivot element chosen is ".concat(pivotElement)); var currentLeftIndex = leftIndex; var currentRightIndex = rightIndex; /* istanbul ignore next */ showLogs && console.log(" current left element: ".concat(arr[currentLeftIndex], ", current right element: ").concat(arr[currentRightIndex])); while (currentLeftIndex <= currentRightIndex) { while (arr[currentLeftIndex] < pivotElement) { /* istanbul ignore next */ showLogs && console.log(" left element ".concat(arr[currentLeftIndex], " is less than ").concat(pivotElement, ", moving right by one element to ").concat(arr[currentLeftIndex + 1])); currentLeftIndex++; } /* istanbul ignore next */ showLogs && console.log(" left element ".concat(arr[currentLeftIndex], " is greater than or equal to ").concat(pivotElement, ", so ").concat(arr[currentLeftIndex], " is eligible to be swapped")); while (arr[currentRightIndex] > pivotElement) { /* istanbul ignore next */ showLogs && console.log(" right element ".concat(arr[currentRightIndex], " is greater than ").concat(pivotElement, ", moving left by one element to ").concat(arr[currentRightIndex - 1])); currentRightIndex--; } /* istanbul ignore next */ showLogs && console.log(" right element ".concat(arr[currentRightIndex], " is less than or equal to ").concat(pivotElement, ", so ").concat(arr[currentRightIndex], " is eligible to be swapped")); if (currentLeftIndex <= currentRightIndex) { /* istanbul ignore next */ showLogs && console.log(" left element ".concat(arr[currentLeftIndex], " is less than or equal to right element ").concat(arr[currentRightIndex], ", so we can swap these two elements")); swap(arr, currentLeftIndex, currentRightIndex, showLogs); currentLeftIndex++; currentRightIndex--; /* istanbul ignore next */ showLogs && console.log(" resulting array: ".concat(arr.join(' '))); } else { /* istanbul ignore next */ showLogs && console.log(" left element ".concat(arr[currentLeftIndex], " is NOT less than or equal to right element ").concat(arr[currentRightIndex], ", so we will NOT swap these two elements")); } } return currentLeftIndex; }; /** * Selection Sort * * Finds the lowest value in the remaining unsorted items in the array * and swaps it into the earliest unsorted position * Loops through the array for every element * * The bubble sort iterates through the entire array for each pass, * but the selection sort only has to iterate through the remaining unsorted * items for each pass, making it a little more efficient * Not appropriate for large unsorted data sets * * Best case performance: Ω(n^2) (always loops through the entire array) * Average case performance: 0(n^2) (always loops through the entire array) * Worst case performance: O(n^2) (always loops through the entire array) * * Space required: O(n) (because it operates directly on the input array) */ var selectionSort = function selectionSort(arr, showLogs) { var sortedArr = _toConsumableArray(arr); /* istanbul ignore next */ showLogs && console.log("starting array: ".concat(sortedArr.join(' '))); for (var i = 0; i < sortedArr.length - 1; i++) { var minValueIndex = i; for (var j = i; j < sortedArr.length; j++) { if (sortedArr[j] < sortedArr[minValueIndex]) { minValueIndex = j; } } var originalStartingValue = sortedArr[i]; sortedArr[i] = sortedArr[minValueIndex]; sortedArr[minValueIndex] = originalStartingValue; /* istanbul ignore next */ showLogs && console.log("iteration ".concat(i + 1, ": array: ").concat(sortedArr.join(' '))); } return sortedArr; }; /** * Node - Contains a value and a pointer to the left node and to the right node * * Meant to be used with a binary search tree * * Methods and properties: * * - val: any value * - left: pointer to the left node (or null) * - right: pointer to the right node (or null) */ var Node$2 = /*#__PURE__*/_createClass(function Node(val) { _classCallCheck(this, Node); this.val = val; this.left = null; this.right = null; }); var BinarySearchTree = /*#__PURE__*/function () { function BinarySearchTree() { _classCallCheck(this, BinarySearchTree); this.root = null; } _createClass(BinarySearchTree, [{ key: "insert", value: function insert(val) { var newNode = new Node$2(val); // handle adding the first node if (this.root === null) { this.root = newNode; return this.root; } // adding a node to an existing tree this._insertNode(this.root, newNode); return newNode; } // helper method for recursively finding the correct place to insert the new node }, { key: "_insertNode", value: function _insertNode(currentNode, newNode) { if (currentNode.val > newNode.val && currentNode.left === null) { currentNode.left = newNode; return newNode; } else if (currentNode.val > newNode.val) { return this._insertNode(currentNode.left, newNode); } else if ((currentNode.val < newNode.val || currentNode.val === newNode.val) && currentNode.right === null) { currentNode.right = newNode; return newNode; } else { return this._insertNode(currentNode.right, newNode); } } }, { key: "contains", value: function contains(val) { var doesTreeContainValue = false; if (this.root === null) { return doesTreeContainValue; } function findValueInTree(currentNode) { if (currentNode.val === val) { doesTreeContainValue = true; } else if (currentNode.left !== null && val < currentNode.val) { findValueInTree(currentNode.left); } else if (currentNode.right !== null && val > currentNode.val) { findValueInTree(currentNode.right); } } findValueInTree(this.root); return doesTreeContainValue; } }, { key: "remove", value: function remove(val) { // the root is re-initialized with the root of a modified tree this.root = this._removeNode(this.root, val); return this.root; } }, { key: "_removeNode", value: function _removeNode(currentNode, key) { // if the root is null then tree is empty if (currentNode === null) { return null; } // if the value to be deleted is less than the root's value, then move to the left subtree if (key < currentNode.val) { currentNode.left = this._removeNode(currentNode.left, key); return currentNode; } // if the value to be deleted is greater than the root's value, then move to the right subtree if (key > currentNode.val) { currentNode.right = this._removeNode(currentNode.right, key); return currentNode; } // if the value to be deleted is equal to the root's data, then delete this node. // the node could have 0, 1, or 2 children // deleting a node with no children if (currentNode.left === null && currentNode.right === null) { currentNode = null; return currentNode; } // deleting a node with one child (right) if (currentNode.left === null) { currentNode = currentNode.right; return currentNode; } // deleting a node with one child (left) if (currentNode.right === null) { currentNode = currentNode.left; return currentNode; } // deleting a node with two children. // the minimum node of the right subtree is stored in a temporary variable var temp = this._findMinNode(currentNode.right); currentNode.val = temp.val; currentNode.right = this._removeNode(currentNode.right, temp.val); return currentNode; } // 1. Traverse the left subtree (i.e. perform inOrderTraversal on the left subtree) // 2. Visit the root // 3. Traverse the right subtree (i.e. perform inOrderTraversal on the right subtree) }, { key: "inOrderTraversal", value: function inOrderTraversal() { var node = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.root; var callback = arguments.length > 1 ? arguments[1] : undefined; if (node !== null) { this.inOrderTraversal(node.left, callback); if (typeof callback === 'function') { callback(node); } this.inOrderTraversal(node.right, callback); } } // 1. Visit the root // 2. Traverse the left subtree (i.e. perform preOrderTraversal on the left subtree) // 3. Traverse the right subtree (i.e. perform preOrderTraversal on the right subtree) }, { key: "preOrderTraversal", value: function preOrderTraversal() { var node = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.root; var callback = arguments.length > 1 ? arguments[1] : undefined; if (node !== null) { if (typeof callback === 'function') { callback(node); } this.preOrderTraversal(node.left, callback); this.preOrderTraversal(node.right, callback); } } // 1. Traverse the left subtree (i.e. perform postOrderTraversal on the left subtree) // 2. Traverse the right subtree (i.e. perform postOrderTraversal on the right subtree) // 3. Visit the root }, { key: "postOrderTraversal", value: function postOrderTraversal() { var node = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.root; var callback = arguments.length > 1 ? arguments[1] : undefined; if (node !== null) { this.postOrderTraversal(node.left, callback); this.postOrderTraversal(node.right, callback); if (typeof callback === 'function') { callback(node); } } } }, { key: "isEmpty", value: function isEmpty() { return this.root === null; } }, { key: "clear", value: function clear() { this.root = null; return this.root; } // helper method to find the minimum node in the tree, starting at a specified node }, { key: "_findMinNode", value: function _findMinNode(node) { // if the left of a node is null, then it must be the minimum node if (node.left === null) { return node; } return this._findMinNode(node.left); } }]); return BinarySearchTree; }(); /** * Node - Contains a value and a pointer to the next node and to the previous node * * Meant to be used with a doubly linked list * * Methods and properties: * * - val: any value * - next: pointer to the next node (or null) * - prev: pointer to the previous node (or null) */ var Node$1 = /*#__PURE__*/_createClass(function Node(val) { var next = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; var prev = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; _classCallCheck(this, Node); this.val = val; this.next = next; this.prev = prev; }); var DoublyLinkedList = /*#__PURE__*/function () { function DoublyLinkedList() { _classCallCheck(this, DoublyLinkedList); this.head = null; this.tail = null; this.length = 0; } _createClass(DoublyLinkedList, [{ key: "insertAtBeginning", value: function insertAtBeginning(val) { var newNode = new Node$1(val); // the list is currently empty if (!this.head) { this.head = newNode; this.tail = newNode; this.length++; return this.head; } // the list has at least one node in it already this.head.prev = newNode; newNode.next = this.head; this.head = newNode; this.length++; return this.head; } }, { key: "insertAtEnd", value: function insertAtEnd(val) { var newNode = new Node$1(val); // the list is currently empty if (!this.head) { return this.insertAtBeginning(val); } // the list has at least one node in it already newNode.prev = this.tail; this.tail.next = newNode; this.tail = newNode; this.length++; return this.head; } }, { key: "insertAt", value: function insertAt(val, index) { // if the list is empty // i.e. head = null if (!this.head) { return this.insertAtBeginning(val); } // if new node needs to be inserted at the front of the list // i.e. before the head if (index === 0) { return this.insertAtBeginning(val); } // if new node needs to be inserted at the end of the list // i.e. after the tail if (index >= this.length) { return this.insertAtEnd(val); } // else, use getAt() to find the node that is right before the specified index var previous = this.getAt(index - 1); var newNode = new Node$1(val); newNode.next = previous.next; newNode.prev = previous; previous.next = newNode; this.length++; return this.head; } }, { key: "getAt", value: function getAt(index) { var counter = 0; var node = this.head; while (node) { if (counter === index) { return node; } counter++; node = node.next; } return null; } }, { key: "deleteFirstNode", value: function deleteFirstNode() { // empty list, so nothing to delete if (!this.head) { return this.head; } // if there is only one node in the list if (!this.head.next) { this.head = null; this.tail = null; this.length--; return this.head; } // delete the head node and make the next node the new head this.head = this.head.next; this.head.prev = null; this.length--; return this.head; } }, { key: "deleteLastNode", value: function deleteLastNode() { // empty list, so nothing to delete if (!this.head) { return this.head; } // if there is only one node in the list if (!this.head.next) { this.head = null; this.tail = null; this.length--; return this.head; } // if there are multiple nodes in the list this.tail = this.tail.prev; this.tail.next = null; this.length--; return this.head; } }, { key: "deleteAt", value: function deleteAt(index) { // empty list, so nothing to delete if (!this.head) { return this.head; } // node needs to be deleted from the front of the list // i.e. delete the head. if (index === 0) { return this.deleteFirstNode(); } // node needs to be deleted from the end of the list // i.e. delete the tail. if (index === this.length - 1) { return this.deleteLastNode(); } // else, use getAt() to find the node that is right before the specified index var previous = this.getAt(index - 1); // if the specified index does not map to a node, do nothing if (!previous || !previous.next) { return this.head; } // if the specified index does map to a node, // set the previous node's next value to the next next value, // thereby removing the node you want to delete from the list previous.next = previous.next.next; previous.next.prev = previous; this.length--; return this.head; } }, { key: "reverse", value: function reverse() { var currentNode = this.head; var previousNode = null; while (currentNode !== null) { // save the next pointer before we overwrite currentNode.next! var tmp = currentNode.next; // switch the values of the next and prev pointers currentNode.next = previousNode; currentNode.prev = tmp; // step forward in the list previousNode = currentNode; currentNode = tmp; } // set the new head and tail nodes when the reversal is finished this.tail = this.head; this.head = previousNode; } }, { key: "traverse", value: function traverse(callback) { if (typeof callback !== 'function') { return false; } var currentNode = this.head; while (currentNode !== null) { callback(currentNode); currentNode = currentNode.next; } return true; } }, { key: "traverseReverse", value: function traverseReverse(callback) { if (typeof callback !== 'function') { return false; } var currentNode = this.tail; while (currentNode !== null) { callback(currentNode); currentNode = currentNode.prev; } return true; } }, { key: "isEmpty", value: function isEmpty() { return this.length === 0; } }, { key: "size", value: function size() { return this.length; } }, { key: "enumerate", value: function enumerate() { var nodes = []; var node = this.head; while (node) { nodes.push(node.val); node = node.next; } return nodes; } }, { key: "clear", value: function clear() { this.head = null; this.tail = null; this.length = 0; return this.head; } }]); return DoublyLinkedList; }(); /** * Node - Contains a value and a pointer to the next node * * Meant to be used with a singly linked list * * Methods and properties: * * - val: any value * - next: pointer to the next node (or null) */ var Node = /*#__PURE__*/_createClass(function Node(val) { var next = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null; _classCallCheck(this, Node); this.val = val; this.next = next; }); var LinkedList = /*#__PURE__*/function () { function LinkedList() { _classCallCheck(this, LinkedList); this.head = null; this.length = 0; } _createClass(LinkedList, [{ key: "insertAtBeginning", value: function insertAtBeginning(val) { var newNode = new Node(val); newNode.next = this.head; this.head = newNode; this.length++; return this.head; } }, { key: "insertAtEnd", value: function insertAtEnd(val) { var newNode = new Node(val); // If this is the first node added, it is the head if (!this.head) { this.head = newNode; this.length++; return this.head; } // If there are nodes in this linked list, // iterate thru the nodes and then add this // new node to the end of the list so it becomes the tail var currentNode = this.head; while (currentNode.next !== null) { currentNode = currentNode.next; } currentNode.next = newNode; this.length++; return this.head; } }, { key: "insertAt", value: function insertAt(val, index) { // if the list is empty // i.e. head = null if (!this.head) { return this.insertAtBeginning(val); } // if new node needs to be inserted at the front of the list // i.e. before the head if (index === 0) { return this.insertAtBeginning(val); } // if new node needs to be inserted at the end of the list // i.e. after the tail if (index >= this.length) { return this.insertAtEnd(val); } // else, use getAt() to find the node that is right before the specified index var previous = this.getAt(index - 1); var newNode = new Node(val); newNode.next = previous.next; previous.next = newNode; this.length++; return this.head; } }, { key: "getAt", value: function getAt(index) { var counter = 0; var node = this.head; while (node) { if (counter === index) { return node; } counter++; node = node.next; } return null; } }, { key: "deleteFirstNode", value: function deleteFirstNode() { // empty list, so nothing to delete if (!this.head) { return this.head; } // delete the head node and make the next node the new head this.head = this.head.next; this.length--; return this.head; } }, { key: "deleteLastNode", value: function deleteLastNode() { // empty list, so nothing to delete if (!this.head) { return this.head; } // if there is only one node in the list if (!this.head.next) { this.head = null; this.length--; return this.head; } // if there are multiple nodes in the list var previous = this.head; var tail = this.head.next; while (tail.next !== null) { previous = tail; tail = tail.next; } previous.next = null; this.length--; return this.head; } }, { key: "deleteAt", value: function deleteAt(index) { // empty list, so nothing to delete if (!this.head) { return this.head; } // node needs to be deleted from the front of the list // i.e. before the head. if (index === 0) { return this.deleteFirstNode(); } // else, use getAt() to find the node that is right before the specified index var previous = this.getAt(index - 1); // if the specified index does not map to a node, do nothing if (!previous || !previous.next) { return this.head; } // if the specified index does map to a node, // set the previous node's next value to the next next value, // thereby removing the node you want to delete from the list previous.next = previous.next.next; this.length--; return this.head; } }, { key: "reverse", value: function reverse() { var currentNode = this.head; var previousNode = null; while (currentNode !== null) { // save the next pointer before we overwrite currentNode.next! var tmp = currentNode.next; // reverse the next pointer to point at the previous node currentNode.next = previousNode; // step forward in the list previousNode = currentNode; currentNode = tmp; } // set the new head node when the reversal is finished this.head = previousNode; return true; } }, { key: "traverse", value: function traverse(callback) { if (typeof callback !== 'function') { return false; } var currentNode = this.head; while (currentNode !== null) { callback(currentNode); currentNode = currentNode.next; } return true; } }, { key: "isEmpty", value: function isEmpty() { return this.length === 0; } }, { key: "size", value: function size() { return this.length; } }, { key: "enumerate", value: function enumerate() { var nodes = []; var node = this.head; while (node) { nodes.push(node.val); node = node.next; } return nodes; } }, { key: "clear", value: function clear() { this.head = null; this.length = 0; return this.head; } }]); return LinkedList; }(); /** * Priority Queue - highest priority get dequeued first, like a list of calls to 911 dispatchers * * NOTE: For this implementation, we'll assume a lower number means a higher priority. * i.e. An item with priority 1 would be dequeued before an item with priority 3 * * Methods and properties: * * - enqueue: Constant — O(1) * - dequeue: Constant — O(1) * - peek: Constant — O(1) * - isEmpty: Constant — O(1) * - size: Constant — O(1) * - enumerate: Linear - O(n) * - clear: Constant - O(1) */ var PriorityQueue = /*#__PURE__*/function () { function PriorityQueue() { _classCallCheck(this, PriorityQueue); this.items = []; this.length = 0; } _createClass(PriorityQueue, [{ key: "enqueue", value: function enqueue(value) { var priority = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0; // handle inserting the new item in the right place according to priority for (var i = 0; i < this.length; i++) { if (priority < this.items[i].priority) { var _item = { value: value, priority: priority }; this.items.splice(i, 0, _item); this.length++; return _item; } } // if we've iterated through the entire priority queue, // then just add the new value at the end var item = { value: value, priority: priority }; this.items.push(item); this.length++; return item; } }, { key: "dequeue", value: function dequeue() { if (this.length) { this.length--; return this.items.shift(); } else { return null; } } }, { key: "peek", value: function peek() { return this.items[0] || null; } }, { key: "isEmpty", value: function isEmpty() { return this.length === 0; } }, { key: "size", value: function size() { return this.length; } }, { key: "enumerate", value: function enumerate() { return this.items; } }, { key: "clear", value: function clear() { this.length = 0; this.items = []; return this.items; } }]); return PriorityQueue; }(); /** * Queue - FIFO, like a checkout line at a grocery store * * NOTE: This queue is implemented with an array as its underlying data structure. * * Methods and properties: * * - enqueue: Constant — O(1) * - dequeue: Constant — O(1) * - peek: Constant — O(1) * - isEmpty: Constant — O(1) * - size: Constant — O(1) * - enumerate: Linear - O(n) * - clear: Constant - O(1) */ var Queue = /*#__PURE__*/function () { function Queue() { _classCallCheck(this, Queue); this.items = []; this.length = 0; } _createClass(Queue, [{ key: "enqueue", value: function enqueue(val) { this.items.push(val); this.length++; return val; } }, { key: "dequeue", value: function dequeue() { if (this.length) { this.length--; return this.items.shift(); } else { return null; } } }, { key: "peek", value: function peek() { return this.items[0]