From c166c6571951240701544f43a9078553ae722357 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanna=20S=C3=B6derstr=C3=B6m?= Date: Sun, 4 Apr 2021 16:06:10 +0200 Subject: [PATCH 01/17] feat: add SinglyLinkedList class --- src/dataStructures/SinglyLinkedList.test.ts | 241 ++++++++++++++++++++ src/dataStructures/SinglyLinkedList.ts | 158 +++++++++++++ 2 files changed, 399 insertions(+) create mode 100644 src/dataStructures/SinglyLinkedList.test.ts create mode 100644 src/dataStructures/SinglyLinkedList.ts diff --git a/src/dataStructures/SinglyLinkedList.test.ts b/src/dataStructures/SinglyLinkedList.test.ts new file mode 100644 index 0000000..910e158 --- /dev/null +++ b/src/dataStructures/SinglyLinkedList.test.ts @@ -0,0 +1,241 @@ +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); + }); + }); +}); diff --git a/src/dataStructures/SinglyLinkedList.ts b/src/dataStructures/SinglyLinkedList.ts new file mode 100644 index 0000000..c02065f --- /dev/null +++ b/src/dataStructures/SinglyLinkedList.ts @@ -0,0 +1,158 @@ +/* + * 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 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) { + 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 prev = this.get(n - 1); + + if (prev) { + const newNode = new Node(val); + const temp = prev?.next; + prev.next = newNode; + newNode.next = temp; + this.length++; + return true; + } + + return false; + } +} + +class Node { + val: any; + next: Node | null; + + constructor(val: any) { + this.val = val; + this.next = null; + } +} From f25054bce8d46ac57352f42be27760181b5ad8bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanna=20S=C3=B6derstr=C3=B6m?= Date: Wed, 7 Apr 2021 19:34:20 +0200 Subject: [PATCH 02/17] feat: add SinglyLinkedList --- src/dataStructures/SinglyLinkedList.test.ts | 91 +++++++++++++++++++++ src/dataStructures/SinglyLinkedList.ts | 71 +++++++++++----- 2 files changed, 141 insertions(+), 21 deletions(-) diff --git a/src/dataStructures/SinglyLinkedList.test.ts b/src/dataStructures/SinglyLinkedList.test.ts index 910e158..e5122c8 100644 --- a/src/dataStructures/SinglyLinkedList.test.ts +++ b/src/dataStructures/SinglyLinkedList.test.ts @@ -238,4 +238,95 @@ describe("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 index c02065f..cd6a71b 100644 --- a/src/dataStructures/SinglyLinkedList.ts +++ b/src/dataStructures/SinglyLinkedList.ts @@ -25,6 +25,7 @@ export class SinglyLinkedList { push(val: any): SinglyLinkedList { const newNode = new Node(val); + if (!this.head) { this.head = newNode; this.tail = this.head; @@ -40,9 +41,7 @@ export class SinglyLinkedList { } pop(): Node | undefined { - if (!this.head) { - return undefined; - } + if (!this.head) return undefined; let current = this.head; let newTail = current; @@ -65,9 +64,7 @@ export class SinglyLinkedList { } shift(): Node | undefined { - if (!this.head) { - return undefined; - } + if (!this.head) return undefined; let removedHead = this.head; this.head = removedHead.next; @@ -112,6 +109,7 @@ export class SinglyLinkedList { set(n: number, val: any): boolean { const foundNode = this.get(n); + if (foundNode) { foundNode.val = val; return true; @@ -120,24 +118,16 @@ export class SinglyLinkedList { } insert(n: number, val: any): boolean { - if (n < 0 || n > this.length) { - return false; - } - - if (n === 0) { - return Boolean(this.unshift(val)); - } + 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)); - if (n === this.length) { - return Boolean(this.push(val)); - } - - const prev = this.get(n - 1); + const prevNode = this.get(n - 1); - if (prev) { + if (prevNode) { const newNode = new Node(val); - const temp = prev?.next; - prev.next = newNode; + const temp = prevNode?.next; + prevNode.next = newNode; newNode.next = temp; this.length++; return true; @@ -145,6 +135,45 @@ export class SinglyLinkedList { 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; + } } class Node { From 822bb3e2852d7fae3a66964618a47e07ca968d9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanna=20S=C3=B6derstr=C3=B6m?= Date: Wed, 7 Apr 2021 19:34:39 +0200 Subject: [PATCH 03/17] docs: add SinglyLinkedList --- src/dataStructures/README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 src/dataStructures/README.md diff --git a/src/dataStructures/README.md b/src/dataStructures/README.md new file mode 100644 index 0000000..06405ee --- /dev/null +++ b/src/dataStructures/README.md @@ -0,0 +1,21 @@ +# 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 + +- Good for insertion 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 + +## Stack + +## Queue From c19e35ca2f7a5ef1b28d13e411426af0fd527001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanna=20S=C3=B6derstr=C3=B6m?= Date: Wed, 7 Apr 2021 19:35:02 +0200 Subject: [PATCH 04/17] docs: on Sorting Algorithms --- src/sorters/README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/sorters/README.md diff --git a/src/sorters/README.md b/src/sorters/README.md new file mode 100644 index 0000000..e67e4ac --- /dev/null +++ b/src/sorters/README.md @@ -0,0 +1,10 @@ +# 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) From 6c2e4555ab81c68c901755d2870393199298b765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanna=20S=C3=B6derstr=C3=B6m?= Date: Wed, 7 Apr 2021 19:35:27 +0200 Subject: [PATCH 05/17] docs: update --- README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2f1cb26..ad6d0f7 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ # JavaScript Algorithms -Snippets for a bunch of useful JavaScript algorithms. +Library for a bunch of useful JavaScript algorithms. -## Complexity - Beginner +## Complexity - Low + +### Search | naiveSearch() | | | -------------------- | -------------------------------------------------------------------------- | @@ -12,6 +14,8 @@ Snippets for a bunch of useful JavaScript algorithms. | _Negative_ | Inefficient for large data sets. | | _Efficiency Average_ | O(n^2) | +### Sorting - Low + | bubbleSort() | | | -------------------- | ------------------------------------------- | | _Type_ | Loop in loop | @@ -36,7 +40,9 @@ Snippets for a bunch of useful JavaScript algorithms. | _Negative_ | Inefficient for large data sets. | | _Efficiency Average_ | O(n^2) | -## Complexity - Intermediate +## Complexity - Medium + +### Sorting - Medium | mergeSort() | | | -------------------- | --------------------------------------------------------------------------------------------------------------------------------- | @@ -54,7 +60,7 @@ Snippets for a bunch of useful JavaScript algorithms. | _Negative_ | With too large data sets you might run out of call stacks. | | _Efficiency Average_ | O(n log n) | -## Complexity - Expert +## Complexity - High | radixSort() | | | -------------------- | --------------------------------------------------------------------------------------------------------- | From 1e09ae6640caf626cbfead4451cf623ae1c23ffe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanna=20S=C3=B6derstr=C3=B6m?= Date: Wed, 7 Apr 2021 22:46:04 +0200 Subject: [PATCH 06/17] refactor: extract Node class --- src/dataStructures/Node.ts | 10 ++++++++++ src/dataStructures/SinglyLinkedList.test.ts | 2 +- src/dataStructures/SinglyLinkedList.ts | 16 ++++------------ 3 files changed, 15 insertions(+), 13 deletions(-) create mode 100644 src/dataStructures/Node.ts 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/SinglyLinkedList.test.ts b/src/dataStructures/SinglyLinkedList.test.ts index e5122c8..99ab910 100644 --- a/src/dataStructures/SinglyLinkedList.test.ts +++ b/src/dataStructures/SinglyLinkedList.test.ts @@ -1,4 +1,4 @@ -import { SinglyLinkedList } from "./SinglyLinkedList"; +import SinglyLinkedList from "./SinglyLinkedList"; describe("SinglyLinkedList", () => { describe("push", () => { diff --git a/src/dataStructures/SinglyLinkedList.ts b/src/dataStructures/SinglyLinkedList.ts index cd6a71b..d58cbb1 100644 --- a/src/dataStructures/SinglyLinkedList.ts +++ b/src/dataStructures/SinglyLinkedList.ts @@ -1,3 +1,5 @@ +import Node from "./Node"; + /* * Data structure of nodes that contains a head, tail * and length property. @@ -12,7 +14,7 @@ * Good for fast insertion and deletion of data. * Bad for accessing data. */ -export class SinglyLinkedList { +export default class SinglyLinkedList { head: Node | null; tail: Node | null; length: number; @@ -110,7 +112,7 @@ export class SinglyLinkedList { set(n: number, val: any): boolean { const foundNode = this.get(n); - if (foundNode) { + if (foundNode !== null && foundNode !== undefined) { foundNode.val = val; return true; } @@ -175,13 +177,3 @@ export class SinglyLinkedList { return this; } } - -class Node { - val: any; - next: Node | null; - - constructor(val: any) { - this.val = val; - this.next = null; - } -} From 77f52162d34d987cf2d53ed22625d889690c2b26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanna=20S=C3=B6derstr=C3=B6m?= Date: Wed, 7 Apr 2021 22:46:36 +0200 Subject: [PATCH 07/17] chore: prepare for npm publish --- .gitignore | 1 + package.json | 8 ++++++-- src/index.ts | 4 ++++ tsconfig.json | 8 ++++---- 4 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 src/index.ts 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/package.json b/package.json index 53d9dd5..ae146d1 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,12 @@ { "name": "javascript-algoritms", "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/index.ts b/src/index.ts new file mode 100644 index 0000000..3d29558 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,4 @@ +import sorters from "./sorters/index"; +import dataStructures from "./dataStructures/index"; + +export default { sorters, dataStructures }; 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 */ From 737d897ab0463c8b648f4412545965ee825309db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanna=20S=C3=B6derstr=C3=B6m?= Date: Thu, 8 Apr 2021 11:30:43 +0200 Subject: [PATCH 08/17] chore: restructure files for publishing --- index.js | 235 ------------------------------------------------------- index.ts | 148 ----------------------------------- 2 files changed, 383 deletions(-) delete mode 100644 index.js delete mode 100644 index.ts 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/index.ts b/index.ts deleted file mode 100644 index 52697ba..0000000 --- a/index.ts +++ /dev/null @@ -1,148 +0,0 @@ -import sorters from "./src/sorters"; - -/* - Naive String Search -*/ -function naiveSearch(string: string, find: string): number { - 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') - -/* - Checks if two strings are anagrams - O(n) -*/ -function validAnagram(string1: string, string2: string): boolean { - if (string1.length !== string2.length) return false; - if (string1 === string2) return true; - - const charCounts: { [key: string]: number } = {}; - - // Count number of times letter exists in string1 - for (let i = 0; i < string1.length; i++) { - const letter = string1[i]; - charCounts[letter] ? (charCounts[letter] += 1) : (charCounts[letter] = 1); - } - - // Compare char count in string2 - for (let i = 0; i < string2.length; i++) { - let letter = string2[i]; - - if (!charCounts[letter]) { - // doesn't exist or is zero - return false; - } else { - // reduce count - charCounts[letter] -= 1; - } - } - - return true; -} - -/* - Count number of unique values - [1, 1, 2, 3, 3, 4, 5, 6, 6, 7] should return 7 -*/ -function countUniqueValues(arr: number[]) { - if (arr.length === 0) return 0; - - let i = 0; - - for (let j = 1; j < arr.length; j++) { - if (arr[i] !== arr[j]) { - i++; - arr[i] = arr[j]; - } - } - - return i + 1; -} - -/* - Find max sub-array sum - Naive - O(n^2) - - ([2, 6, 9, 2, 1, 8, 5, 6, 3], 3) should return 19 -*/ -function maxSubarraySum(arr: number[], num: number) { - if (num > arr.length) return null; - - let max = -Infinity; - - for (let i = 0; i < arr.length - num + 1; i++) { - let tmp = 0; - for (let j = 0; j < num; j++) { - tmp += arr[i + j]; - } - if (tmp > max) { - max = tmp; - } - } - - return max; -} - -/* - Find max sub-array sum - Sliding window pattern - - ([2, 6, 9, 2, 1, 8, 5, 6, 3], 3) should return 19 -*/ -function maxSubarraySumSlidingWindow(arr: number[], num: number) { - if (arr.length < num) return null; - - let maxSum = 0; - let tmpSum = 0; - - for (let i = 0; i < num; i++) { - maxSum += arr[i]; - } - - tmpSum = maxSum; - - for (let i = num; i < arr.length; i++) { - tmpSum = tmpSum - arr[i - num] + arr[i]; - maxSum = Math.max(maxSum, tmpSum); - } - - return maxSum; -} - -// 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, -}; From a57b892048896deba607780afb4eba603d7b521c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanna=20S=C3=B6derstr=C3=B6m?= Date: Thu, 8 Apr 2021 11:30:51 +0200 Subject: [PATCH 09/17] chore: correct spelling --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ae146d1..7d04c4e 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "javascript-algoritms", + "name": "javascript-algorithms", "version": "1.0.0", "description": "A collection of sorting-algorithms", "main": "dist/index.js", From 5bf1b7362bb8e4524dc4d8476bf979e05d33e3b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanna=20S=C3=B6derstr=C3=B6m?= Date: Thu, 8 Apr 2021 11:35:18 +0200 Subject: [PATCH 10/17] docs: improve library description --- README.md | 110 +++++++++++++++++++++--------------------------------- 1 file changed, 43 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index ad6d0f7..d72202f 100644 --- a/README.md +++ b/README.md @@ -2,70 +2,46 @@ Library for a bunch of useful JavaScript algorithms. -## Complexity - Low - -### Search - -| 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) | - -### Sorting - Low - -| 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 - Medium - -### Sorting - Medium - -| 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 - High - -| 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) | +## 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] +``` From 50a2ef7b2af4b42d2ab924285e3b3103f22f95e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanna=20S=C3=B6derstr=C3=B6m?= Date: Thu, 8 Apr 2021 11:35:42 +0200 Subject: [PATCH 11/17] docs: add details --- src/sorters/README.md | 48 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/sorters/README.md b/src/sorters/README.md index e67e4ac..9cb5d91 100644 --- a/src/sorters/README.md +++ b/src/sorters/README.md @@ -8,3 +8,51 @@ 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) | From e629efb7addfc492f0982e9d89ab4ef1da0bfde6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanna=20S=C3=B6derstr=C3=B6m?= Date: Thu, 8 Apr 2021 11:36:22 +0200 Subject: [PATCH 12/17] feat: temporary placement for some functions --- src/index.ts | 126 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/src/index.ts b/src/index.ts index 3d29558..97b0f77 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,3 +2,129 @@ import sorters from "./sorters/index"; import dataStructures from "./dataStructures/index"; export default { sorters, dataStructures }; + +/* + Naive String Search +*/ +function naiveSearch(string: string, find: string): number { + 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') + +/* + Checks if two strings are anagrams + O(n) +*/ +function validAnagram(string1: string, string2: string): boolean { + if (string1.length !== string2.length) return false; + if (string1 === string2) return true; + + const charCounts: { [key: string]: number } = {}; + + // Count number of times letter exists in string1 + for (let i = 0; i < string1.length; i++) { + const letter = string1[i]; + charCounts[letter] ? (charCounts[letter] += 1) : (charCounts[letter] = 1); + } + + // Compare char count in string2 + for (let i = 0; i < string2.length; i++) { + let letter = string2[i]; + + if (!charCounts[letter]) { + // doesn't exist or is zero + return false; + } else { + // reduce count + charCounts[letter] -= 1; + } + } + + return true; +} + +/* + Count number of unique values + [1, 1, 2, 3, 3, 4, 5, 6, 6, 7] should return 7 +*/ +function countUniqueValues(arr: number[]) { + if (arr.length === 0) return 0; + + let i = 0; + + for (let j = 1; j < arr.length; j++) { + if (arr[i] !== arr[j]) { + i++; + arr[i] = arr[j]; + } + } + + return i + 1; +} + +/* + Find max sub-array sum + Naive + O(n^2) + + ([2, 6, 9, 2, 1, 8, 5, 6, 3], 3) should return 19 +*/ +function maxSubarraySum(arr: number[], num: number) { + if (num > arr.length) return null; + + let max = -Infinity; + + for (let i = 0; i < arr.length - num + 1; i++) { + let tmp = 0; + for (let j = 0; j < num; j++) { + tmp += arr[i + j]; + } + if (tmp > max) { + max = tmp; + } + } + + return max; +} + +/* + Find max sub-array sum + Sliding window pattern + + ([2, 6, 9, 2, 1, 8, 5, 6, 3], 3) should return 19 +*/ +function maxSubarraySumSlidingWindow(arr: number[], num: number) { + if (arr.length < num) return null; + + let maxSum = 0; + let tmpSum = 0; + + for (let i = 0; i < num; i++) { + maxSum += arr[i]; + } + + tmpSum = maxSum; + + for (let i = num; i < arr.length; i++) { + tmpSum = tmpSum - arr[i - num] + arr[i]; + maxSum = Math.max(maxSum, tmpSum); + } + + return maxSum; +} + +// console.log(maxSubarraySumSlidingWindow([2, 6, 9, 2, 1, 8, 5, 6, 3], 3)) From 8991002c8444bc8edd7135fbd95b8de7c042ca88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanna=20S=C3=B6derstr=C3=B6m?= Date: Sat, 10 Apr 2021 15:31:17 +0200 Subject: [PATCH 13/17] feat: add DoublyLinkedList --- src/dataStructures/DoublyLinkedList.test.ts | 306 ++++++++++++++++++++ src/dataStructures/DoublyLinkedList.ts | 188 ++++++++++++ 2 files changed, 494 insertions(+) create mode 100644 src/dataStructures/DoublyLinkedList.test.ts create mode 100644 src/dataStructures/DoublyLinkedList.ts 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; From d01a103df6851b66e1b507486764a98acda8d407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanna=20S=C3=B6derstr=C3=B6m?= Date: Sat, 10 Apr 2021 15:31:36 +0200 Subject: [PATCH 14/17] feat: add Stack --- src/dataStructures/Stack.test.ts | 76 ++++++++++++++++++++++++++++++++ src/dataStructures/Stack.ts | 56 +++++++++++++++++++++++ 2 files changed, 132 insertions(+) create mode 100644 src/dataStructures/Stack.test.ts create mode 100644 src/dataStructures/Stack.ts 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; + } +} From 04d9532f3df917ca1f755246ef403f33e0f48170 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanna=20S=C3=B6derstr=C3=B6m?= Date: Sat, 10 Apr 2021 15:31:46 +0200 Subject: [PATCH 15/17] feat: add Queue --- src/dataStructures/Queue.test.ts | 60 ++++++++++++++++++++++++++++++++ src/dataStructures/Queue.ts | 54 ++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 src/dataStructures/Queue.test.ts create mode 100644 src/dataStructures/Queue.ts 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; + } +} From e89d218b696cafc19af596430be128e221569c4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanna=20S=C3=B6derstr=C3=B6m?= Date: Sat, 10 Apr 2021 15:32:23 +0200 Subject: [PATCH 16/17] feat: add Stack and Queue --- src/dataStructures/index.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/dataStructures/index.ts 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; From 00a062931865ea73dd3c77ee1b8597f22fb41be7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanna=20S=C3=B6derstr=C3=B6m?= Date: Sat, 10 Apr 2021 15:35:45 +0200 Subject: [PATCH 17/17] docs: add Stack, Queue and DoublyLinkedList --- src/dataStructures/README.md | 42 ++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/src/dataStructures/README.md b/src/dataStructures/README.md index 06405ee..43422e5 100644 --- a/src/dataStructures/README.md +++ b/src/dataStructures/README.md @@ -4,8 +4,10 @@ JavaScript doesn't inherently have support for several of the type of lists that ## Singly Linked List -- Good for insertion and removal at the beginning of the list. -- Bad for searching and access. +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: @@ -16,6 +18,42 @@ Performance: ## 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)