js-data-structures-and-algorithms
Version:
Data structures and algorithms implemented in JavaScript
1,579 lines (1,468 loc) • 54.8 kB
JavaScript
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]