diff --git a/.gitignore b/.gitignore index 2439c82..7c323aa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .vscode/ node_modules +/dist \ No newline at end of file diff --git a/README.md b/README.md index 2f1cb26..d72202f 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,47 @@ # JavaScript Algorithms -Snippets for a bunch of useful JavaScript algorithms. - -## Complexity - Beginner - -| naiveSearch() | | -| -------------------- | -------------------------------------------------------------------------- | -| _Type_ | Loop in loop | -| _How it works_ | Compares a string with another string and returns number of matches found. | -| _Positive_ | Simple, good for small data sets. | -| _Negative_ | Inefficient for large data sets. | -| _Efficiency Average_ | O(n^2) | - -| bubbleSort() | | -| -------------------- | ------------------------------------------- | -| _Type_ | Loop in loop | -| _How it works_ | Swapping, sorting from smallest to largest. | -| _Positive_ | Simple, good for small data sets. | -| _Negative_ | Inefficient for large data sets. | -| _Efficiency Average_ | O(n^2) | - -| selectionSort() | | -| -------------------- | ------------------------------------------- | -| _Type_ | Loop in loop | -| _How it works_ | Swapping, sorting from largest to smallest. | -| _Positive_ | Simple, good for small data sets. | -| _Negative_ | Inefficient for large data sets. | -| _Efficiency Average_ | O(n^2) | - -| insertionSort() | | -| -------------------- | ------------------------------------------------------------------------------------------------- | -| _Type_ | Loop in loop | -| _How it works_ | Sorting into new array. | -| _Positive_ | Simple, good for small data sets and nearly sorted data, or when new data is addded continuously. | -| _Negative_ | Inefficient for large data sets. | -| _Efficiency Average_ | O(n^2) | - -## Complexity - Intermediate - -| mergeSort() | | -| -------------------- | --------------------------------------------------------------------------------------------------------------------------------- | -| _Type_ | Recursive with loop | -| _How it works_ | Splits arrays until each array has only one item (= is sorted). Then merge and sort each array until there's only one array left. | -| _Positive_ | More complex, good for large data sets | -| _Negative_ | | -| _Efficiency Average_ | O(n log n) | - -| quickSort() | | -| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| _Type_ | Recursive with loop | -| _How it works_ | Sets pivot on the mean value, puts smaller numbers left of pivot and larger right of pivot. Then sorts left half and right half of increasingly smaller sizes, until its done. | -| _Positive_ | | -| _Negative_ | With too large data sets you might run out of call stacks. | -| _Efficiency Average_ | O(n log n) | - -## Complexity - Expert - -| radixSort() | | -| -------------------- | --------------------------------------------------------------------------------------------------------- | -| _Type_ | Loop in loop | -| _How it works_ | Sorts an array of integers by loop-sorting them in buckets by number and then putting them back together. | -| _Positive_ | Fast. | -| _Negative_ | Only works on integers. | -| _Efficiency Average_ | O(n k) | +Library for a bunch of useful JavaScript algorithms. + +## Installation + +`npm install javascript-algorithms` + +## Content + +### Sorters + +The sorters package contains a number of different kind of sorters which have different benefits and strengths. You can read more about them in detail [here](). + +### Number Generators + +Number generators can be used for creating different kind of numbers and number series. + +### Data Structures + +JavaScript doesn't inherently support certain types of Lists that exist in other languages. These classes will allow you to use List types such as _Singly Linked List_, _Doubly Linked List_, _Stacks_ and _Queues_. + +## Example (Sorters) + +```typescript +import { sorters } from "javascript-algorithms"; + +const arrayToSort = [1, 6, 2, 5, 9, 10, 11, 3, 4, 12]; + +sorters.selectionSorts.quicksort_inPlace(arrayToSort); + +console.log(arrayToSort); // [1, 2, 3, 4, 5, 6, 9, 10, 11, 12] +``` + +Note: Some sorters sorts in-place, meaning they will mutate the original array. If you want to keep the original array untouched, you need to first make a copy. + +```typescript +import { sorters } from "javascript-algorithms"; + +const unsortedArray = [1, 6, 2, 5, 9, 10, 11, 3, 4, 12]; + +const arrayToSort = [...unsortedArray]; + +sorters.selectionSorts.quicksort_inPlace(arrayToSort); + +console.log(arrayToSort); // [1, 2, 3, 4, 5, 6, 9, 10, 11, 12] +``` diff --git a/index.js b/index.js deleted file mode 100644 index 0003730..0000000 --- a/index.js +++ /dev/null @@ -1,235 +0,0 @@ -/* - Naive String Search -*/ -function naiveSearch(string, find) { - let count = 0 - for (let i = 0; i < string.length; i++) { - for (let j = 0; j < find.length; j++) { - console.log(find[j], string[i + j]) - if (find[j] !== string[i + j]) { - console.log('BREAK') - break - } - if (j === find.length - 1) { - console.log('FOUND MATCH') - count++ - } - } - } - return count -} -//naiveSearch('lorie loled', 'lo') - -// 1. EASIER SORTING ALGORITHMS - -/* - Bubble Sort - Swapping, from smallest to biggest - + Simple, good for small data sets - - Not very efficient - Time Complexity Average: O(n^2) -*/ -function bubbleSort(arr) { - let arrayIsSorted - for (let i = arr.length; i > 0; i--) { - arrayIsSorted = true - for (let j = 0; j < i - 1; j++) { - console.log(arr, arr[j], arr[j + 1]) - - if (arr[j] > arr[j + 1]) { - let temp = arr[j] - arr[j] = arr[j + 1] - arr[j + 1] = temp - arrayIsSorted = false - } - console.log(arr) - } - if (arrayIsSorted) break - } - return arr -} -// bubbleSort([37, 45, 29, 8, 12, 88, -3]) -// bubbleSort([8, 1, 2, 3, 4, 5, 6, 7]) - -/* - Selection Sort - Swapping, from biggest to smallest - + Simple, good for small data sets - - Not good for nearly sorted lists - Time Complexity Average: O(n^2) - */ -function selectionSort(arr) { - for (let i = 0; i < arr.length; i++) { - let lowest = i - for (let j = i + 1; j < arr.length; j++) { - if (arr[j] < arr[lowest]) { - lowest = j - } - } - if (i !== lowest) { - console.log('before', arr) - const temp = arr[i] - arr[i] = arr[lowest] - arr[lowest] = temp - console.log('after', arr) - } - } - return arr -} -//selectionSort([34, 22, 10, 19, 17]) - -/* - Insertion Sort - - Complex - + Good for nearly sorted arrays or where new data is continuously added - Time Complexity Average: O(n^2) -*/ -function insertionSort(arr) { - for (var i = 1; i < arr.length; i++) { - const current = arr[i] - for (var j = i - 1; j >= 0 && arr[j] > current; j--) { - arr[j + 1] = arr[j] - console.log(arr[j]) - } - arr[j + 1] = current - console.log(arr) - } - return arr -} -//insertionSort([2, 1, 9, 76, 4]) - -// 2. INTERMEDIATE SORTING ALGORITHMS - -// Helper function Merge -function merge(arr1, arr2) { - console.log('merge!') - const results = [] - let i = 0 - let j = 0 - - // Neither array is sorted - while (arr1.length > i && arr2.length > j) { - if (arr2[j] > arr1[i]) { - results.push(arr1[i]) - i++ - } else { - results.push(arr2[j]) - j++ - } - console.log(results, 'sorting both...') - } - - // Second array is sorted, continue with first - while (i < arr1.length) { - results.push(arr1[i]) - i++ - console.log(results, 'sorting first...') - } - - // First array is sorted, continue with second - while (j < arr2.length) { - results.push(arr2[j]) - j++ - console.log(results, 'sorting second...') - } - console.log(results, 'finished!') - return results -} -// merge([1, 10, 50], [2, 14, 99, 100]) - -/* - Merge Sort - recursive function - Time Complexity Average: O(n log n) -*/ -function mergeSort(arr) { - if (arr.length <= 1) return arr - - let mid = Math.floor(arr.length / 2) - let left = mergeSort(arr.slice(0, mid)) - let right = mergeSort(arr.slice(mid)) - - return merge(left, right) -} -// mergeSort([10, 24, 76, 73]) - -/* - Quick Sort with Pivot - recursive function - Time Complexity Average: O(n log n) -*/ - -function pivot(arr, start = 0, end = arr.length + 1) { - function swap(array, i, j) { - const temp = array[i] - array[i] = array[j] - array[j] = temp - } - - let pivot = arr[start] - let swapIdx = start - - for (let i = start + 1; i < arr.length; i++) { - if (pivot > arr[i]) { - swapIdx++ - swap(arr, swapIdx, i) - } - } - swap(arr, start, swapIdx) - return swapIdx -} - -function quickSort(arr, left = 0, right = arr.length - 1) { - if (left < right) { - let pivotIndex = pivot(arr, left, right) - quickSort(arr, left, pivotIndex - 1) - quickSort(arr, pivotIndex + 1, right) - } - return arr -} - -//console.log(quickSort([100, -3, 4, 6, 9, 1, 2, 5, 3])) - -// 3. EXPERT SORTING ALGORITHMS - -/* - Radix Sort - (Only works with Integers) - Time Complexity Average: O(nk), n = length of array, k = number of digits average -*/ - -// Helper function getDigit -function getDigit(num, i) { - return Math.floor(Math.abs(num) / Math.pow(10, i)) % 10 -} - -// Helper function digitCount -function digitCount(num) { - if (num === 0) return 1 - return Math.floor(Math.log10(Math.abs(num))) + 1 -} - -// Helper function mostDigits (implementing digitCount) -function mostDigits(numArray) { - let maxDigits = 0 - for (let i = 0; i < numArray.length; i++) { - maxDigits = Math.max(maxDigits, digitCount(numArray[i])) - } - return maxDigits -} - -function radixSort(numArray) { - let maxDigitCount = mostDigits(numArray) - for (let k = 0; k < maxDigitCount; k++) { - let digitBuckets = Array.from({ length: 10 }, () => []) - for (let i = 0; i < numArray.length; i++) { - let digit = getDigit(numArray[i], k) - digitBuckets[digit].push(numArray[i]) - } - numArray = [].concat(...digitBuckets) - console.log(numArray) - } - return numArray -} - -radixSort([23, 345, 5467, 12, 2345, 9852]) diff --git a/package.json b/package.json index 53d9dd5..7d04c4e 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,12 @@ { - "name": "javascript-algoritms", + "name": "javascript-algorithms", "version": "1.0.0", - "description": "Snippets for a bunch of useful JavaScript algorithms.", - "main": "index.js", + "description": "A collection of sorting-algorithms", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "files": [ + "/dist" + ], "type": "module", "scripts": { "test": "jest --watch" diff --git a/src/dataStructures/DoublyLinkedList.test.ts b/src/dataStructures/DoublyLinkedList.test.ts new file mode 100644 index 0000000..3c22394 --- /dev/null +++ b/src/dataStructures/DoublyLinkedList.test.ts @@ -0,0 +1,306 @@ +import DoublyLinkedList from "./DoublyLinkedList"; + +describe("DoublyLinkedList", () => { + describe("push", () => { + it("adds a Node to the end of the List", () => { + const list = new DoublyLinkedList(); + + list.push("value one"); + list.push("value two"); + expect(list.tail?.val).toEqual("value two"); + + list.push("value three"); + expect(list.tail?.val).toEqual("value three"); + }); + + it("adds Node when the List is previously empty", () => { + const list = new DoublyLinkedList(); + + list.push("value one"); + expect(list.head?.val).toEqual("value one"); + expect(list.tail?.val).toEqual("value one"); + }); + + it("increments length by one", () => { + const list = new DoublyLinkedList(); + expect(list.length).toEqual(0); + + list.push("value one"); + expect(list.length).toEqual(1); + + list.push("value two"); + expect(list.length).toEqual(2); + }); + }); + + describe("pop", () => { + it("removes Node at end of the List", () => { + const list = new DoublyLinkedList(); + + list.push("value one"); + expect(list.tail?.val).toEqual("value one"); + + list.pop(); + expect(list.tail).toEqual(null); + }); + + it("returns undefined if List is empty", () => { + const list = new DoublyLinkedList(); + expect(list.head).toEqual(null); + + const result = list.pop(); + expect(result).toEqual(undefined); + }); + + it("decrements length by one", () => { + const list = new DoublyLinkedList(); + + list.push("value one"); + expect(list.length).toEqual(1); + + list.pop(); + expect(list.length).toEqual(0); + }); + + it("returns the removed Node", () => { + const list = new DoublyLinkedList(); + + list.push("value one"); + list.push("value two"); + + const result = list.pop(); + expect(result).toEqual({ + next: null, + prev: null, + val: "value two", + }); + }); + }); + + describe("shift", () => { + it("removes Node at beginning of the List", () => { + const list = new DoublyLinkedList(); + list.push("value one"); + list.push("value two"); + expect(list.head?.val).toEqual("value one"); + + list.shift(); + expect(list.head?.val).toEqual("value two"); + }); + + it("returns undefined if List is empty", () => { + const list = new DoublyLinkedList(); + const result = list.shift(); + expect(result).toEqual(undefined); + }); + + it("decrements length by one", () => { + const list = new DoublyLinkedList(); + list.push("value one"); + expect(list.length).toEqual(1); + + list.shift(); + expect(list.length).toEqual(0); + }); + + it("returns the removed Node", () => { + const list = new DoublyLinkedList(); + list.push("value one"); + + const result = list.shift(); + expect(result).toEqual({ next: null, val: "value one" }); + }); + }); + + describe("unshift", () => { + it("adds a Node at the beginning of the List", () => { + const list = new DoublyLinkedList(); + list.push("value one"); + expect(list.head?.val).toEqual("value one"); + + list.unshift("value two"); + expect(list.head?.val).toEqual("value two"); + }); + + it("adds a Node when the List is previously empty", () => { + const list = new DoublyLinkedList(); + + list.unshift("value one"); + expect(list.head?.val).toEqual("value one"); + expect(list.tail?.val).toEqual("value one"); + }); + + it("increments length by one", () => { + const list = new DoublyLinkedList(); + expect(list.length).toEqual(0); + + list.unshift("value one"); + expect(list.length).toEqual(1); + }); + }); + + describe("get", () => { + it("returns the Node at place n", () => { + const list = new DoublyLinkedList(); + list.push("value one"); + list.push("value two"); + list.push("value three"); + + const result = list.get(1); + expect(result?.val).toEqual("value two"); + }); + + it("returns undefined if n does not exist", () => { + const list = new DoublyLinkedList(); + expect(list.get(0)).toEqual(undefined); + expect(list.get(-1)).toEqual(undefined); + }); + + it("returns Node at the first half of of the List", () => { + const list = new DoublyLinkedList(); + list.push("value one"); + list.push("value two"); + list.push("value three"); + + const result = list.get(0); + expect(result?.val).toEqual("value one"); + }); + + it("returns Node at the second half of of the List", () => { + const list = new DoublyLinkedList(); + list.push("value one"); + list.push("value two"); + list.push("value three"); + + const result = list.get(2); + expect(result?.val).toEqual("value three"); + }); + }); + + describe("set", () => { + it("updates the value at place n", () => { + const list = new DoublyLinkedList(); + list.push("value one"); + list.push("value two"); + list.push("value three"); + + list.set(2, "new value"); + expect(list.tail?.val).toEqual("new value"); + }); + + it("returns false if no value exist at place n", () => { + const list = new DoublyLinkedList(); + expect(list.set(0, "new value")).toEqual(false); + }); + }); + + describe("insert", () => { + it("inserts value into the List", () => { + const list = new DoublyLinkedList(); + list.push("value one"); + list.push("value two"); + list.push("value three"); + + list.insert(2, "new value"); + const result = list.get(2); + + expect(result?.val).toEqual("new value"); + }); + + it("inserts value at beginning of List", () => { + const list = new DoublyLinkedList(); + list.push("value one"); + list.push("value two"); + expect(list.head?.val).toEqual("value one"); + + list.insert(0, "new value"); + expect(list.head?.val).toEqual("new value"); + }); + + it("inserts value at the end of the List", () => { + const list = new DoublyLinkedList(); + list.push("value one"); + list.push("value two"); + expect(list.tail?.val).toEqual("value two"); + + list.insert(2, "new value"); + expect(list.tail?.val).toEqual("new value"); + }); + + it("increments length by one", () => { + const list = new DoublyLinkedList(); + expect(list.length).toEqual(0); + + list.insert(0, "new value"); + expect(list.length).toEqual(1); + }); + + it("returns false if n is outside the range of the List", () => { + const list = new DoublyLinkedList(); + + const resultNegativeNumber = list.insert(-1, "new value"); + expect(resultNegativeNumber).toEqual(false); + + const resultOutOfRange = list.insert(1, "new value"); + expect(resultOutOfRange).toEqual(false); + }); + + it("returns true when it has added a value", () => { + const list = new DoublyLinkedList(); + expect(list.insert(0, "new value")).toEqual(true); + }); + }); + + describe("remove", () => { + it("returns undefined if n is outside the range of the List", () => { + const list = new DoublyLinkedList(); + expect(list.remove(-1)).toEqual(undefined); + expect(list.remove(0)).toEqual(undefined); + }); + + it("removes Node from inside the List", () => { + const list = new DoublyLinkedList(); + list.push("value one"); + list.push("value two"); + list.push("value three"); + + expect(list.get(1)?.val).toEqual("value two"); + expect(list.remove(1)?.val).toEqual("value two"); + }); + it("removes Node from the beginning of the List", () => { + const list = new DoublyLinkedList(); + + list.push("value one"); + list.push("value two"); + list.push("value three"); + expect(list.head?.val).toEqual("value one"); + + list.remove(0); + expect(list.head?.val).toEqual("value two"); + }); + + it("removes Node from the end of the List", () => { + const list = new DoublyLinkedList(); + + list.push("value one"); + list.push("value two"); + list.push("value three"); + expect(list.tail?.val).toEqual("value three"); + + list.remove(2); + expect(list.tail?.val).toEqual("value two"); + }); + + it("decrements the length by one", () => { + const list = new DoublyLinkedList(); + + list.push("value one"); + list.push("value two"); + list.push("value three"); + expect(list.length).toEqual(3); + + list.remove(1); + expect(list.length).toEqual(2); + }); + }); +}); diff --git a/src/dataStructures/DoublyLinkedList.ts b/src/dataStructures/DoublyLinkedList.ts new file mode 100644 index 0000000..704402a --- /dev/null +++ b/src/dataStructures/DoublyLinkedList.ts @@ -0,0 +1,188 @@ +import Node from "./Node"; + +/* + * Same as SinglyLinked List but with the benefit of links to + * previous Node + * Takes up more memory. + * Finding a Node can in best case be done in half the time of + * a Singly Linked List. + * + * Used for things like Browser History. + */ +class DoublyLinkedList { + head: Node | null; + tail: Node | null; + length: number; + + constructor() { + this.head = null; + this.tail = null; + this.length = 0; + } + + push(val: any): DoublyLinkedList { + const newNode = new Node(val); + + if (this.length === 0) { + this.head = newNode; + this.tail = newNode; + } else { + if (this.tail) { + this.tail.next = newNode; + } + newNode.prev = this.tail; + this.tail = newNode; + } + + this.length++; + + return this; + } + + pop(): Node | null | undefined { + if (this.length === 0) return undefined; + + const tail = this.tail; + + if (this.length === 1) { + this.head = null; + this.tail = null; + } else { + if (this.tail) + if (tail && tail.prev) { + this.tail = tail?.prev; + this.tail.next = null; + tail.prev = null; + } + } + + this.length--; + + return tail; + } + + shift(): Node | undefined { + if (this.head === null) return undefined; + + let oldHead = this.head; + + if (this.length === 1) { + this.head = null; + this.tail = null; + } else { + this.head = oldHead.next; + if (this.head) { + this.head.prev = null; + } + oldHead.next = null; + } + + this.length--; + + return oldHead; + } + + unshift(val: any): DoublyLinkedList { + const newNode = new Node(val); + + if (this.head === null) { + this.head = newNode; + this.tail = newNode; + } else { + this.head.prev = newNode; + newNode.next = this.head; + this.head = newNode; + } + + this.length++; + + return this; + } + + get(n: number): Node | undefined | null { + if (n < 0 || n >= this.length || this.head === null || this.tail === null) + return undefined; + + let count: number; + let current: Node | null | undefined; + + const isInFirstHalf = n <= this.length / 2; + + if (isInFirstHalf) { + // Loop forward + count = 0; + current = this.head; + while (count !== n) { + current = current?.next; + count++; + } + } else { + // Loop backward + count = this.length - 1; + current = this.tail; + while (count !== n) { + current = current?.prev; + count--; + } + } + return current; + } + + set(n: number, val: any): boolean { + const foundNode = this.get(n); + + if (foundNode !== null && foundNode !== undefined) { + foundNode.val = val; + return true; + } + + return false; + } + + insert(n: number, val: any): boolean { + if (n < 0 || n > this.length) return false; + if (n === 0) return Boolean(this.unshift(val)); + if (n === this.length) return Boolean(this.push(val)); + + const newNode = new Node(val); + const prevNode = this.get(n - 1); + const nextNode = prevNode?.next; + + if (prevNode) { + prevNode.next = newNode; + newNode.prev = prevNode; + } + + if (nextNode !== undefined && nextNode !== null) { + newNode.next = nextNode; + nextNode.prev = newNode; + } + + this.length++; + + return true; + } + + remove(n: number): Node | null | undefined { + if (n < 0 || n > this.length) return undefined; + if (n === 0) return this.shift(); + if (n === this.length - 1) return this.pop(); + + const foundNode = this.get(n); + const prevNode = foundNode?.prev; + const nextNode = foundNode?.next; + + if (foundNode !== null && foundNode !== undefined) { + if (prevNode && nextNode) { + prevNode.next = nextNode; + nextNode.prev = prevNode; + } + foundNode.next = null; + foundNode.prev = null; + this.length--; + return foundNode; + } + } +} + +export default DoublyLinkedList; diff --git a/src/dataStructures/Node.ts b/src/dataStructures/Node.ts new file mode 100644 index 0000000..83d8f18 --- /dev/null +++ b/src/dataStructures/Node.ts @@ -0,0 +1,10 @@ +export default class Node { + val: any; + next: Node | null; + prev?: Node | null; + + constructor(val: any) { + this.val = val; + this.next = null; + } +} diff --git a/src/dataStructures/Queue.test.ts b/src/dataStructures/Queue.test.ts new file mode 100644 index 0000000..9d5da6b --- /dev/null +++ b/src/dataStructures/Queue.test.ts @@ -0,0 +1,60 @@ +import Queue from "./Queue"; + +describe("Queue", () => { + describe("enqueue", () => { + it("adds to the end of the Queue", () => { + const queue = new Queue(); + queue.enqueue("value one"); + queue.enqueue("value two"); + expect(queue.last?.val).toEqual("value two"); + + queue.enqueue("value three"); + expect(queue.last?.val).toEqual("value three"); + }); + + it("increments the size of the Queue by one", () => { + const queue = new Queue(); + expect(queue.size).toEqual(0); + + queue.enqueue("value one"); + expect(queue.size).toEqual(1); + }); + + it("returns the new Queue Size", () => { + const queue = new Queue(); + const returnedValue = queue.enqueue("value one"); + expect(returnedValue).toEqual(1); + }); + }); + + describe("dequeue", () => { + it("removes from the start of the queue", () => { + const queue = new Queue(); + queue.enqueue("value one"); + queue.enqueue("value two"); + expect(queue.first?.val).toEqual("value one"); + + queue.dequeue(); + expect(queue.first?.val).toEqual("value two"); + }); + + it("decrements the size of the Queue by one", () => { + const queue = new Queue(); + queue.enqueue("value one"); + queue.enqueue("value two"); + expect(queue.size).toEqual(2); + + queue.dequeue(); + expect(queue.size).toEqual(1); + }); + + it("returns the value of the Node removed", () => { + const queue = new Queue(); + queue.enqueue("value one"); + queue.enqueue("value two"); + + const returnedValue = queue.dequeue(); + expect(returnedValue).toEqual("value one"); + }); + }); +}); diff --git a/src/dataStructures/Queue.ts b/src/dataStructures/Queue.ts new file mode 100644 index 0000000..51465d2 --- /dev/null +++ b/src/dataStructures/Queue.ts @@ -0,0 +1,54 @@ +import Node from "./Node"; + +/* + * Queue + * + * Similar to a Stack, optimized for adding and removing values. + * Follows the principle of "first in, first out". + * + * Examples: + * - Players waiting for access to a game server. + */ + +export default class Queue { + first: null | Node; + last: null | Node; + size: number; + + constructor() { + this.first = null; + this.last = null; + this.size = 0; + } + + enqueue(val: any): number { + const newNode = new Node(val); + + if (this.size === 0) { + this.first = newNode; + this.last = newNode; + } else { + if (this.last) { + this.last.next = newNode; + this.last = newNode; + } + } + return ++this.size; + } + + dequeue(): any { + if (this.size === 0) return null; + + const tmp = this.first; + + if (this.first === this.last) { + this.last = null; + } + + if (this.first) { + this.first = this.first.next; + } + this.size--; + return tmp?.val; + } +} diff --git a/src/dataStructures/README.md b/src/dataStructures/README.md new file mode 100644 index 0000000..43422e5 --- /dev/null +++ b/src/dataStructures/README.md @@ -0,0 +1,59 @@ +# Data Structures + +JavaScript doesn't inherently have support for several of the type of lists that other languages support. This is a list of such data structures implemented via JavaScript class-syntax (with Typescript). + +## Singly Linked List + +List of Nodes with references to next only. + +- Good for insertion in the entire List and removal at the beginning of the list +- Bad for searching and access + +Performance: + +- Insertion - O(1) +- Removal - Removing first O(1) otherwise O(n) +- Searching - O(n) +- Access - O(n) + +## Doubly Linked List + +List of Nodes with references to previous and next. + +- Good for insertion and removing in the entire list +- Bad for searching and access + +Performance: + +- Insertion - O(1) +- Removal - O(1) +- Searching - O(n) +- Access - O(n) + +## Stack + +Adds to start, removes from start + +- Good for insertion and removal +- Bad for searching and access + +Performance: + +- Insertion - O(1) +- Removal - O(1) +- Searching - O(n) +- Access - O(n) + +## Queue + +Adds to end, removes from start + +- Good for insertion and removal +- Bad for searching and access + +Performance: + +- Insertion - O(1) +- Removal - O(1) +- Searching - O(n) +- Access - O(n) diff --git a/src/dataStructures/SinglyLinkedList.test.ts b/src/dataStructures/SinglyLinkedList.test.ts new file mode 100644 index 0000000..99ab910 --- /dev/null +++ b/src/dataStructures/SinglyLinkedList.test.ts @@ -0,0 +1,332 @@ +import SinglyLinkedList from "./SinglyLinkedList"; + +describe("SinglyLinkedList", () => { + describe("push", () => { + it("takes a value and adds to the end of the List", () => { + const list = new SinglyLinkedList(); + const val = "hello"; + list.push(val); + + expect(list.tail?.val).toEqual(val); + }); + + it("if the value is the first added, then both head and tail will be the same Node", () => { + const list = new SinglyLinkedList(); + const val = "hello"; + list.push(val); + + expect(list.head?.val).toEqual(val); + expect(list.tail?.val).toEqual(val); + }); + + it("if the value is NOT the first value added, then the head and tail will NOT be the same Node", () => { + const list = new SinglyLinkedList(); + const val1 = "hello"; + const val2 = "people"; + + list.push(val1); + list.push(val2); + + expect(list.head?.val).toEqual(val1); + expect(list.tail?.val).toEqual(val2); + }); + + it("increments the length by one", () => { + const list = new SinglyLinkedList(); + + expect(list.length).toEqual(0); + + list.push("value one"); + + expect(list.length).toEqual(1); + }); + }); + + describe("pop", () => { + it("returns the last Node from the List", () => { + const list = new SinglyLinkedList(); + list.push("value one"); + list.push("value two"); + list.push("value three"); + + const value = list.pop(); + expect(value).toEqual({ next: null, val: "value three" }); + }); + + it("removes the last Node from the List", () => { + const list = new SinglyLinkedList(); + list.push("value one"); + list.push("value two"); + list.push("value three"); + + expect(list.head?.val).toEqual("value one"); + expect(list.tail?.val).toEqual("value three"); + + list.pop(); + + expect(list.head?.val).toEqual("value one"); + expect(list.tail?.val).toEqual("value two"); + }); + + it("decrements the length by one", () => { + const list = new SinglyLinkedList(); + list.push("value one"); + list.push("value two"); + list.push("value three"); + + expect(list.length).toEqual(3); + + list.pop(); + + expect(list.length).toEqual(2); + }); + + it("handles when there is only one Node left", () => { + const list = new SinglyLinkedList(); + list.push("value one"); + + const value = list.pop(); + + expect(value).toEqual({ next: null, val: "value one" }); + expect(list.head?.val).toEqual(undefined); + expect(list.tail?.val).toEqual(undefined); + expect(list.length).toEqual(0); + }); + }); + + describe("shift", () => { + it("returns the first Node from the List", () => { + const list = new SinglyLinkedList(); + list.push("value one"); + + const value = list.shift(); + expect(value).toEqual({ next: null, val: "value one" }); + }); + + it("removes the first Node from the List", () => { + const list = new SinglyLinkedList(); + list.push("value one"); + + list.shift(); + }); + + it("decrements the length by one", () => { + const list = new SinglyLinkedList(); + list.push("value one"); + + expect(list.length).toEqual(1); + + list.shift(); + + expect(list.length).toEqual(0); + }); + + it("returns undefined if there is no Node", () => {}); + }); + + describe("unshift", () => { + it("adds a new Node with the value given at the start of the List", () => { + const list = new SinglyLinkedList(); + list.push("value one"); + list.unshift("value two"); + expect(list.head?.val).toEqual("value two"); + expect(list.tail?.val).toEqual("value one"); + }); + + it("sets head and tail to new Node if there are no previous Nodes", () => { + const list = new SinglyLinkedList(); + list.unshift("value two"); + expect(list.head?.val).toEqual("value two"); + expect(list.head?.next).toEqual(null); + expect(list.tail?.val).toEqual("value two"); + expect(list.tail?.next).toEqual(null); + }); + + it("increments the length by one", () => { + const list = new SinglyLinkedList(); + expect(list.length).toEqual(0); + list.unshift("value one"); + expect(list.length).toEqual(1); + }); + }); + + describe("get", () => { + it("returns the (n)th item in the List", () => { + const list = new SinglyLinkedList(); + list.push("value one"); + list.push("value two"); + list.push("value three"); + list.push("value four"); + list.push("value five"); + list.push("value six"); + list.push("value seven"); + list.push("value eight"); + + const item = list.get(5); + expect(item?.val).toEqual("value six"); + }); + + it("returns undefined if there is no item", () => { + const list = new SinglyLinkedList(); + const item = list.get(1); + expect(item).toEqual(undefined); + }); + }); + + describe("set", () => { + it("updates the value of item", () => { + const list = new SinglyLinkedList(); + list.push("value one"); + list.set(0, "new value"); + expect(list.head?.val).toEqual("new value"); + }); + + it("returns true if value is updated", () => { + const list = new SinglyLinkedList(); + list.push("value one"); + const result = list.set(0, "new value"); + expect(result).toEqual(true); + }); + + it("returns false if there is no item to update", () => { + const list = new SinglyLinkedList(); + const item = list.set(1, "new value"); + expect(item).toEqual(false); + }); + }); + + describe("insert", () => { + it("returns false if n is less than zero", () => { + const list = new SinglyLinkedList(); + expect(list.insert(-1, "value")).toEqual(false); + }); + + it("returns false if n is greater than length", () => { + const list = new SinglyLinkedList(); + expect(list.insert(1, "value")).toEqual(false); + }); + + it("unshift a new Node to the start of the List if n is 0", () => { + const list = new SinglyLinkedList(); + list.push("value one"); + list.push("value two"); + list.push("value three"); + list.push("value four"); + list.insert(0, "new value"); + expect(list.head?.val).toEqual("new value"); + }); + + it("pushes a new Node to the end of the List if n same as length", () => { + const list = new SinglyLinkedList(); + list.push("value one"); + list.push("value two"); + list.push("value three"); + list.push("value four"); + list.insert(4, "new value"); + expect(list.tail?.val).toEqual("new value"); + }); + + it("increments the length by one", () => { + const list = new SinglyLinkedList(); + expect(list.length).toEqual(0); + list.insert(0, "new value"); + expect(list.length).toEqual(1); + }); + + it("returns true if insertion is successful", () => { + const list = new SinglyLinkedList(); + expect(list.insert(0, "new value")).toEqual(true); + }); + }); + + describe("remove", () => { + it("returns undefined if n is less than zero", () => { + const list = new SinglyLinkedList(); + expect(list.remove(-1)).toEqual(undefined); + }); + + it("returns undefined if n is greater than length", () => { + const list = new SinglyLinkedList(); + expect(list.remove(0)).toEqual(undefined); + }); + + it("returns first item", () => { + const list = new SinglyLinkedList(); + list.push("value one"); + list.push("value two"); + list.push("value three"); + list.push("value four"); + expect(list.remove(0)?.val).toEqual("value one"); + }); + + it("returns last item", () => { + const list = new SinglyLinkedList(); + list.push("value one"); + list.push("value two"); + list.push("value three"); + list.push("value four"); + expect(list.remove(3)).toEqual({ next: null, val: "value four" }); + }); + + it("returns item in list that is not first or last", () => { + const list = new SinglyLinkedList(); + list.push("value one"); + list.push("value two"); + list.push("value three"); + list.push("value four"); + expect(list.remove(2)?.val).toEqual("value three"); + }); + }); + + describe("reverse", () => { + it("returns the list in reversed order", () => { + const list = new SinglyLinkedList(); + list.push("value one"); + list.push("value two"); + list.push("value three"); + list.push("value four"); + + const listOriginal = { + head: { + next: { + next: { + next: { next: null, val: "value four" }, + val: "value three", + }, + val: "value two", + }, + val: "value one", + }, + length: 4, + tail: { next: null, val: "value four" }, + }; + + expect(list).toEqual(listOriginal); + + list.reverse(); + + const reversedList = { + head: { + next: { + next: { + next: { + next: null, + val: "value one", + }, + val: "value two", + }, + val: "value three", + }, + val: "value four", + }, + length: 4, + tail: { + next: null, + val: "value one", + }, + }; + + expect(list).toEqual(reversedList); + }); + }); +}); diff --git a/src/dataStructures/SinglyLinkedList.ts b/src/dataStructures/SinglyLinkedList.ts new file mode 100644 index 0000000..d58cbb1 --- /dev/null +++ b/src/dataStructures/SinglyLinkedList.ts @@ -0,0 +1,179 @@ +import Node from "./Node"; + +/* + * Data structure of nodes that contains a head, tail + * and length property. + * + * Each node has value and a next pointer to another node, or null. + * + * Unlike an array: + * - It does not have an index + * - Each node is only linked to the next node + * - Random access is not allowed + * + * Good for fast insertion and deletion of data. + * Bad for accessing data. + */ +export default class SinglyLinkedList { + head: Node | null; + tail: Node | null; + length: number; + + constructor() { + this.head = null; + this.tail = null; + this.length = 0; + } + + push(val: any): SinglyLinkedList { + const newNode = new Node(val); + + if (!this.head) { + this.head = newNode; + this.tail = this.head; + } else { + if (this.tail !== null) { + this.tail.next = newNode; + this.tail = newNode; + } + } + + this.length++; + return this; + } + + pop(): Node | undefined { + if (!this.head) return undefined; + + let current = this.head; + let newTail = current; + + while (current.next) { + newTail = current; + current = current.next; + } + + this.tail = newTail; + this.tail.next = null; + this.length--; + + if (this.length === 0) { + this.head = null; + this.tail = null; + } + + return current; + } + + shift(): Node | undefined { + if (!this.head) return undefined; + + let removedHead = this.head; + this.head = removedHead.next; + this.length--; + + if (this.length === 0) { + this.tail = null; + } + + return removedHead; + } + + unshift(val: any): SinglyLinkedList { + const newNode = new Node(val); + + if (!this.head) { + this.head = newNode; + this.tail = this.head; + } else { + newNode.next = this.head; + this.head = newNode; + } + + this.length++; + + return this; + } + + get(n: number): Node | undefined | null { + if (n < 0 || n >= this.length) return undefined; + + let i = 0; + let current: Node | undefined | null = this.head; + + while (i !== n) { + current = current?.next; + i++; + } + + return current; + } + + set(n: number, val: any): boolean { + const foundNode = this.get(n); + + if (foundNode !== null && foundNode !== undefined) { + foundNode.val = val; + return true; + } + return false; + } + + insert(n: number, val: any): boolean { + if (n < 0 || n > this.length) return false; + if (n === 0) return Boolean(this.unshift(val)); + if (n === this.length) return Boolean(this.push(val)); + + const prevNode = this.get(n - 1); + + if (prevNode) { + const newNode = new Node(val); + const temp = prevNode?.next; + prevNode.next = newNode; + newNode.next = temp; + this.length++; + return true; + } + + return false; + } + + remove(n: number): Node | undefined { + if (n < 0 || n >= this.length) return undefined; + if (n === 0) return this.shift(); + if (n === this.length - 1) return this.pop(); + + const prevNode = this.get(n - 1); + + if (prevNode && prevNode.next) { + const removed = prevNode?.next; + prevNode.next = removed?.next; + this.length--; + return removed; + } + } + + reverse(): SinglyLinkedList { + // swap head and tail + let node = this.head; + this.head = this.tail; + this.tail = node; + + let prev = null; + let next = null; + + // swap all nodes + for (let i = 0; i < this.length; i++) { + next = node?.next; + if (node) { + node.next = prev; + prev = node; + } + if (next) { + node = next; + } + } + + return this; + } +} diff --git a/src/dataStructures/Stack.test.ts b/src/dataStructures/Stack.test.ts new file mode 100644 index 0000000..ab5f55d --- /dev/null +++ b/src/dataStructures/Stack.test.ts @@ -0,0 +1,76 @@ +import Stack from "./Stack"; + +describe("Stack", () => { + describe("push", () => { + it("adds to the beginning of the Stack", () => { + const stack = new Stack(); + + stack.push("value one"); + expect(stack.first?.val).toEqual("value one"); + + stack.push("value two"); + expect(stack.first?.val).toEqual("value two"); + expect(stack.last?.val).toEqual("value one"); + }); + + it("increments Stack Size by one", () => { + const stack = new Stack(); + expect(stack.size).toEqual(0); + + stack.push("value one"); + expect(stack.size).toEqual(1); + }); + + it("returns value of new Stack Size", () => { + const stack = new Stack(); + expect(stack.size).toEqual(0); + + const returnedValue = stack.push("value one"); + expect(returnedValue).toEqual(1); + }); + }); + + describe("pop", () => { + it("returns null of Stack is empty", () => { + const stack = new Stack(); + + const returnedValue = stack.pop(); + expect(returnedValue).toEqual(null); + }); + + it("returns the lastly added value", () => { + const stack = new Stack(); + + stack.push("value one"); + stack.push("value two"); + stack.push("value three"); + + const returnedValue = stack.pop(); + expect(returnedValue).toEqual("value three"); + }); + + it("removes the lastly added value from the Stack", () => { + const stack = new Stack(); + + stack.push("value one"); + stack.push("value two"); + stack.push("value three"); + expect(stack.first?.val).toEqual("value three"); + + stack.pop(); + expect(stack.first?.val).toEqual("value two"); + }); + + it("decrements Stack Size by one", () => { + const stack = new Stack(); + + stack.push("value one"); + stack.push("value two"); + stack.push("value three"); + expect(stack.size).toEqual(3); + + stack.pop(); + expect(stack.size).toEqual(2); + }); + }); +}); diff --git a/src/dataStructures/Stack.ts b/src/dataStructures/Stack.ts new file mode 100644 index 0000000..17b2c70 --- /dev/null +++ b/src/dataStructures/Stack.ts @@ -0,0 +1,56 @@ +import Node from "./Node"; + +/* + * Stack + * + * Last added is first removed. + * + * Examples: + * - The Call Stack in JavaScript + * - Undo/Redo functionality + * - Routing History + */ + +export default class Stack { + first: null | Node; + last: null | Node; + size: number; + + constructor() { + this.first = null; + this.last = null; + this.size = 0; + } + + push(val: any): number { + const newNode = new Node(val); + if (!this.first) { + this.first = newNode; + this.last = newNode; + } else { + const tmp = this.first; + this.first = newNode; + this.first.next = tmp; + } + + return ++this.size; + } + + pop(): null | any { + if (this.size === 0) return null; + + const tmp = this.first; + + if (this.first === this.last) { + this.last = null; + } + + if (this.first) { + this.first = this.first?.next; + } + + this.size--; + + return tmp?.val; + } +} diff --git a/src/dataStructures/index.ts b/src/dataStructures/index.ts new file mode 100644 index 0000000..2682319 --- /dev/null +++ b/src/dataStructures/index.ts @@ -0,0 +1,13 @@ +import SinglyLinkedList from "./SinglyLinkedList"; +import DoublyLinkedList from "./DoublyLinkedList"; +import Stack from "./Stack"; +import Queue from "./Queue"; + +const dataStructures = { + SinglyLinkedList, + DoublyLinkedList, + Stack, + Queue, +}; + +export default dataStructures; diff --git a/index.ts b/src/index.ts similarity index 90% rename from index.ts rename to src/index.ts index 52697ba..97b0f77 100644 --- a/index.ts +++ b/src/index.ts @@ -1,4 +1,7 @@ -import sorters from "./src/sorters"; +import sorters from "./sorters/index"; +import dataStructures from "./dataStructures/index"; + +export default { sorters, dataStructures }; /* Naive String Search @@ -125,24 +128,3 @@ function maxSubarraySumSlidingWindow(arr: number[], num: number) { } // console.log(maxSubarraySumSlidingWindow([2, 6, 9, 2, 1, 8, 5, 6, 3], 3)) - -const finders = { - naiveSearch, -}; - -const counters = { - maxSubarraySum, - maxSubarraySumSlidingWindow, - countUniqueValues, -}; - -const misc = { - validAnagram, -}; - -module.exports = { - sorters, - finders, - counters, - misc, -}; diff --git a/src/sorters/README.md b/src/sorters/README.md new file mode 100644 index 0000000..9cb5d91 --- /dev/null +++ b/src/sorters/README.md @@ -0,0 +1,58 @@ +# Sorting Algorithms + +There are a bunch of different kind of sorting algorithms. +This library does not contain all of them. Some of them are +generally faster (quicksort) and some are generally slower (bubble sort) and some perform better under certain conditions. + +The sorting algorithms are sorted by their type. Sometimes +there are more than one implementation. + +[Read more on Wikipedia](https://en.wikipedia.org/wiki/Sorting_algorithm) + +| bubbleSort() | | +| -------------------- | ------------------------------------------- | +| _Type_ | Loop in loop | +| _How it works_ | Swapping, sorting from smallest to largest. | +| _Positive_ | Simple, good for small data sets. | +| _Negative_ | Inefficient for large data sets. | +| _Efficiency Average_ | O(n^2) | + +| selectionSort() | | +| -------------------- | ------------------------------------------- | +| _Type_ | Loop in loop | +| _How it works_ | Swapping, sorting from largest to smallest. | +| _Positive_ | Simple, good for small data sets. | +| _Negative_ | Inefficient for large data sets. | +| _Efficiency Average_ | O(n^2) | + +| insertionSort() | | +| -------------------- | ------------------------------------------------------------------------------------------------- | +| _Type_ | Loop in loop | +| _How it works_ | Sorting into new array. | +| _Positive_ | Simple, good for small data sets and nearly sorted data, or when new data is addded continuously. | +| _Negative_ | Inefficient for large data sets. | +| _Efficiency Average_ | O(n^2) | + +| mergeSort() | | +| -------------------- | --------------------------------------------------------------------------------------------------------------------------------- | +| _Type_ | Recursive with loop | +| _How it works_ | Splits arrays until each array has only one item (= is sorted). Then merge and sort each array until there's only one array left. | +| _Positive_ | More complex, good for large data sets | +| _Negative_ | | +| _Efficiency Average_ | O(n log n) | + +| quickSort() | | +| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| _Type_ | Recursive with loop | +| _How it works_ | Sets pivot on the mean value, puts smaller numbers left of pivot and larger right of pivot. Then sorts left half and right half of increasingly smaller sizes, until its done. | +| _Positive_ | | +| _Negative_ | With too large data sets you might run out of call stacks. | +| _Efficiency Average_ | O(n log n) | + +| radixSort() | | +| -------------------- | --------------------------------------------------------------------------------------------------------- | +| _Type_ | Loop in loop | +| _How it works_ | Sorts an array of integers by loop-sorting them in buckets by number and then putting them back together. | +| _Positive_ | Fast. | +| _Negative_ | Only works on integers. | +| _Efficiency Average_ | O(n k) | diff --git a/tsconfig.json b/tsconfig.json index 36093d7..ac02ab9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,17 +4,17 @@ /* Basic Options */ // "incremental": true, /* Enable incremental compilation */ - "target": "ES2020" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, - "module": "es2020" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, + "target": "es2015" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, + "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, // "lib": [], /* Specify library files to be included in the compilation. */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ - // "declaration": true, /* Generates corresponding '.d.ts' file. */ + "declaration": true /* Generates corresponding '.d.ts' file. */, // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ // "sourceMap": true, /* Generates corresponding '.map' file. */ // "outFile": "./", /* Concatenate and emit output to single file. */ - "outDir": "./build" /* Redirect output structure to the directory. */, + "outDir": "./dist" /* Redirect output structure to the directory. */, // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ // "composite": true, /* Enable project compilation */ // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */