commit d57926602daec16d4bc0678246da8a954cb60178 Author: Revone Date: Fri Jun 16 00:34:28 2023 +0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..62ab633 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +node_modules/**/* +npm-debug.* +*.jks +*.p8 +*.p12 +*.key +*.mobileprovision +*.orig.* +dist/ +.DS_Store +__tests__/ +#.expo/* +#web-build/ +#.idea/ +#.vscode/ +#note.txt +#backup/ +#note.md +#test.json +#test.ts +#test-b.json +#test.html +#.env +#.expo-shared/ +#yarn-error.log +#.next/ +#.vercel/ +#src/google/ +#src/firebase/migrations/raw-data/ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..b58b603 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/data-structure-typed.iml b/.idea/data-structure-typed.iml new file mode 100644 index 0000000..0c8867d --- /dev/null +++ b/.idea/data-structure-typed.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..eaebbb7 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ceb4799 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# data-structure-ts +Hash (CoordinateSet, CoordinateMap) Heap (MaxHeap, MinHeap) Binary Tree (AVL Tree, Binary Indexed Tree, Binary Search Tree, Segment Tree, Tree Multiset) Graph (Directed Graph, Undirected Graph) Linked List (Singly Linked List, Doubly Linked List) Matrix Priority Queue (Max Priority Queue, Min Priority Queue) Queue (Queue, Dequeue) Stack Trie diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..50cf0c9 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,63 @@ +{ + "name": "data-structure-typed", + "version": "0.8.6", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "data-structure-typed", + "version": "0.8.6", + "license": "ISC", + "dependencies": { + "lodash": "^4.17.21" + }, + "devDependencies": { + "@types/lodash": "^4.14.178", + "typescript": "^4.6.2" + } + }, + "node_modules/@types/lodash": { + "version": "4.14.195", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz", + "integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==", + "dev": true + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + } + }, + "dependencies": { + "@types/lodash": { + "version": "4.14.195", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz", + "integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==", + "dev": true + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..29f7eef --- /dev/null +++ b/package.json @@ -0,0 +1,75 @@ +{ + "name": "data-structure-typed", + "version": "0.8.6", + "description": "Hash (CoordinateSet, CoordinateMap) Heap (MaxHeap, MinHeap) Binary Tree (AVL Tree, Binary Indexed Tree, Binary Search Tree, Segment Tree, Tree Multiset) Graph (Directed Graph, Undirected Graph) Linked List (Singly Linked List, Doubly Linked List) Matrix Priority Queue (Max Priority Queue, Min Priority Queue) Queue (Queue, Dequeue) Stack Trie", + "main": "dist/index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/zrwusa/data-structure-typed.git" + }, + "keywords": [ + "Hash", + "CoordinateSet", + "CoordinateMap", + "Heap", + "MaxHeap", + "MinHeap", + "Binary", + "Tree", + "AVL", + "Tree", + "Binary", + "Indexed", + "Tree", + "Binary", + "Search", + "Tree", + "Segment", + "Tree", + "Tree", + "Multiset", + "Graph", + "Directed", + "Graph", + "Undirected", + "Graph", + "Linked", + "List", + "Singly", + "Linked", + "List", + "Doubly", + "Linked", + "List", + "Matrix", + "Priority", + "Queue", + "Max", + "Priority", + "Queue", + "Min", + "Priority", + "Queue", + "Queue", + "Queue", + "Dequeue", + "Stack", + "Trie" + ], + "author": "Tyler Zeng", + "license": "ISC", + "bugs": { + "url": "https://github.com/zrwusa/data-structure-ts/issues" + }, + "homepage": "https://github.com/zrwusa/data-structure-ts#readme", + "devDependencies": { + "@types/lodash": "^4.14.178", + "typescript": "^4.6.2" + }, + "dependencies": { + "lodash": "^4.17.21" + } +} diff --git a/src/data-structures/binary-tree/aa-tree.ts b/src/data-structures/binary-tree/aa-tree.ts new file mode 100644 index 0000000..e46bdd9 --- /dev/null +++ b/src/data-structures/binary-tree/aa-tree.ts @@ -0,0 +1,3 @@ +export class AaTree { + +} diff --git a/src/data-structures/binary-tree/avl-tree.ts b/src/data-structures/binary-tree/avl-tree.ts new file mode 100644 index 0000000..3de4505 --- /dev/null +++ b/src/data-structures/binary-tree/avl-tree.ts @@ -0,0 +1,232 @@ +import {BST, BSTNode} from './bst'; +import {BinaryTreeNodeId} from './binary-tree'; + +export interface AVLTreeDeleted { + deleted: AVLTreeNode | null; + needBalanced: AVLTreeNode | null; +} + +export class AVLTreeNode extends BSTNode { + override clone(): AVLTreeNode { + return new AVLTreeNode(this.id, this.val, this.count); + } +} + +export class AVLTree extends BST { + + override createNode(id: BinaryTreeNodeId, val: T, count?: number): AVLTreeNode { + return new AVLTreeNode(id, val, count); + } + + override put(id: BinaryTreeNodeId, val: T | null, count?: number): AVLTreeNode | null { + const inserted = super.put(id, val, count); + if (inserted) this.balancePath(inserted); + return inserted; + } + + override remove(id: BinaryTreeNodeId, isUpdateAllLeftSum?: boolean): AVLTreeDeleted[] { + const deletedResults = super.remove(id, isUpdateAllLeftSum); + for (const {needBalanced} of deletedResults) { + if (needBalanced) { + this.balancePath(needBalanced); + } + } + return deletedResults; + } + + balanceFactor(node: AVLTreeNode): number { + if (!node.right) // node has no right subtree + return -node.height; + else if (!node.left) // node has no left subtree + return +node.height; + else + return node.right.height - node.left.height; + } + + updateHeight(node: AVLTreeNode): void { + if (!node.left && !node.right) // node is a leaf + node.height = 0; + else if (!node.left) { + // node has no left subtree + const rightHeight = node.right ? node.right.height : 0; + node.height = 1 + rightHeight; + } else if (!node.right) // node has no right subtree + node.height = 1 + node.left.height; + else + node.height = 1 + Math.max(node.right.height, node.left.height); + } + + balancePath(node: AVLTreeNode): void { + const path = this.getPathToRoot(node); + for (let i = path.length - 1; i >= 0; i--) { + const A = path[i]; + this.updateHeight(A); + switch (this.balanceFactor(A)) { + case -2: + if (A && A.left) { + if (this.balanceFactor(A.left) <= 0) { + this.balanceLL(A); // Perform LL rotation + } else { + this.balanceLR(A); // Perform LR rotation + } + } + break; + case +2: + if (A && A.right) { + if (this.balanceFactor(A.right) >= 0) { + this.balanceRR(A); // Perform RR rotation + } else { + this.balanceRL(A); // Perform RL rotation + } + } + } + } + } + + balanceLL(A: AVLTreeNode): void { + const parentOfA = A.parent; + const B = A.left; // A is left-heavy and B is left-heavy + A.parent = B; + if (B && B.right) { + B.right.parent = A; + } + if (B) B.parent = parentOfA; + if (A === this.root) { + if (B) this.root = B; + } else { + if (parentOfA?.left === A) { + parentOfA.left = B; + } else { + if (parentOfA) parentOfA.right = B; + } + } + + if (B) { + A.left = B.right; // Make T2 the left subtree of A + B.right = A; // Make A the left child of B + } + this.updateHeight(A); + if (B) this.updateHeight(B); + } + + balanceLR(A: AVLTreeNode): void { + const parentOfA = A.parent; + const B = A.left; // A is left-heavy + let C = null; + if (B) { + C = B.right;// B is right-heavy + } + if (A) A.parent = C; + if (B) B.parent = C; + + if (C) { + if (C.left) { + C.left.parent = B; + } + if (C.right) { + C.right.parent = A; + } + C.parent = parentOfA; + } + + if (A === this.root) { + if (C) this.root = C; + } else { + if (parentOfA) { + if (parentOfA.left === A) { + parentOfA.left = C; + } else { + parentOfA.right = C; + } + } + } + + if (C) { + A.left = C.right; // Make T3 the left subtree of A + if (B) B.right = C.left; // Make T2 the right subtree of B + C.left = B; + C.right = A; + } + + this.updateHeight(A); // Adjust heights + B && this.updateHeight(B); + C && this.updateHeight(C); + } + + balanceRR(A: AVLTreeNode): void { + const parentOfA = A.parent; + const B = A.right; // A is right-heavy and B is right-heavy + A.parent = B; + if (B) { + if (B.left) { + B.left.parent = A; + } + B.parent = parentOfA; + } + + if (A === this.root) { + if (B) this.root = B; + } else { + if (parentOfA) { + if (parentOfA.left === A) { + parentOfA.left = B; + } else { + parentOfA.right = B; + } + } + } + + if (B) { + A.right = B.left; // Make T2 the right subtree of A + B.left = A; + } + this.updateHeight(A); + B && this.updateHeight(B); + } + + balanceRL(A: AVLTreeNode): void { + const parentOfA = A.parent; + const B = A.right; // A is right-heavy + let C = null; + if (B) { + C = B.left; // B is left-heavy + } + + A.parent = C; + if (B) B.parent = C; + + if (C) { + if (C.left) { + C.left.parent = A; + } + if (C.right) { + C.right.parent = B; + } + C.parent = parentOfA; + } + + + if (A === this.root) { + if (C) this.root = C; + } else { + if (parentOfA) { + if (parentOfA.left === A) { + parentOfA.left = C; + } else { + parentOfA.right = C; + } + } + } + + if (C) A.right = C.left; // Make T2 the right subtree of A + if (B && C) B.left = C.right; // Make T3 the left subtree of B + if (C) C.left = A; + if (C) C.right = B; + + this.updateHeight(A); // Adjust heights + B && this.updateHeight(B); + C && this.updateHeight(C); + } +} + + diff --git a/src/data-structures/binary-tree/b-tree.ts b/src/data-structures/binary-tree/b-tree.ts new file mode 100644 index 0000000..509f57e --- /dev/null +++ b/src/data-structures/binary-tree/b-tree.ts @@ -0,0 +1,3 @@ +export class BTree { + +} diff --git a/src/data-structures/binary-tree/binary-indexed-tree.ts b/src/data-structures/binary-tree/binary-indexed-tree.ts new file mode 100644 index 0000000..01d85e7 --- /dev/null +++ b/src/data-structures/binary-tree/binary-indexed-tree.ts @@ -0,0 +1,33 @@ +export class BinaryIndexedTree { + private readonly _sumTree: number[]; + + constructor(n: number) { + this._sumTree = new Array(n + 1).fill(0); + } + + update(i: number, delta: number) { + while (i < this._sumTree.length) { + this._sumTree[i] += delta; + i += BinaryIndexedTree.lowBit(i); + } + } + + getPrefixSum(i: number) { + let sum = 0; + while (i > 0) { + sum += this._sumTree[i]; + i -= BinaryIndexedTree.lowBit(i); + } + return sum; + } + + public getRangeSum(start: number, end: number): number { + if (!(0 <= start && start <= end && end <= this._sumTree.length)) + throw 'Index out of bounds'; + return this.getPrefixSum(end) - this.getPrefixSum(start); + } + + static lowBit(x: number) { + return x & (-x); + } +} diff --git a/src/data-structures/binary-tree/binary-tree.ts b/src/data-structures/binary-tree/binary-tree.ts new file mode 100644 index 0000000..4d53a2b --- /dev/null +++ b/src/data-structures/binary-tree/binary-tree.ts @@ -0,0 +1,1088 @@ +import {ThunkOrValue, trampoline} from '../trampoline'; + +export type BinaryTreeNodePropertyName = 'id' | 'val' | 'count'; +export type NodeOrPropertyName = 'node' | BinaryTreeNodePropertyName; +export type DFSOrderPattern = 'in' | 'pre' | 'post'; +export type BinaryTreeNodeId = number; +export type BinaryTreeDeleted = { deleted: BinaryTreeNode | null | undefined, needBalanced: BinaryTreeNode | null }; +export type ResultByProperty = T | BinaryTreeNode | number | BinaryTreeNodeId; +export type ResultsByProperty = ResultByProperty[]; + +export interface BinaryTreeNodeObj { + id: BinaryTreeNodeId; + val: T; + count?: number; +} + +export enum FamilyPosition {root, left, right} + +export enum LoopType { iterative = 1, recursive = 2} + +export class BinaryTreeNode { + protected _id: BinaryTreeNodeId; + get id(): BinaryTreeNodeId { + return this._id; + } + + set id(v: BinaryTreeNodeId) { + this._id = v; + } + + protected _val: T; + get val(): T { + return this._val; + } + + set val(v: T) { + this._val = v; + } + + protected _left?: BinaryTreeNode | null; + get left(): BinaryTreeNode | null | undefined { + return this._left; + } + + set left(v: BinaryTreeNode | null | undefined) { + if (v) { + v.parent = this; + v.familyPosition = FamilyPosition.left; + } + this._left = v; + } + + protected _right?: BinaryTreeNode | null; + get right(): BinaryTreeNode | null | undefined { + return this._right; + } + + set right(v: BinaryTreeNode | null | undefined) { + if (v) { + v.parent = this; + v.familyPosition = FamilyPosition.right; + } + this._right = v; + } + + protected _parent: BinaryTreeNode | null | undefined = undefined; + get parent(): BinaryTreeNode | null | undefined { + return this._parent; + } + + set parent(v: BinaryTreeNode | null | undefined) { + this._parent = v; + } + + protected _familyPosition: FamilyPosition = FamilyPosition.root; + get familyPosition(): FamilyPosition { + return this._familyPosition; + } + + set familyPosition(v: FamilyPosition) { + this._familyPosition = v; + } + + protected _count = 1; + get count(): number { + return this._count; + } + + set count(v: number) { + this._count = v; + } + + protected _height = 0; + + get height(): number { + return this._height; + } + + set height(v: number) { + this._height = v; + } + + constructor(id: BinaryTreeNodeId, val: T, count?: number) { + this._id = id; + this._val = val; + this._count = count ?? 1; + } + + swapLocation(swapNode: BinaryTreeNode): BinaryTreeNode { + const {val, count, height} = swapNode; + const tempNode = new BinaryTreeNode(swapNode.id, val); + tempNode.val = val; + tempNode.count = count; + tempNode.height = height; + + swapNode.id = this.id; + swapNode.val = this.val; + swapNode.count = this.count; + swapNode.height = this.height; + + this.id = tempNode.id; + this.val = tempNode.val; + this.count = tempNode.count; + this.height = tempNode.height; + return swapNode; + } + + clone(): BinaryTreeNode { + return new BinaryTreeNode(this.id, this.val, this.count); + } +} + +export class BinaryTree { + protected _root: BinaryTreeNode | null = null; + public get root(): BinaryTreeNode | null { + return this._root; + } + + protected set root(v: BinaryTreeNode | null) { + if (v) { + v.parent = null; + v.familyPosition = FamilyPosition.root; + } + this._root = v; + } + + protected _size = 0; + get size(): number { + return this._size; + } + + protected set size(v: number) { + this._size = v; + } + + protected _count = 0; + get count(): number { + return this._count; + } + + protected set count(v: number) { + this._count = v; + } + + private readonly _autoIncrementId: boolean = false; + private _maxId: number = -1; + private readonly _isDuplicatedVal: boolean = false; + + protected _loopType: LoopType = LoopType.iterative; + protected _visitedId: BinaryTreeNodeId[] = []; + protected _visitedVal: Array = []; + protected _visitedNode: BinaryTreeNode[] = []; + protected _visitedCount: number[] = []; + protected _visitedLeftSum: number[] = []; + + protected _resetResults() { + this._visitedId = []; + this._visitedVal = []; + this._visitedNode = []; + this._visitedCount = []; + this._visitedLeftSum = []; + } + + constructor(options?: { + loopType?: LoopType, + autoIncrementId?: boolean, + isDuplicatedVal?: boolean + }) { + if (options !== undefined) { + const { + loopType = LoopType.iterative, + autoIncrementId = false, + isDuplicatedVal = false + } = options; + this._isDuplicatedVal = isDuplicatedVal; + this._autoIncrementId = autoIncrementId; + this._loopType = loopType; + } + } + + createNode(id: BinaryTreeNodeId, val: T | null, count?: number): BinaryTreeNode | null { + return val !== null ? new BinaryTreeNode(id, val, count) : null; + } + + clear() { + this.root = null; + this.size = 0; + this.count = 0; + this._maxId = -1; + } + + isEmpty(): boolean { + return this.size === 0; + } + + insertTo({newNode, parent}: { newNode: BinaryTreeNode | null, parent: BinaryTreeNode }) { + if (parent) { + if (parent.left === undefined) { + if (newNode) { + newNode.parent = parent; + newNode.familyPosition = FamilyPosition.left; + } + parent.left = newNode; + if (newNode !== null) { + this.size++; + this.count += newNode?.count ?? 0; + } + + return parent.left; + } else if (parent.right === undefined) { + if (newNode) { + newNode.parent = parent; + newNode.familyPosition = FamilyPosition.right; + } + parent.right = newNode; + if (newNode !== null) { + this.size++; + this.count += newNode?.count ?? 0; + } + return parent.right; + } else { + return; + } + } else { + return; + } + } + + put(id: BinaryTreeNodeId, val: T, count?: number): BinaryTreeNode | null | undefined { + count = count ?? 1; + + const _bfs = (root: BinaryTreeNode, newNode: BinaryTreeNode | null): BinaryTreeNode | undefined | null => { + const queue: Array | null> = [root]; + while (queue.length > 0) { + const cur = queue.shift(); + if (cur) { + const inserted = this.insertTo({newNode, parent: cur}); + if (inserted !== undefined) return inserted; + if (cur.left) queue.push(cur.left); + if (cur.right) queue.push(cur.right); + } else return; + } + return; + }; + + let inserted: BinaryTreeNode | null | undefined; + const needInsert = val !== null ? new BinaryTreeNode(id, val, count) : null; + const existNode = val !== null ? this.get(id, 'id') : null; + if (this.root) { + if (existNode) { + existNode.count += count; + existNode.val = val; + if (needInsert !== null) { + this.count += count; + inserted = existNode; + } + } else { + inserted = _bfs(this.root, needInsert); + } + } else { + this.root = val !== null ? new BinaryTreeNode(id, val, count) : null; + if (needInsert !== null) { + this.size = 1; + this.count = count; + } + inserted = this.root; + } + return inserted; + } + + insertMany(data: T[] | BinaryTreeNode[]): (BinaryTreeNode | null | undefined)[] { + const inserted: (BinaryTreeNode | null | undefined)[] = []; + const map: Map, number> = new Map(); + + if (!this._isDuplicatedVal) { + for (const i of data) map.set(i, (map.get(i) ?? 0) + 1); + } + + for (const item of data) { + const count = this._isDuplicatedVal ? 1 : map.get(item); + + if (item instanceof BinaryTreeNode) { + inserted.push(this.put(item.id, item.val, item.count)); + } else if (typeof item === 'number' && !this._autoIncrementId) { + if (!this._isDuplicatedVal) { + if (map.get(item) !== undefined) { + inserted.push(this.put(item, item, count)); + map.delete(item); + } + } else { + inserted.push(this.put(item, item, 1)); + } + } else { + if (item !== null) { + if (!this._isDuplicatedVal) { + if (map.get(item) !== undefined) { + inserted.push(this.put(++this._maxId, item, count)); + map.delete(item); + } + } else { + inserted.push(this.put(++this._maxId, item, 1)); + } + } else { + inserted.push(this.put(Number.MAX_SAFE_INTEGER, item, 0)); + } + } + } + return inserted; + } + + fill(data: T[] | BinaryTreeNode[]): boolean { + this.clear(); + return data.length === this.insertMany(data).length; + } + + remove(id: BinaryTreeNodeId, ignoreCount?: boolean): BinaryTreeDeleted[] { + const nodes = this.getNodes(id, 'id', true); + let node: BinaryTreeNode | null | undefined = nodes[0]; + + if (!node) node = undefined; + else if (node.count > 1 && !ignoreCount) { + node.count--; + this.count--; + } else if (node instanceof BinaryTreeNode) { + const [subSize, subCount] = this.getSubTreeSizeAndCount(node); + + switch (node.familyPosition) { + case 0: + this.size -= subSize; + this.count -= subCount; + node = undefined; + break; + case 1: + if (node.parent) { + this.size -= subSize; + this.count -= subCount; + node.parent.left = null; + } + break; + case 2: + if (node.parent) { + this.size -= subSize; + this.count -= subCount; + node.parent.right = null; + } + break; + } + } + return [{deleted: node, needBalanced: null}]; + } + + getDepth(node: BinaryTreeNode): number { + let depth = 0; + while (node.parent) { + depth++; + node = node.parent; + } + return depth; + } + + getHeight(beginRoot?: BinaryTreeNode | null): number { + beginRoot = beginRoot ?? this.root; + if (!beginRoot) return -1; + + if (this._loopType === LoopType.recursive) { + const _getMaxHeight = (cur: BinaryTreeNode | null | undefined): number => { + if (!cur) return -1; + const leftHeight = _getMaxHeight(cur.left); + const rightHeight = _getMaxHeight(cur.right); + return Math.max(leftHeight, rightHeight) + 1; + }; + + return _getMaxHeight(beginRoot); + } else { + const stack: BinaryTreeNode[] = []; + let node: BinaryTreeNode | null | undefined = beginRoot, last: BinaryTreeNode | null = null, + depths: Map, number> = new Map(); + + while (stack.length > 0 || node) { + if (node) { + stack.push(node); + node = node.left; + } else { + node = stack[stack.length - 1] + if (!node.right || last === node.right) { + node = stack.pop(); + if (node) { + let leftHeight = node.left ? depths.get(node.left) ?? -1 : -1; + let rightHeight = node.right ? depths.get(node.right) ?? -1 : -1; + depths.set(node, 1 + Math.max(leftHeight, rightHeight)); + last = node; + node = null; + } + } else node = node.right + } + } + + return depths.get(beginRoot) ?? -1; + } + } + + getMinHeight(beginRoot?: BinaryTreeNode | null): number { + beginRoot = beginRoot || this.root; + if (!beginRoot) return -1; + + if (this._loopType === LoopType.recursive) { + const _getMinHeight = (cur: BinaryTreeNode | null | undefined): number => { + if (!cur) return 0; + if (!cur.left && !cur.right) return 0; + const leftMinHeight = _getMinHeight(cur.left); + const rightMinHeight = _getMinHeight(cur.right); + return Math.min(leftMinHeight, rightMinHeight) + 1; + }; + + return _getMinHeight(beginRoot); + } else { + const stack: BinaryTreeNode[] = []; + let node: BinaryTreeNode | null | undefined = beginRoot, last: BinaryTreeNode | null = null, + depths: Map, number> = new Map(); + + while (stack.length > 0 || node) { + if (node) { + stack.push(node); + node = node.left; + } else { + node = stack[stack.length - 1] + if (!node.right || last === node.right) { + node = stack.pop(); + if (node) { + let leftMinHeight = node.left ? depths.get(node.left) ?? -1 : -1; + let rightMinHeight = node.right ? depths.get(node.right) ?? -1 : -1; + depths.set(node, 1 + Math.min(leftMinHeight, rightMinHeight)); + last = node; + node = null; + } + } else node = node.right + } + } + + return depths.get(beginRoot) ?? -1; + } + } + + isBalanced(beginRoot?: BinaryTreeNode | null): boolean { + return (this.getMinHeight(beginRoot) + 1 >= this.getHeight(beginRoot)); + } + + getNodes(nodeProperty: BinaryTreeNodeId | T, propertyName ?: BinaryTreeNodePropertyName, onlyOne ?: boolean) { + if (!this.root) return [] as null[]; + propertyName = propertyName ?? 'id'; + + const result: (BinaryTreeNode | null | undefined)[] = []; + + if (this._loopType === LoopType.recursive) { + const _traverse = (cur: BinaryTreeNode) => { + if (this._pushByPropertyNameStopOrNot(cur, result, nodeProperty, propertyName, onlyOne)) return; + if (!cur.left && !cur.right) return; + cur.left && _traverse(cur.left); + cur.right && _traverse(cur.right); + } + + _traverse(this.root); + } else { + const queue: BinaryTreeNode[] = [this.root]; + while (queue.length > 0) { + const cur = queue.shift(); + if (cur) { + if (this._pushByPropertyNameStopOrNot(cur, result, nodeProperty, propertyName, onlyOne)) return result; + cur.left && queue.push(cur.left); + cur.right && queue.push(cur.right); + } + } + } + + return result; + } + + has(nodeProperty: BinaryTreeNodeId | T, propertyName ?: BinaryTreeNodePropertyName): boolean { + return this.getNodes(nodeProperty, propertyName).length > 0; + } + + get(nodeProperty: BinaryTreeNodeId | T, propertyName ?: BinaryTreeNodePropertyName): BinaryTreeNode | null { + propertyName = propertyName ?? 'id'; + return this.getNodes(nodeProperty, propertyName, true)[0] ?? null; + } + + getPathToRoot(node: BinaryTreeNode): BinaryTreeNode[] { + const result: BinaryTreeNode[] = []; + while (node.parent) { + result.unshift(node); + node = node.parent; + } + result.unshift(node); + return result; + } + + protected _pushByPropertyNameStopOrNot(cur: BinaryTreeNode, result: (BinaryTreeNode | null | undefined)[], nodeProperty: BinaryTreeNodeId | T, propertyName ?: BinaryTreeNodePropertyName, onlyOne ?: boolean) { + switch (propertyName) { + case 'id': + if (cur.id === nodeProperty) { + result.push(cur); + return !!onlyOne; + } + break; + case 'count': + if (cur.count === nodeProperty) { + result.push(cur); + return !!onlyOne; + } + break; + case 'val': + if (cur.val === nodeProperty) { + result.push(cur); + return !!onlyOne; + } + break; + default: + if (cur.id === nodeProperty) { + result.push(cur); + return !!onlyOne; + } + break; + } + } + + protected _accumulatedByPropertyName(node: BinaryTreeNode, nodeOrPropertyName ?: NodeOrPropertyName) { + nodeOrPropertyName = nodeOrPropertyName ?? 'id'; + + switch (nodeOrPropertyName) { + case 'id': + this._visitedId.push(node.id); + break; + case 'val': + this._visitedVal.push(node.val); + break; + case 'node': + this._visitedNode.push(node); + break; + case 'count': + this._visitedCount.push(node.count); + break; + default: + this._visitedId.push(node.id); + break; + } + } + + protected _getResultByPropertyName(nodeOrPropertyName ?: NodeOrPropertyName): ResultsByProperty { + nodeOrPropertyName = nodeOrPropertyName ?? 'id'; + + switch (nodeOrPropertyName) { + case 'id': + return this._visitedId; + case 'val': + return this._visitedVal; + case 'node': + return this._visitedNode; + case 'count': + return this._visitedCount; + default: + return this._visitedId; + } + } + + getLeftMost(): BinaryTreeNode | null; + getLeftMost(node: BinaryTreeNode): BinaryTreeNode; + getLeftMost(node?: BinaryTreeNode | null): BinaryTreeNode | null { + node = node ?? this.root; + if (!node) return node; + + if (this._loopType === LoopType.recursive) { + + const _traverse = (cur: BinaryTreeNode): BinaryTreeNode => { + if (!cur.left) return cur; + return _traverse(cur.left); + } + + return _traverse(node); + } else { + // Indirect implementation of iteration using tail recursion optimization + const _traverse = trampoline((cur: BinaryTreeNode): ThunkOrValue | null> => { + if (!cur.left) return cur; + return _traverse.cont(cur.left); + }); + + return _traverse(node); + } + } + + getRightMost(): BinaryTreeNode | null; + getRightMost(node: BinaryTreeNode): BinaryTreeNode; + getRightMost(node?: BinaryTreeNode | null): BinaryTreeNode | null { + node = node ?? this.root; + if (!node) return node; + + if (this._loopType === LoopType.recursive) { + const _traverse = (cur: BinaryTreeNode): BinaryTreeNode => { + if (!cur.right) return cur; + return _traverse(cur.right); + } + + return _traverse(node); + } else { + // Indirect implementation of iteration using tail recursion optimization + const _traverse = trampoline((cur: BinaryTreeNode): ThunkOrValue | null> => { + if (!cur.right) return cur; + return _traverse.cont(cur.right); + }); + + return _traverse(node); + } + } + + // --- start additional methods --- + isBST(node?: BinaryTreeNode | null): boolean { + node = node ?? this.root; + if (!node) return true; + + if (this._loopType === LoopType.recursive) { + const dfs = (cur: BinaryTreeNode | null | undefined, min: BinaryTreeNodeId, max: BinaryTreeNodeId): boolean => { + if (!cur) return true; + if (cur.id <= min || cur.id >= max) return false; + return dfs(cur.left, min, cur.id) && dfs(cur.right, cur.id, max); + } + + return dfs(node, Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER); + } else { + const stack = []; + let prev = Number.MIN_SAFE_INTEGER, curr: BinaryTreeNode | null | undefined = node; + while (curr || stack.length > 0) { + while (curr) { + stack.push(curr); + curr = curr.left; + } + curr = stack.pop()!; + if (prev >= curr.id) return false; + prev = curr.id; + curr = curr.right; + } + return true; + } + } + + getSubTreeSizeAndCount(subTreeRoot: BinaryTreeNode | null | undefined) { + const res: [number, number] = [0, 0]; + if (!subTreeRoot) return res; + + if (this._loopType === LoopType.recursive) { + const _traverse = (cur: BinaryTreeNode) => { + res[0]++; + res[1] += cur.count; + cur.left && _traverse(cur.left); + cur.right && _traverse(cur.right); + } + + _traverse(subTreeRoot); + return res; + } else { + const stack: BinaryTreeNode[] = [subTreeRoot]; + + while (stack.length > 0) { + const cur = stack.pop()!; + res[0]++; + res[1] += cur.count; + cur.right && stack.push(cur.right); + cur.left && stack.push(cur.left); + } + + return res; + } + } + + subTreeSum(subTreeRoot: BinaryTreeNode, propertyName ?: BinaryTreeNodePropertyName): number { + propertyName = propertyName ?? 'val'; + if (!subTreeRoot) return 0; + + let sum = 0; + + const _sumByProperty = (cur: BinaryTreeNode) => { + let needSum: number; + switch (propertyName) { + case 'id': + needSum = cur.id; + break; + case 'count': + needSum = cur.count; + break; + case 'val': + needSum = typeof cur.val === 'number' ? cur.val : 0; + break; + default: + needSum = cur.id; + break; + } + return needSum; + } + + if (this._loopType === LoopType.recursive) { + const _traverse = (cur: BinaryTreeNode): void => { + sum += _sumByProperty(cur); + cur.left && _traverse(cur.left); + cur.right && _traverse(cur.right); + } + + _traverse(subTreeRoot); + } else { + const stack: BinaryTreeNode[] = [subTreeRoot]; + + while (stack.length > 0) { + const cur = stack.pop()!; + sum += _sumByProperty(cur); + cur.right && stack.push(cur.right); + cur.left && stack.push(cur.left); + } + } + + return sum; + } + + subTreeAdd(subTreeRoot: BinaryTreeNode, delta: number, propertyName ?: BinaryTreeNodePropertyName): boolean { + propertyName = propertyName ?? 'id'; + if (!subTreeRoot) return false; + + const _addByProperty = (cur: BinaryTreeNode) => { + switch (propertyName) { + case 'id': + cur.id += delta; + break; + case 'count': + cur.count += delta; + this.count += delta; + break; + default: + cur.id += delta; + break; + } + } + + if (this._loopType === LoopType.recursive) { + const _traverse = (cur: BinaryTreeNode) => { + _addByProperty(cur); + cur.left && _traverse(cur.left); + cur.right && _traverse(cur.right); + }; + + _traverse(subTreeRoot); + } else { + const stack: BinaryTreeNode[] = [subTreeRoot]; + + while (stack.length > 0) { + const cur = stack.pop()!; + + _addByProperty(cur); + cur.right && stack.push(cur.right); + cur.left && stack.push(cur.left); + } + } + return true; + } + + BFS(): BinaryTreeNodeId[]; + BFS(nodeOrPropertyName: 'id'): BinaryTreeNodeId[]; + BFS(nodeOrPropertyName: 'val'): T[]; + BFS(nodeOrPropertyName: 'node'): BinaryTreeNode[]; + BFS(nodeOrPropertyName: 'count'): number[]; + BFS(nodeOrPropertyName ?: NodeOrPropertyName): ResultsByProperty { + nodeOrPropertyName = nodeOrPropertyName ?? 'id'; + this._resetResults(); + const queue: Array | null | undefined> = [this.root]; + + while (queue.length !== 0) { + const cur = queue.shift(); + if (cur) { + this._accumulatedByPropertyName(cur, nodeOrPropertyName); + if (cur?.left !== null) queue.push(cur.left); + if (cur?.right !== null) queue.push(cur.right); + } + } + + return this._getResultByPropertyName(nodeOrPropertyName); + } + + DFS(): BinaryTreeNodeId[]; + DFS(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'id'): BinaryTreeNodeId[]; + DFS(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'val'): T[]; + DFS(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'node'): BinaryTreeNode[]; + DFS(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'count'): number[]; + DFS(pattern ?: 'in' | 'pre' | 'post', nodeOrPropertyName ?: NodeOrPropertyName): ResultsByProperty { + pattern = pattern ?? 'in'; + nodeOrPropertyName = nodeOrPropertyName ?? 'id'; + this._resetResults(); + const _traverse = (node: BinaryTreeNode) => { + switch (pattern) { + case 'in': + if (node.left) _traverse(node.left); + this._accumulatedByPropertyName(node, nodeOrPropertyName); + if (node.right) _traverse(node.right); + break; + case 'pre': + this._accumulatedByPropertyName(node, nodeOrPropertyName); + if (node.left) _traverse(node.left); + if (node.right) _traverse(node.right); + break; + case 'post': + if (node.left) _traverse(node.left); + if (node.right) _traverse(node.right); + this._accumulatedByPropertyName(node, nodeOrPropertyName); + break; + } + }; + + this.root && _traverse(this.root); + return this._getResultByPropertyName(nodeOrPropertyName); + } + + DFSIterative(): BinaryTreeNodeId[]; + DFSIterative(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'id'): BinaryTreeNodeId[]; + DFSIterative(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'val'): T[]; + DFSIterative(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'node'): BinaryTreeNode[]; + DFSIterative(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'count'): number[]; + + /** + * Time complexity is O(n) + * Space complexity of Iterative DFS equals to recursive DFS which is O(n) because of the stack + * @param pattern + * @param nodeOrPropertyName + * @constructor + */ + DFSIterative(pattern ?: 'in' | 'pre' | 'post', nodeOrPropertyName ?: NodeOrPropertyName): ResultsByProperty { + pattern = pattern || 'in'; + nodeOrPropertyName = nodeOrPropertyName || 'id'; + this._resetResults(); + if (!this.root) return this._getResultByPropertyName(nodeOrPropertyName); + // 0: visit, 1: print + const stack: { opt: 0 | 1, node: BinaryTreeNode | null | undefined }[] = [{opt: 0, node: this.root}]; + + while (stack.length > 0) { + const cur = stack.pop(); + if (!cur || !cur.node) continue; + if (cur.opt === 1) { + this._accumulatedByPropertyName(cur.node, nodeOrPropertyName); + } else { + switch (pattern) { + case 'in': + stack.push({opt: 0, node: cur.node.right}); + stack.push({opt: 1, node: cur.node}); + stack.push({opt: 0, node: cur.node.left}); + break; + case 'pre': + stack.push({opt: 0, node: cur.node.right}); + stack.push({opt: 0, node: cur.node.left}); + stack.push({opt: 1, node: cur.node}); + break; + case 'post': + stack.push({opt: 1, node: cur.node}); + stack.push({opt: 0, node: cur.node.right}); + stack.push({opt: 0, node: cur.node.left}); + break; + default: + stack.push({opt: 0, node: cur.node.right}); + stack.push({opt: 1, node: cur.node}); + stack.push({opt: 0, node: cur.node.left}); + break; + } + } + } + + return this._getResultByPropertyName(nodeOrPropertyName); + } + + levelIterative(node: BinaryTreeNode | null): BinaryTreeNodeId[]; + levelIterative(node: BinaryTreeNode | null, nodeOrPropertyName?: 'id'): BinaryTreeNodeId[]; + levelIterative(node: BinaryTreeNode | null, nodeOrPropertyName?: 'val'): T[]; + levelIterative(node: BinaryTreeNode | null, nodeOrPropertyName?: 'node'): BinaryTreeNode[]; + levelIterative(node: BinaryTreeNode | null, nodeOrPropertyName?: 'count'): number[]; + levelIterative(node: BinaryTreeNode | null, nodeOrPropertyName ?: NodeOrPropertyName): ResultsByProperty { + nodeOrPropertyName = nodeOrPropertyName || 'id'; + node = node || this.root; + if (!node) return []; + + this._resetResults(); + const queue: BinaryTreeNode[] = [node]; + + while (queue.length > 0) { + const cur = queue.shift(); + if (cur) { + this._accumulatedByPropertyName(cur, nodeOrPropertyName); + if (cur.left) { + queue.push(cur.left); + } + if (cur.right) { + queue.push(cur.right); + } + } + } + + return this._getResultByPropertyName(nodeOrPropertyName); + } + + listLevels(node: BinaryTreeNode | null): BinaryTreeNodeId[][]; + listLevels(node: BinaryTreeNode | null, nodeOrPropertyName?: 'id'): BinaryTreeNodeId[][]; + listLevels(node: BinaryTreeNode | null, nodeOrPropertyName?: 'val'): T[][]; + listLevels(node: BinaryTreeNode | null, nodeOrPropertyName?: 'node'): BinaryTreeNode[][]; + listLevels(node: BinaryTreeNode | null, nodeOrPropertyName?: 'count'): number[][]; + listLevels(node: BinaryTreeNode | null, nodeOrPropertyName?: NodeOrPropertyName): ResultByProperty[][] { + nodeOrPropertyName = nodeOrPropertyName || 'id'; + node = node || this.root; + if (!node) return []; + + const levelsNodes: ResultByProperty[][] = []; + + const collectByProperty = (node: BinaryTreeNode, level: number) => { + switch (nodeOrPropertyName) { + case 'id': + levelsNodes[level].push(node.id); + break; + case 'val': + levelsNodes[level].push(node.val); + break; + case 'node': + levelsNodes[level].push(node); + break; + case 'count': + levelsNodes[level].push(node.count); + break; + default: + levelsNodes[level].push(node.id); + break; + } + } + + if (this._loopType === LoopType.recursive) { + const _recursive = (node: BinaryTreeNode, level: number) => { + if (!levelsNodes[level]) levelsNodes[level] = []; + collectByProperty(node, level); + if (node.left) _recursive(node.left, level + 1); + if (node.right) _recursive(node.right, level + 1); + }; + + _recursive(node, 0); + } else { + const stack: [BinaryTreeNode, number][] = [[node, 0]]; + + while (stack.length > 0) { + const head = stack.pop()!; + const [node, level] = head; + + if (!levelsNodes[level]) levelsNodes[level] = []; + collectByProperty(node, level); + if (node.right) stack.push([node.right, level + 1]); + if (node.left) stack.push([node.left, level + 1]); + } + } + + return levelsNodes; + } + + getPredecessor(node: BinaryTreeNode): BinaryTreeNode { + if (node.left) { + let predecessor: BinaryTreeNode | null = node.left; + while (predecessor.right && predecessor.right !== node) { + predecessor = predecessor.right; + } + return predecessor; + } else { + return node; + } + } + + morris(): BinaryTreeNodeId[]; + morris(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'id'): BinaryTreeNodeId[]; + morris(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'val'): T[]; + morris(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'node'): BinaryTreeNode[]; + morris(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'count'): number[]; + /** + * The time complexity of Morris traversal is O(n), it's may slower than others + * The space complexity Morris traversal is O(1) because no using stack + * @param pattern + * @param nodeOrPropertyName + */ + morris(pattern?: 'in' | 'pre' | 'post', nodeOrPropertyName?: NodeOrPropertyName): ResultsByProperty { + if (this.root === null) return []; + + pattern = pattern || 'in'; + nodeOrPropertyName = nodeOrPropertyName || 'id'; + + this._resetResults(); + + let cur: BinaryTreeNode | null | undefined = this.root; + const _reverseEdge = (node: BinaryTreeNode | null | undefined) => { + let pre: BinaryTreeNode | null | undefined = null; + let next: BinaryTreeNode | null | undefined = null; + while (node) { + next = node.right; + node.right = pre; + pre = node; + node = next; + } + return pre; + }; + const _printEdge = (node: BinaryTreeNode | null) => { + const tail: BinaryTreeNode | null | undefined = _reverseEdge(node); + let cur: BinaryTreeNode | null | undefined = tail; + while (cur) { + this._accumulatedByPropertyName(cur, nodeOrPropertyName); + cur = cur.right; + } + _reverseEdge(tail); + }; + switch (pattern) { + case 'in': + while (cur) { + if (cur.left) { + const predecessor = this.getPredecessor(cur); + if (!predecessor.right) { + predecessor.right = cur; + cur = cur.left; + continue; + } else { + predecessor.right = null; + } + } + this._accumulatedByPropertyName(cur, nodeOrPropertyName); + cur = cur.right; + } + break; + case 'pre': + while (cur) { + if (cur.left) { + const predecessor = this.getPredecessor(cur); + if (!predecessor.right) { + predecessor.right = cur; + this._accumulatedByPropertyName(cur, nodeOrPropertyName); + cur = cur.left; + continue; + } else { + predecessor.right = null; + } + } else { + this._accumulatedByPropertyName(cur, nodeOrPropertyName); + } + cur = cur.right; + } + break; + case 'post': + while (cur) { + if (cur.left) { + const predecessor = this.getPredecessor(cur); + if (predecessor.right === null) { + predecessor.right = cur; + cur = cur.left; + continue; + } else { + predecessor.right = null; + _printEdge(cur.left); + } + } + cur = cur.right; + } + _printEdge(this.root); + break; + } + + return this._getResultByPropertyName(nodeOrPropertyName); + } + + // --- end additional methods --- +} \ No newline at end of file diff --git a/src/data-structures/binary-tree/bst.ts b/src/data-structures/binary-tree/bst.ts new file mode 100644 index 0000000..2393f23 --- /dev/null +++ b/src/data-structures/binary-tree/bst.ts @@ -0,0 +1,404 @@ +import { + BinaryTree, + BinaryTreeNode, + BinaryTreeNodeId, + BinaryTreeNodePropertyName, + FamilyPosition, + LoopType, +} from './binary-tree'; + +export type BSTComparator = (a: BinaryTreeNodeId, b: BinaryTreeNodeId) => number; +export type BSTDeletedResult = { deleted: BSTNode | null, needBalanced: BSTNode | null }; + +export enum CP {lt = -1, eq = 0, gt = 1} + +export class BSTNode extends BinaryTreeNode { + override clone(): BSTNode { + return new BSTNode(this.id, this.val, this.count); + } +} + +export class BST extends BinaryTree { + protected _comparator: BSTComparator = (a, b) => a - b; + + protected _compare(a: BinaryTreeNodeId, b: BinaryTreeNodeId): CP { + const compared = this._comparator(a, b); + if (compared > 0) return CP.gt; + else if (compared < 0) return CP.lt; + else return CP.eq; + } + + constructor(options?: { + comparator?: BSTComparator, + loopType?: LoopType + }) { + super(options); + if (options !== undefined) { + const {comparator} = options; + if (comparator !== undefined) { + this._comparator = comparator; + } + } + } + + override createNode(id: BinaryTreeNodeId, val: T | null, count?: number): BSTNode | null { + return val !== null ? new BSTNode(id, val, count) : null; + } + + override put(id: BinaryTreeNodeId, val: T | null, count: number = 1): BSTNode | null { + let inserted: BSTNode | null = null; + const newNode = this.createNode(id, val, count); + if (this.root === null) { + this.root = newNode; + this.size++; + this.count += newNode?.count ?? 1; + inserted = (this.root); + } else { + let cur = this.root; + let traversing = true; + while (traversing) { + if (cur !== null && newNode !== null) { + if (this._compare(cur.id, id) === CP.eq) { + if (newNode) { + cur.count += newNode.count; + this.count += newNode.count; + cur.val = newNode.val; + } + //Duplicates are not accepted. + traversing = false; + inserted = cur; + } else if (this._compare(cur.id, id) === CP.gt) { + // Traverse left of the node + if (cur.left === undefined) { + if (newNode) { + newNode.parent = cur; + newNode.familyPosition = FamilyPosition.left; + } + //Add to the left of the current node + cur.left = newNode; + this.size++; + this.count += newNode.count; + traversing = false; + inserted = cur.left; + } else { + //Traverse the left of the current node + if (cur.left) cur = cur.left; + } + } else if (this._compare(cur.id, id) === CP.lt) { + // Traverse right of the node + if (cur.right === undefined) { + if (newNode) { + newNode.parent = cur; + newNode.familyPosition = FamilyPosition.right; + } + //Add to the right of the current node + cur.right = newNode; + this.size++; + this.count += newNode.count; + traversing = false; + inserted = (cur.right); + } else { + //Traverse the left of the current node + if (cur.right) cur = cur.right; + } + } + } else { + traversing = false; + } + } + } + return inserted; + } + + override get(nodeProperty: BinaryTreeNodeId | T, propertyName ?: BinaryTreeNodePropertyName): BSTNode | null { + propertyName = propertyName ?? 'id'; + return this.getNodes(nodeProperty, propertyName, true)[0] ?? null; + } + + lastKey() { + if (this._compare(0, 1) === CP.lt) return this.getRightMost()?.id ?? 0; + else if (this._compare(0, 1) === CP.gt) return this.getLeftMost()?.id ?? 0; + else return this.getRightMost()?.id ?? 0; + } + + override remove(id: BinaryTreeNodeId, ignoreCount?: boolean): BSTDeletedResult[] { + const bstDeletedResult: BSTDeletedResult[] = []; + + if (!this.root) return bstDeletedResult; + + const curr: BSTNode | null = this.get(id); + if (!curr) return bstDeletedResult; + + const parent: BSTNode | null = curr?.parent ? curr.parent : null; + let needBalanced: BSTNode | null = null, orgCurrent = curr; + + if (curr.count > 1 && !ignoreCount) { + curr.count--; + this.count--; + } else { + if (!curr.left) { + if (!parent) { + if (curr.right !== undefined) this.root = curr.right; + } else { + switch (curr.familyPosition) { + case FamilyPosition.left: + parent.left = curr.right; + break; + case FamilyPosition.right: + parent.right = curr.right; + break; + } + needBalanced = parent; + } + } else { + const leftSubTreeMax = curr.left ? this.getRightMost(curr.left) : null; + if (leftSubTreeMax) { + const parentOfLeftSubTreeMax = leftSubTreeMax.parent; + orgCurrent = curr.swapLocation(leftSubTreeMax); + if (parentOfLeftSubTreeMax) { + if (parentOfLeftSubTreeMax.right === leftSubTreeMax) parentOfLeftSubTreeMax.right = leftSubTreeMax.left; + else parentOfLeftSubTreeMax.left = leftSubTreeMax.left; + needBalanced = parentOfLeftSubTreeMax; + } + } + } + this.size--; + this.count -= curr.count; + } + + bstDeletedResult.push({deleted: orgCurrent, needBalanced}); + return bstDeletedResult; + } + + override getNodes(nodeProperty: BinaryTreeNodeId | T, propertyName ?: BinaryTreeNodePropertyName, onlyOne ?: boolean): BSTNode[] { + propertyName = propertyName ?? 'id'; + if (!this.root) return []; + const result: BSTNode[] = []; + + if (this._loopType === LoopType.recursive) { + const _traverse = (cur: BSTNode) => { + if (this._pushByPropertyNameStopOrNot(cur, result, nodeProperty, propertyName, onlyOne)) return; + + if (!cur.left && !cur.right) return; + if (propertyName === 'id') { + if (this._compare(cur.id, nodeProperty as number) === CP.gt) cur.left && _traverse(cur.left); + if (this._compare(cur.id, nodeProperty as number) === CP.lt) cur.right && _traverse(cur.right); + } else { + cur.left && _traverse(cur.left); + cur.right && _traverse(cur.right); + } + } + + _traverse(this.root); + } else { + const queue: BSTNode[] = [this.root]; + while (queue.length > 0) { + const cur = queue.shift(); + if (cur) { + if (this._pushByPropertyNameStopOrNot(cur, result, nodeProperty, propertyName, onlyOne)) return result; + if (propertyName === 'id') { + if (this._compare(cur.id, nodeProperty as number) === CP.gt) cur.left && queue.push(cur.left); + if (this._compare(cur.id, nodeProperty as number) === CP.lt) cur.right && queue.push(cur.right); + } else { + cur.left && queue.push(cur.left); + cur.right && queue.push(cur.right); + } + } + } + } + + return result; + } + + // --- start additional functions + lesserSum(id: BinaryTreeNodeId, propertyName ?: BinaryTreeNodePropertyName): number { + propertyName = propertyName ?? 'id'; + if (!this.root) return 0; + + const getSumByPropertyName = (cur: BSTNode) => { + let needSum: number; + switch (propertyName) { + case 'id': + needSum = cur.id; + break; + case 'count': + needSum = cur.count; + break; + default: + needSum = cur.id; + break; + } + return needSum; + } + + let sum = 0; + + if (this._loopType === LoopType.recursive) { + const _traverse = (cur: BSTNode): void => { + const compared = this._compare(cur.id, id); + if (compared === CP.eq) { + if (cur.right) sum += this.subTreeSum(cur.right, propertyName); + return; + } else if (compared === CP.lt) { + if (cur.left) sum += this.subTreeSum(cur.left, propertyName); + sum += getSumByPropertyName(cur); + if (cur.right) _traverse(cur.right); + else return; + } else { + if (cur.left) _traverse(cur.left); + else return; + } + }; + + _traverse(this.root); + } else { + const queue: BSTNode[] = [this.root]; + while (queue.length > 0) { + const cur = queue.shift(); + if (cur) { + const compared = this._compare(cur.id, id); + if (compared === CP.eq) { + if (cur.right) sum += this.subTreeSum(cur.right, propertyName); + return sum; + } else if (compared === CP.lt) { // todo maybe a bug + if (cur.left) sum += this.subTreeSum(cur.left, propertyName); + sum += getSumByPropertyName(cur); + if (cur.right) queue.push(cur.right); + else return sum; + } else { + if (cur.left) queue.push(cur.left); + else return sum; + } + } + } + } + + return sum; + } + + allGreaterNodesAdd(node: BSTNode, delta: number, propertyName ?: BinaryTreeNodePropertyName): boolean { + propertyName = propertyName ?? 'id'; + if (!this.root) return false; + + const _sumByPropertyName = (cur: BSTNode) => { + switch (propertyName) { + case 'id': + cur.id += delta; + break; + case 'count': + cur.count += delta; + break; + default: + cur.id += delta; + break; + } + } + + if (this._loopType === LoopType.recursive) { + const _traverse = (cur: BSTNode) => { + const compared = this._compare(cur.id, node.id); + _sumByPropertyName(cur); + + if (!cur.left && !cur.right) return; + if (cur.left && compared === CP.gt) _traverse(cur.left); + else if (cur.right && compared === CP.gt) _traverse(cur.right); + }; + + _traverse(this.root); + return true; + } else { + const queue: BSTNode[] = [this.root]; + while (queue.length > 0) { + const cur = queue.shift(); + if (cur) { + const compared = this._compare(cur.id, node.id); + _sumByPropertyName(cur); + + if (cur.left && compared === CP.gt) queue.push(cur.left); + else if (cur.right && compared === CP.gt) queue.push(cur.right); + } + } + return true; + } + } + + balance(): boolean { + const sorted = this.DFS('in', 'node'), n = sorted.length; + this.clear(); + + if (sorted.length < 1) return false; + if (this._loopType === LoopType.recursive) { + const buildBalanceBST = (l: number, r: number) => { + if (l > r) return; + const m = l + Math.floor((r - l) / 2); + const midNode = sorted[m]; + this.put(midNode.id, midNode.val, midNode.count); + buildBalanceBST(l, m - 1); + buildBalanceBST(m + 1, r); + }; + + buildBalanceBST(0, n - 1); + return true; + } else { + const stack: [[number, number]] = [[0, n - 1]]; + while (stack.length > 0) { + const popped = stack.pop(); + if (popped) { + const [l, r] = popped; + if (l <= r) { + const m = l + Math.floor((r - l) / 2); + const midNode = sorted[m]; + this.put(midNode.id, midNode.val, midNode.count); + stack.push([m + 1, r]); + stack.push([l, m - 1]); + } + } + } + return true; + } + } + + isAVLBalanced(): boolean { + if (!this.root) return true; + + let balanced = true; + + if (this._loopType === LoopType.recursive) { + const _height = (cur: BSTNode | null | undefined): number => { + if (!cur) return 0; + const leftHeight = _height(cur.left), rightHeight = _height(cur.right); + if (Math.abs(leftHeight - rightHeight) > 1) balanced = false; + return Math.max(leftHeight, rightHeight) + 1; + }; + _height(this.root); + } else { + const stack: BSTNode[] = []; + let node: BSTNode | null | undefined = this.root, last: BSTNode | null = null, + depths: Map, number> = new Map(); + + while (stack.length > 0 || node) { + if (node) { + stack.push(node); + node = node.left; + } else { + node = stack[stack.length - 1] + if (!node.right || last === node.right) { + node = stack.pop(); + if (node) { + let left = node.left ? depths.get(node.left) ?? -1 : -1; + let right = node.right ? depths.get(node.right) ?? -1 : -1; + if (Math.abs(left - right) > 1) return false; + depths.set(node, 1 + Math.max(left, right)); + last = node; + node = null; + } + } else node = node.right + } + } + } + + return balanced; + } + + // --- end additional functions +} \ No newline at end of file diff --git a/src/data-structures/binary-tree/index.ts b/src/data-structures/binary-tree/index.ts new file mode 100644 index 0000000..62cb0df --- /dev/null +++ b/src/data-structures/binary-tree/index.ts @@ -0,0 +1,4 @@ +export * from './binary-tree'; +export * from './bst'; +export * from './binary-indexed-tree'; +export * from './segment-tree'; diff --git a/src/data-structures/binary-tree/rb-tree.ts b/src/data-structures/binary-tree/rb-tree.ts new file mode 100644 index 0000000..097b542 --- /dev/null +++ b/src/data-structures/binary-tree/rb-tree.ts @@ -0,0 +1,3 @@ +export class RBTree { + +} diff --git a/src/data-structures/binary-tree/segment-tree.ts b/src/data-structures/binary-tree/segment-tree.ts new file mode 100644 index 0000000..c6139d2 --- /dev/null +++ b/src/data-structures/binary-tree/segment-tree.ts @@ -0,0 +1,164 @@ +export type SegmentTreeNodeVal = number; + +export class SegmentTreeNode { + protected _start = 0; + get start(): number { + return this._start; + } + + set start(v: number) { + this._start = v; + } + + protected _end = 0; + get end(): number { + return this._end; + } + + set end(v: number) { + this._end = v; + } + + protected _val: SegmentTreeNodeVal | null = null; + get val(): SegmentTreeNodeVal | null { + return this._val; + } + + set val(v: SegmentTreeNodeVal | null) { + this._val = v; + } + + protected _sum = 0; + get sum(): number { + return this._sum; + } + + set sum(v: number) { + this._sum = v; + } + + protected _left: SegmentTreeNode | null = null; + get left(): SegmentTreeNode | null { + return this._left; + } + + set left(v: SegmentTreeNode | null) { + this._left = v; + } + + protected _right: SegmentTreeNode | null = null; + get right(): SegmentTreeNode | null { + return this._right; + } + + set right(v: SegmentTreeNode | null) { + this._right = v; + } + + constructor(start: number, end: number, sum: number, val?: SegmentTreeNodeVal | null) { + this._start = start; + this._end = end; + this._sum = sum; + this._val = val || null; + } +} + +export class SegmentTree { + protected _values: number[] = []; + protected _start = 0; + protected _end: number; + protected _root: SegmentTreeNode | null; + get root(): SegmentTreeNode | null { + return this._root; + } + + constructor(values: number[], start?: number, end?: number) { + start = start || 0; + end = end || values.length - 1; + this._values = values; + this._start = start; + this._end = end; + this._root = this.build(start, end); + } + + build(start: number, end: number): SegmentTreeNode { + if (start === end) { + return new SegmentTreeNode(start, end, this._values[start]); + } + const mid = start + Math.floor((end - start) / 2); + const left = this.build(start, mid); + const right = this.build(mid + 1, end); + const cur = new SegmentTreeNode(start, end, left.sum + right.sum); + cur.left = left; + cur.right = right; + return cur; + } + + updateNode(index: number, sum: number, val?: SegmentTreeNodeVal) { + const root = this.root || null; + if (!root) { + return; + } + const dfs = (cur: SegmentTreeNode, index: number, sum: number, val?: SegmentTreeNodeVal) => { + if (cur.start === cur.end && cur.start === index) { + cur.sum = sum; + // cur.val = val; + return; + } + const mid = cur.start + Math.floor((cur.end - cur.start) / 2); + if (index <= mid) { + if (cur.left) { + dfs(cur.left, index, sum, val); + } + } else { + if (cur.right) { + dfs(cur.right, index, sum, val); + } + } + if (cur.left && cur.right) { + cur.sum = cur.left.sum + cur.right.sum; + } + }; + + dfs(root, index, sum); + } + + querySumByRange(indexA: number, indexB: number): number { + const root = this.root || null; + if (!root) { + return 0; + } + + const dfs = (cur: SegmentTreeNode, i: number, j: number): number => { + if (cur.start === i && cur.end === j) { + return cur.sum; + } + const mid = cur.start + Math.floor((cur.end - cur.start) / 2); + if (j <= mid) { + // TODO after no-non-null-assertion not ensure the logic + if (cur.left) { + return dfs(cur.left, i, j); + } else { + return NaN; + } + } else if (i > mid) { + // TODO after no-non-null-assertion not ensure the logic + if (cur.right) { + // TODO after no-non-null-assertion not ensure the logic + return dfs(cur.right, i, j); + + } else { + return NaN; + } + } else { + // TODO after no-non-null-assertion not ensure the logic + if (cur.left && cur.right) { + return dfs(cur.left, i, mid) + dfs(cur.right, mid + 1, j); + } else { + return NaN; + } + } + }; + return dfs(root, indexA, indexB); + } +} diff --git a/src/data-structures/binary-tree/splay-tree.ts b/src/data-structures/binary-tree/splay-tree.ts new file mode 100644 index 0000000..6cabcdd --- /dev/null +++ b/src/data-structures/binary-tree/splay-tree.ts @@ -0,0 +1,3 @@ +export class SplayTree { + +} diff --git a/src/data-structures/binary-tree/tree-multiset.ts b/src/data-structures/binary-tree/tree-multiset.ts new file mode 100644 index 0000000..d615a02 --- /dev/null +++ b/src/data-structures/binary-tree/tree-multiset.ts @@ -0,0 +1,21 @@ +import {BST, BSTNode} from './bst'; +import {BinaryTreeNodeId} from './binary-tree'; + +export type TreeMultiSetDeletedResult = { deleted: BSTNode | null, needBalanced: BSTNode | null }; + + +export class TreeMultiSet extends BST { + override createNode(id: BinaryTreeNodeId, val: T, count?: number): BSTNode { + return new BSTNode(id, val, count); + } + + override put(id: BinaryTreeNodeId, val: T | null, count?: number): BSTNode | null { + return super.put(id, val, count); + } + + override remove(id: BinaryTreeNodeId, isUpdateAllLeftSum?: boolean): TreeMultiSetDeletedResult[] { + return super.remove(id, isUpdateAllLeftSum); + } +} + + diff --git a/src/data-structures/binary-tree/two-three-tree.ts b/src/data-structures/binary-tree/two-three-tree.ts new file mode 100644 index 0000000..639ccb7 --- /dev/null +++ b/src/data-structures/binary-tree/two-three-tree.ts @@ -0,0 +1,3 @@ +export class TwoThreeTree { + +} diff --git a/src/data-structures/graph/abstract-graph.ts b/src/data-structures/graph/abstract-graph.ts new file mode 100644 index 0000000..217aff4 --- /dev/null +++ b/src/data-structures/graph/abstract-graph.ts @@ -0,0 +1,789 @@ +import {arrayRemove, uuidV4} from '../../utils'; +import {PriorityQueue} from '../priority-queue'; + +export type VertexId = string | number; +export type DijkstraResult = + { distMap: Map, preMap: Map, seen: Set, paths: V[][], minDist: number, minPath: V[] } + | null; + +export interface I_Graph { + + containsVertex(vertexOrId: V | VertexId): boolean; + + getVertex(vertexOrId: VertexId | V): V | null; + + getVertexId(vertexOrId: V | VertexId): VertexId; + + vertexSet(): Map; + + addVertex(v: V): boolean; + + removeVertex(vertexOrId: V | VertexId): boolean; + + removeAllVertices(vertices: V[] | VertexId[]): boolean; + + degreeOf(vertexOrId: V | VertexId): number; + + edgesOf(vertexOrId: V | VertexId): E[]; + + containsEdge(src: V | VertexId, dest: V | VertexId): boolean; + + // containsEdge(e: E): boolean; + + getEdge(srcOrId: V | VertexId, destOrId: V | VertexId): E | null; + + // getAllEdges(src: V, dest: V): E[]; + + edgeSet(): E[]; + + addEdge(edge: E): boolean; + + removeEdgeBetween(srcOrId: V | VertexId, destOrId: V | VertexId): E | null; + + removeEdge(edge: E): E | null; + + // removeAllEdges(v1: VertexId | V, v2: VertexId | V): (E | null)[]; + + // removeAllEdges(edges: E[] | [VertexId, VertexId]): boolean; + + setEdgeWeight(srcOrId: V | VertexId, destOrId: V | VertexId, weight: number): boolean; + + getMinPathBetween(v1: V | VertexId, v2: V | VertexId, isWeight?: boolean): V[] | null; + + getNeighbors(vertexOrId: V | VertexId): V[]; +} + +export class AbstractVertex { + private _id: VertexId; + public get id(): VertexId { + return this._id; + } + + public set id(v: VertexId) { + this._id = v; + } + + constructor(id: VertexId) { + this._id = id; + } +} + +export abstract class AbstractEdge { + + private _weight: number; + get weight(): number { + return this._weight; + } + + set weight(v: number) { + this._weight = v; + } + + private _hashCode: string; + + get hashCode(): string { + return this._hashCode; + } + + set hashCode(v: string) { + this._hashCode = v; + } + + protected constructor(weight?: number) { + if (weight === undefined) weight = AbstractEdge.DEFAULT_EDGE_WEIGHT; + this._weight = weight; + this._hashCode = uuidV4(); + } + + static DEFAULT_EDGE_WEIGHT = 1; +} + +// Connected Component === Largest Connected Sub-Graph +export abstract class AbstractGraph implements I_Graph { + + protected _vertices: Map = new Map(); + + abstract removeEdgeBetween(srcOrId: V | VertexId, destOrId: V | VertexId): E | null; + + abstract removeEdge(edge: E): E | null; + + getVertex(vertexOrId: VertexId | V): V | null { + const vertexId = this.getVertexId(vertexOrId); + return this._vertices.get(vertexId) || null; + } + + getVertexId(vertexOrId: V | VertexId): VertexId { + return vertexOrId instanceof AbstractVertex ? vertexOrId.id : vertexOrId; + } + + containsVertex(vertexOrId: V | VertexId): boolean { + return this._vertices.has(this.getVertexId(vertexOrId)); + } + + vertexSet(): Map { + return this._vertices; + } + + abstract getEdge(srcOrId: V | null | VertexId, destOrId: V | null | VertexId): E | null; + + addVertex(newVertex: V): boolean { + if (this.containsVertex(newVertex)) { + return false; + } + this._vertices.set(newVertex.id, newVertex); + return true; + } + + removeVertex(vertexOrId: V | VertexId): boolean { + const vertexId = this.getVertexId(vertexOrId); + return this._vertices.delete(vertexId); + } + + removeAllVertices(vertices: V[] | VertexId[]): boolean { + const removed: boolean[] = []; + for (const v of vertices) { + removed.push(this.removeVertex(v)); + } + return removed.length > 0; + } + + abstract degreeOf(vertexOrId: V | VertexId): number; + + abstract edgeSet(): E[]; + + abstract edgesOf(vertexOrId: V | VertexId): E[]; + + containsEdge(v1: VertexId | V, v2: VertexId | V): boolean { + const edge = this.getEdge(v1, v2); + return !!edge; + } + + abstract addEdge(edge: E): boolean; + + setEdgeWeight(srcOrId: VertexId | V, destOrId: VertexId | V, weight: number): boolean { + const edge = this.getEdge(srcOrId, destOrId); + if (edge) { + edge.weight = weight; + return true; + } else { + return false; + } + } + + abstract getNeighbors(vertexOrId: V | VertexId): V[]; + + getAllPathsBetween(v1: V | VertexId, v2: V | VertexId): V[][] { + const paths: V[][] = []; + const vertex1 = this.getVertex(v1); + const vertex2 = this.getVertex(v2); + if (!(vertex1 && vertex2)) { + return []; + } + + const dfs = (cur: V, dest: V, visiting: Map, path: V[]) => { + visiting.set(cur, true); + + if (cur === dest) { + paths.push([vertex1, ...path]); + } + + const neighbors = this.getNeighbors(cur); + for (const neighbor of neighbors) { + if (!visiting.get(neighbor)) { + path.push(neighbor); + dfs(neighbor, dest, visiting, path); + arrayRemove(path, vertex => vertex === neighbor); + } + } + + visiting.set(cur, false); + }; + + dfs(vertex1, vertex2, new Map(), []); + return paths; + } + + + getPathSumWeight(path: V[]): number { + let sum = 0; + for (let i = 0; i < path.length; i++) { + sum += this.getEdge(path[i], path[i + 1])?.weight || 0; + } + return sum; + } + + getMinCostBetween(v1: V | VertexId, v2: V | VertexId, isWeight?: boolean): number | null { + if (isWeight === undefined) isWeight = false; + + if (isWeight) { + const allPaths = this.getAllPathsBetween(v1, v2); + let min = Infinity; + for (const path of allPaths) { + min = Math.min(this.getPathSumWeight(path), min); + } + return min; + } else { + // BFS + const vertex2 = this.getVertex(v2); + const vertex1 = this.getVertex(v1); + if (!(vertex1 && vertex2)) { + return null; + } + + const visited: Map = new Map(); + const queue: V[] = [vertex1]; + visited.set(vertex1, true); + let cost = 0; + while (queue.length > 0) { + for (let i = 0; i < queue.length; i++) { + const cur = queue.shift(); + if (cur === vertex2) { + return cost; + } + // TODO consider optimizing to AbstractGraph + if (cur !== undefined) { + const neighbors = this.getNeighbors(cur); + for (const neighbor of neighbors) { + if (!visited.has(neighbor)) { + visited.set(neighbor, true); + queue.push(neighbor); + } + } + } + } + cost++; + } + return null; + } + } + + getMinPathBetween(v1: V | VertexId, v2: V | VertexId, isWeight?: boolean): V[] | null { + if (isWeight === undefined) isWeight = false; + + if (isWeight) { + const allPaths = this.getAllPathsBetween(v1, v2); + let min = Infinity; + let minIndex = -1; + let index = 0; + for (const path of allPaths) { + const pathSumWeight = this.getPathSumWeight(path); + if (pathSumWeight < min) { + min = pathSumWeight; + minIndex = index; + } + index++; + } + return allPaths[minIndex] || null; + } else { + // BFS + let minPath: V[] = []; + const vertex1 = this.getVertex(v1); + const vertex2 = this.getVertex(v2); + if (!(vertex1 && vertex2)) { + return []; + } + + const dfs = (cur: V, dest: V, visiting: Map, path: V[]) => { + visiting.set(cur, true); + + if (cur === dest) { + minPath = [vertex1, ...path]; + return; + } + + const neighbors = this.getNeighbors(cur); + for (const neighbor of neighbors) { + if (!visiting.get(neighbor)) { + path.push(neighbor); + dfs(neighbor, dest, visiting, path); + arrayRemove(path, vertex => vertex === neighbor); + } + } + + visiting.set(cur, false); + }; + + dfs(vertex1, vertex2, new Map(), []); + return minPath; + } + } + + /** + * Dijkstra algorithm time: O(VE) space: O(V + E) + * @param src + * @param dest + * @param getMinDist + * @param genPaths + */ + dijkstraWithoutHeap(src: V | VertexId, dest?: V | VertexId | null, getMinDist?: boolean, genPaths?: boolean): DijkstraResult { + if (getMinDist === undefined) getMinDist = false; + if (genPaths === undefined) genPaths = false; + + if (dest === undefined) dest = null; + let minDist = Infinity; + let minDest: V | null = null; + let minPath: V[] = []; + const paths: V[][] = []; + + const vertices = this._vertices; + const distMap: Map = new Map(); + const seen: Set = new Set(); + const preMap: Map = new Map(); // predecessor + const srcVertex = this.getVertex(src); + + const destVertex = dest ? this.getVertex(dest) : null; + + if (!srcVertex) { + return null; + } + + for (const vertex of vertices) { + distMap.set(vertex[1], Infinity); + } + distMap.set(srcVertex, 0); + preMap.set(srcVertex, null); + + const getMinOfNoSeen = () => { + let min = Infinity; + let minV: V | null = null; + for (const [key, val] of distMap) { + if (!seen.has(key)) { + if (val < min) { + min = val; + minV = key; + } + } + } + return minV; + }; + + const getPaths = (minV: V | null) => { + for (const vertex of vertices) { + const path: V[] = [vertex[1]]; + let parent = preMap.get(vertex[1]); + while (parent) { + path.push(parent); + parent = preMap.get(parent); + } + const reversed = path.reverse(); + if (vertex[1] === minV) minPath = reversed; + paths.push(reversed); + } + }; + + for (let i = 1; i < vertices.size; i++) { + const cur = getMinOfNoSeen(); + if (cur) { + seen.add(cur); + if (destVertex && destVertex === cur) { + if (getMinDist) { + minDist = distMap.get(destVertex) || Infinity; + } + if (genPaths) { + getPaths(destVertex); + } + return {distMap, preMap, seen, paths, minDist, minPath}; + } + const neighbors = this.getNeighbors(cur); + for (const neighbor of neighbors) { + if (!seen.has(neighbor)) { + const edge = this.getEdge(cur, neighbor); + if (edge) { + const curFromMap = distMap.get(cur); + const neighborFromMap = distMap.get(neighbor); + // TODO after no-non-null-assertion not ensure the logic + if (curFromMap !== undefined && neighborFromMap !== undefined) { + if (edge.weight + curFromMap < neighborFromMap) { + distMap.set(neighbor, edge.weight + curFromMap); + preMap.set(neighbor, cur); + } + } + + } + } + } + } + } + + getMinDist && distMap.forEach((d, v) => { + if (v !== srcVertex) { + if (d < minDist) { + minDist = d; + if (genPaths) minDest = v; + } + } + }); + + genPaths && getPaths(minDest); + + return {distMap, preMap, seen, paths, minDist, minPath}; + } + + + /** + * Dijkstra algorithm time: O(logVE) space: O(V + E) + * @param src + * @param dest + * @param getMinDist + * @param genPaths + */ + dijkstra(src: V | VertexId, dest?: V | VertexId | null, getMinDist?: boolean, genPaths?: boolean): DijkstraResult { + if (getMinDist === undefined) getMinDist = false; + if (genPaths === undefined) genPaths = false; + + if (dest === undefined) dest = null; + let minDist = Infinity; + let minDest: V | null = null; + let minPath: V[] = []; + const paths: V[][] = []; + const vertices = this._vertices; + const distMap: Map = new Map(); + const seen: Set = new Set(); + const preMap: Map = new Map(); // predecessor + + const srcVertex = this.getVertex(src); + const destVertex = dest ? this.getVertex(dest) : null; + + if (!srcVertex) { + return null; + } + + for (const vertex of vertices) { + distMap.set(vertex[1], Infinity); + } + + const heap = new PriorityQueue<{ id: number, val: V }>({comparator: (a, b) => a.id - b.id}); + heap.offer({id: 0, val: srcVertex}); + + distMap.set(srcVertex, 0); + preMap.set(srcVertex, null); + + const getPaths = (minV: V | null) => { + for (const vertex of vertices) { + const path: V[] = [vertex[1]]; + let parent = preMap.get(vertex[1]); + while (parent) { + path.push(parent); + parent = preMap.get(parent); + } + const reversed = path.reverse(); + if (vertex[1] === minV) minPath = reversed; + paths.push(reversed); + } + }; + + while (heap.size > 0) { + const curHeapNode = heap.poll(); + const dist = curHeapNode?.id; + const cur = curHeapNode?.val; + if (dist !== undefined) { + if (cur) { + seen.add(cur); + if (destVertex && destVertex === cur) { + if (getMinDist) { + minDist = distMap.get(destVertex) || Infinity; + } + if (genPaths) { + getPaths(destVertex); + } + return {distMap, preMap, seen, paths, minDist, minPath}; + } + const neighbors = this.getNeighbors(cur); + for (const neighbor of neighbors) { + if (!seen.has(neighbor)) { + const weight = this.getEdge(cur, neighbor)?.weight; + if (typeof weight === 'number') { + const distSrcToNeighbor = distMap.get(neighbor); + if (distSrcToNeighbor) { + if (dist + weight < distSrcToNeighbor) { + heap.offer({id: dist + weight, val: neighbor}); + preMap.set(neighbor, cur); + distMap.set(neighbor, dist + weight); + } + } + } + } + } + } + } + } + + + if (getMinDist) { + distMap.forEach((d, v) => { + if (v !== srcVertex) { + if (d < minDist) { + minDist = d; + if (genPaths) minDest = v; + } + } + }); + } + + + if (genPaths) { + getPaths(minDest); + } + + + return {distMap, preMap, seen, paths, minDist, minPath}; + } + + abstract getEndsOfEdge(edge: E): [V, V] | null; + + + /** + * BellmanFord time:O(VE) space:O(V) + * one to rest pairs + * @param src + * @param scanNegativeCycle + * @param getMin + * @param genPath + */ + bellmanFord(src: V | VertexId, scanNegativeCycle?: boolean, getMin?: boolean, genPath?: boolean) { + if (getMin === undefined) getMin = false; + if (genPath === undefined) genPath = false; + + const srcVertex = this.getVertex(src); + const paths: V[][] = []; + const distMap: Map = new Map(); + const preMap: Map = new Map(); // predecessor + let min = Infinity; + let minPath: V[] = []; + // TODO + let hasNegativeCycle: boolean | undefined = undefined; + if (scanNegativeCycle) hasNegativeCycle = false; + if (!srcVertex) return {hasNegativeCycle, distMap, preMap, paths, min, minPath}; + + const vertices = this._vertices; + const numOfVertices = vertices.size; + const edges = this.edgeSet(); + const numOfEdges = edges.length; + + this._vertices.forEach(vertex => { + distMap.set(vertex, Infinity); + }); + + distMap.set(srcVertex, 0); + + for (let i = 1; i < numOfVertices; ++i) { + for (let j = 0; j < numOfEdges; ++j) { + const ends = this.getEndsOfEdge(edges[j]); + if (ends) { + const [s, d] = ends; + const weight = edges[j].weight; + const sWeight = distMap.get(s); + const dWeight = distMap.get(d); + if (sWeight !== undefined && dWeight !== undefined) { + if (distMap.get(s) !== Infinity && sWeight + weight < dWeight) { + distMap.set(d, sWeight + weight); + genPath && preMap.set(d, s); + } + } + } + } + } + + let minDest: V | null = null; + if (getMin) { + distMap.forEach((d, v) => { + if (v !== srcVertex) { + if (d < min) { + min = d; + if (genPath) minDest = v; + } + } + }); + } + + if (genPath) { + for (const vertex of vertices) { + const path: V[] = [vertex[1]]; + let parent = preMap.get(vertex[1]); + while (parent !== undefined) { + path.push(parent); + parent = preMap.get(parent); + } + const reversed = path.reverse(); + if (vertex[1] === minDest) minPath = reversed; + paths.push(reversed); + } + } + + for (let j = 0; j < numOfEdges; ++j) { + const ends = this.getEndsOfEdge(edges[j]); + if (ends) { + const [s] = ends; + const weight = edges[j].weight; + const sWeight = distMap.get(s); + if (sWeight) { + if (sWeight !== Infinity && sWeight + weight < sWeight) hasNegativeCycle = true; + } + } + } + + return {hasNegativeCycle, distMap, preMap, paths, min, minPath}; + } + + /** + * Floyd algorithm time: O(V^3) space: O(V^2), not support graph with negative weight cycle + * all pairs + */ + floyd(): { costs: number[][], predecessor: (V | null)[][] } { + const idAndVertices = [...this._vertices]; + const n = idAndVertices.length; + + const costs: number[][] = []; + const predecessor: (V | null)[][] = []; + // successors + + for (let i = 0; i < n; i++) { + costs[i] = []; + predecessor[i] = []; + for (let j = 0; j < n; j++) { + predecessor[i][j] = null; + } + } + + for (let i = 0; i < n; i++) { + for (let j = 0; j < n; j++) { + costs[i][j] = this.getEdge(idAndVertices[i][1], idAndVertices[j][1])?.weight || Infinity; + } + } + + for (let k = 0; k < n; k++) { + for (let i = 0; i < n; i++) { + for (let j = 0; j < n; j++) { + if (costs[i][j] > costs[i][k] + costs[k][j]) { + costs[i][j] = costs[i][k] + costs[k][j]; + predecessor[i][j] = idAndVertices[k][1]; + } + } + } + } + return {costs, predecessor}; + + } + + + /**--- start find cycles --- */ + + /** + * Tarjan is an algorithm based on DFS,which is used to solve the connectivity problem of graphs. + * Tarjan can find cycles in directed or undirected graph + * Tarjan can find the articulation points and bridges(critical edges) of undirected graphs in linear time, + * Tarjan solve the bi-connected components of undirected graphs; + * Tarjan can find the SSC(strongly connected components), articulation points, and bridges of directed graphs. + */ + tarjan(needArticulationPoints?: boolean, needBridges?: boolean, needSCCs?: boolean, needCycles?: boolean) { + // !! in undirected graph we will not let child visit parent when DFS + // !! articulation point(in DFS search tree not in graph): (cur !== root && cur.has(child)) && (low(child) >= dfn(cur)) || (cur === root && cur.children() >= 2) + // !! bridge: low(child) > dfn(cur) + + const defaultConfig = false; + if (needArticulationPoints === undefined) needArticulationPoints = defaultConfig; + if (needBridges === undefined) needBridges = defaultConfig; + if (needSCCs === undefined) needSCCs = defaultConfig; + if (needCycles === undefined) needCycles = defaultConfig; + + const dfnMap: Map = new Map(); + const lowMap: Map = new Map(); + const vertices = this._vertices; + vertices.forEach(v => { + dfnMap.set(v, -1); + lowMap.set(v, Infinity); + }); + + const [root] = vertices.values(); + + const articulationPoints: V[] = []; + const bridges: E[] = []; + let dfn = 0; + const dfs = (cur: V, parent: V | null) => { + dfn++; + dfnMap.set(cur, dfn); + lowMap.set(cur, dfn); + + const neighbors = this.getNeighbors(cur); + let childCount = 0; // child in DFS tree not child in graph + for (const neighbor of neighbors) { + if (neighbor !== parent) { + if (dfnMap.get(neighbor) === -1) { + childCount++; + dfs(neighbor, cur); + } + const childLow = lowMap.get(neighbor); + const curLow = lowMap.get(cur); + // TODO after no-non-null-assertion not ensure the logic + if (curLow !== undefined && childLow !== undefined) { + lowMap.set(cur, Math.min(curLow, childLow)); + } + const curFromMap = dfnMap.get(cur); + if (childLow !== undefined && curFromMap !== undefined) { + if (needArticulationPoints) { + if ((cur === root && childCount >= 2) || ((cur !== root) && (childLow >= curFromMap))) { + // todo not ensure the logic if (cur === root && childCount >= 2 || ((cur !== root) && (childLow >= curFromMap))) { + articulationPoints.push(cur); + } + } + + if (needBridges) { + if (childLow > curFromMap) { + const edgeCurToNeighbor = this.getEdge(cur, neighbor); + if (edgeCurToNeighbor) { + bridges.push(edgeCurToNeighbor); + } + } + } + } + } + } + + }; + + dfs(root, null); + + let SCCs: Map = new Map(); + + const getSCCs = () => { + const SCCs: Map = new Map(); + lowMap.forEach((low, vertex) => { + if (!SCCs.has(low)) { + SCCs.set(low, [vertex]); + } else { + SCCs.get(low)?.push(vertex); + } + }); + return SCCs; + }; + + if (needSCCs) { + SCCs = getSCCs(); + } + + const cycles: Map = new Map(); + if (needCycles) { + let SCCs: Map = new Map(); + if (SCCs.size < 1) { + SCCs = getSCCs(); + } + + SCCs.forEach((SCC, low) => { + if (SCC.length > 1) { + cycles.set(low, SCC); + } + }); + } + + return {dfnMap, lowMap, bridges, articulationPoints, SCCs, cycles}; + } + + + // unionFind() { + // } + + /**--- end find cycles --- */ + + + // Minimum Spanning Tree +} diff --git a/src/data-structures/graph/directed-graph.ts b/src/data-structures/graph/directed-graph.ts new file mode 100644 index 0000000..9090135 --- /dev/null +++ b/src/data-structures/graph/directed-graph.ts @@ -0,0 +1,322 @@ +import {arrayRemove} from '../../utils'; +import {AbstractEdge, AbstractGraph, AbstractVertex, VertexId} from './abstract-graph'; + +export class DirectedVertex extends AbstractVertex { + constructor(id: VertexId) { + super(id); + } +} + +export class DirectedEdge extends AbstractEdge { + constructor(src: VertexId, dest: VertexId, weight?: number) { + super(weight); + this._src = src; + this._dest = dest; + } + + private _src: VertexId; + get src(): VertexId { + return this._src; + } + + set src(v: VertexId) { + this._src = v; + } + + + private _dest: VertexId; + get dest(): VertexId { + return this._dest; + } + + set dest(v: VertexId) { + this._dest = v; + } +} + +export interface I_DirectedGraph { + incomingEdgesOf(vertex: V): E[]; + + outgoingEdgesOf(vertex: V): E[]; + + inDegreeOf(vertexOrId: V | VertexId): number; + + outDegreeOf(vertexOrId: V | VertexId): number; + + getEdgeSrc(e: E): V | null; + + getEdgeDest(e: E): V | null; +} + +// 0 means unknown, 1 means visiting, 2 means visited; +export type TopologicalStatus = 0 | 1 | 2; + +// Strongly connected, One direction connected, Weakly connected +export class DirectedGraph extends AbstractGraph implements I_DirectedGraph { + + protected _outEdgeMap: Map = new Map(); + + protected _inEdgeMap: Map = new Map(); + + constructor() { + super(); + } + + getEdge(srcOrId: V | null | VertexId, destOrId: V | null | VertexId): E | null { + let edges: E[] = []; + + if (srcOrId !== null && destOrId !== null) { + const src: V | null = this.getVertex(srcOrId); + const dest: V | null = this.getVertex(destOrId); + + if (src && dest) { + const srcOutEdges = this._outEdgeMap.get(src); + if (srcOutEdges) { + edges = srcOutEdges.filter(edge => edge.dest === dest.id); + } + } + } + + return edges[0] || null; + } + + addEdge(edge: E): boolean { + if (!(this.containsVertex(edge.src) && this.containsVertex(edge.dest))) { + return false; + } + + const srcVertex = this.getVertex(edge.src); + const destVertex = this.getVertex(edge.dest); + + // TODO after no-non-null-assertion not ensure the logic + if (srcVertex && destVertex) { + const srcOutEdges = this._outEdgeMap.get(srcVertex); + if (srcOutEdges) { + srcOutEdges.push(edge); + } else { + this._outEdgeMap.set(srcVertex, [edge]); + } + + const destInEdges = this._inEdgeMap.get(destVertex); + if (destInEdges) { + destInEdges.push(edge); + } else { + this._inEdgeMap.set(destVertex, [edge]); + } + return true; + } else { + return false; + } + } + + removeEdgeBetween(srcOrId: V | VertexId, destOrId: V | VertexId): E | null { + + const src: V | null = this.getVertex(srcOrId); + const dest: V | null = this.getVertex(destOrId); + let removed: E | null = null; + if (!src || !dest) { + return null; + } + + const srcOutEdges = this._outEdgeMap.get(src); + if (srcOutEdges) { + arrayRemove(srcOutEdges, edge => edge.dest === dest.id); + } + + const destInEdges = this._inEdgeMap.get(dest); + if (destInEdges) { + removed = arrayRemove(destInEdges, edge => edge.src === src.id)[0] || null; + } + return removed; + } + + removeEdge(edge: E): E | null { + let removed: E | null = null; + const src = this.getVertex(edge.src); + const dest = this.getVertex(edge.dest); + if (src && dest) { + const srcOutEdges = this._outEdgeMap.get(src); + if (srcOutEdges && srcOutEdges.length > 0) { + arrayRemove(srcOutEdges, edge => edge.src === src.id); + } + + const destInEdges = this._inEdgeMap.get(dest); + if (destInEdges && destInEdges.length > 0) { + removed = arrayRemove(destInEdges, edge => edge.dest === dest.id)[0]; + } + + } + + return removed; + } + + removeAllEdges(src: VertexId | V, dest: VertexId | V): E[] { + return []; + } + + incomingEdgesOf(vertexOrId: V | VertexId): E[] { + const target = this.getVertex(vertexOrId); + if (target) { + return this._inEdgeMap.get(target) || []; + } + return []; + } + + outgoingEdgesOf(vertexOrId: V | VertexId): E[] { + const target = this.getVertex(vertexOrId); + if (target) { + return this._outEdgeMap.get(target) || []; + } + return []; + } + + degreeOf(vertexOrId: VertexId | V): number { + return this.outDegreeOf(vertexOrId) + this.inDegreeOf(vertexOrId); + } + + inDegreeOf(vertexOrId: VertexId | V): number { + return this.incomingEdgesOf(vertexOrId).length; + } + + outDegreeOf(vertexOrId: VertexId | V): number { + return this.outgoingEdgesOf(vertexOrId).length; + } + + edgesOf(vertexOrId: VertexId | V): E[] { + return [...this.outgoingEdgesOf(vertexOrId), ...this.incomingEdgesOf(vertexOrId)]; + } + + getEdgeSrc(e: E): V | null { + return this.getVertex(e.src); + } + + getEdgeDest(e: E): V | null { + return this.getVertex(e.dest); + } + + getDestinations(vertex: V | null): V[] { + if (vertex === null) { + return []; + } + const destinations: V[] = []; + const outgoingEdges = this.outgoingEdgesOf(vertex); + for (const outEdge of outgoingEdges) { + const child = this.getEdgeDest(outEdge); + if (child) { + destinations.push(child); + } + } + return destinations; + } + + /**--- start find cycles --- */ + + /** + * when stored with adjacency list time: O(V+E) + * when stored with adjacency matrix time: O(V^2) + */ + topologicalSort(): V[] | null { + // vector> g; + // vector color; + // int last; + // bool hasCycle; + // + // bool topo_sort() { + // int n = g.size(); + // vector degree(n, 0); + // queue q; + // for (int i = 0; i < n; i++) { + // degree[i] = g[i].size(); + // if (degree[i] <= 1) { + // q.push(i); + // } + // } + // int cnt = 0; + // while (!q.empty()) { + // cnt++; + // int root = q.front(); + // q.pop(); + // for (auto child : g[root]) { + // degree[child]--; + // if (degree[child] == 1) { + // q.push(child); + // } + // } + // } + // return (cnt != n); + // } + // When judging whether there is a cycle in the undirected graph, all nodes with degree of **<= 1** are enqueued + // When judging whether there is a cycle in the directed graph, all nodes with **in degree = 0** are enqueued + const statusMap: Map = new Map(); + for (const entry of this._vertices) { + statusMap.set(entry[1], 0); + } + + const sorted: V[] = []; + let hasCycle = false; + const dfs = (cur: V) => { + statusMap.set(cur, 1); + const children = this.getDestinations(cur); + for (const child of children) { + const childStatus = statusMap.get(child); + if (childStatus === 0) { + dfs(child); + } else if (childStatus === 1) { + hasCycle = true; + } + } + statusMap.set(cur, 2); + sorted.push(cur); + }; + + for (const entry of this._vertices) { + if (statusMap.get(entry[1]) === 0) { + dfs(entry[1]); + } + } + + if (hasCycle) { + return null; + } + return sorted.reverse(); + } + + /**--- end find cycles --- */ + + edgeSet(): E[] { + let edges: E[] = []; + this._outEdgeMap.forEach(outEdges => { + edges = [...edges, ...outEdges]; + }); + return edges; + } + + getNeighbors(vertexOrId: V | VertexId): V[] { + const neighbors: V[] = []; + const vertex = this.getVertex(vertexOrId); + if (vertex) { + const outEdges = this.outgoingEdgesOf(vertex); + for (const outEdge of outEdges) { + const neighbor = this.getVertex(outEdge.dest); + // TODO after no-non-null-assertion not ensure the logic + if (neighbor) { + neighbors.push(neighbor); + } + } + } + return neighbors; + } + + getEndsOfEdge(edge: E): [V, V] | null { + if (!this.containsEdge(edge.src, edge.dest)) { + return null; + } + const v1 = this.getVertex(edge.src); + const v2 = this.getVertex(edge.dest); + if (v1 && v2) { + return [v1, v2]; + } else { + return null; + } + } +} diff --git a/src/data-structures/graph/index.ts b/src/data-structures/graph/index.ts new file mode 100644 index 0000000..8bcd2a0 --- /dev/null +++ b/src/data-structures/graph/index.ts @@ -0,0 +1,3 @@ +export * from './abstract-graph'; +export * from './directed-graph'; +export * from './undirected-graph'; diff --git a/src/data-structures/graph/undirected-graph.ts b/src/data-structures/graph/undirected-graph.ts new file mode 100644 index 0000000..34d8b48 --- /dev/null +++ b/src/data-structures/graph/undirected-graph.ts @@ -0,0 +1,154 @@ +import {arrayRemove} from '../../utils'; +import {AbstractEdge, AbstractGraph, AbstractVertex, VertexId} from './abstract-graph'; + +export class UndirectedVertex extends AbstractVertex { + constructor(id: VertexId) { + super(id); + } +} + +export class UndirectedEdge extends AbstractEdge { + private _vertices: [VertexId, VertexId]; + + public get vertices() { + return this._vertices; + } + + public set vertices(v: [VertexId, VertexId]) { + this._vertices = v; + } + + constructor(v1: VertexId, v2: VertexId, weight?: number) { + super(weight); + this._vertices = [v1, v2]; + } +} + +export class UndirectedGraph extends AbstractGraph { + constructor() { + super(); + } + + protected _edges: Map = new Map(); + + getEdge(v1: V | null | VertexId, v2: V | null | VertexId): E | null { + let edges: E[] | undefined = []; + + if (v1 !== null && v2 !== null) { + const vertex1: V | null = this.getVertex(v1); + const vertex2: V | null = this.getVertex(v2); + + if (vertex1 && vertex2) { + edges = this._edges.get(vertex1)?.filter(e => e.vertices.includes(vertex2.id)); + } + } + + return edges ? edges[0] || null : null; + } + + addEdge(edge: E): boolean { + for (const end of edge.vertices) { + const endVertex = this.getVertex(end); + if (endVertex === null) return false; + if (endVertex) { + const edges = this._edges.get(endVertex); + if (edges) { + edges.push(edge); + } else { + this._edges.set(endVertex, [edge]); + } + } + } + return true; + } + + removeEdgeBetween(v1: V | VertexId, v2: V | VertexId): E | null { + + const vertex1: V | null = this.getVertex(v1); + const vertex2: V | null = this.getVertex(v2); + + if (!vertex1 || !vertex2) { + return null; + } + + const v1Edges = this._edges.get(vertex1); + let removed: E | null = null; + if (v1Edges) { + removed = arrayRemove(v1Edges, e => e.vertices.includes(vertex2.id))[0] || null; + } + const v2Edges = this._edges.get(vertex2); + if (v2Edges) { + arrayRemove(v2Edges, e => e.vertices.includes(vertex1.id)); + } + return removed; + } + + + removeEdge(edge: E): E | null { + return this.removeEdgeBetween(edge.vertices[0], edge.vertices[1]); + } + + degreeOf(vertexOrId: VertexId | V): number { + const vertex = this.getVertex(vertexOrId); + if (vertex) { + return this._edges.get(vertex)?.length || 0; + } else { + return 0; + } + } + + edgesOf(vertexOrId: VertexId | V): E[] { + const vertex = this.getVertex(vertexOrId); + if (vertex) { + return this._edges.get(vertex) || []; + } else { + return []; + } + } + + edgeSet(): E[] { + const edgeSet: Set = new Set(); + this._edges.forEach(edges => { + edges.forEach(edge => { + edgeSet.add(edge); + }); + }); + return [...edgeSet]; + } + + getEdgesOf(vertexOrId: V | VertexId): E[] { + const vertex = this.getVertex(vertexOrId); + if (!vertex) { + return []; + } + return this._edges.get(vertex) || []; + } + + getNeighbors(vertexOrId: V | VertexId): V[] { + const neighbors: V[] = []; + const vertex = this.getVertex(vertexOrId); + if (vertex) { + const neighborEdges = this.getEdgesOf(vertex); + for (const edge of neighborEdges) { + const neighbor = this.getVertex(edge.vertices.filter(e => e !== vertex.id)[0]); + if (neighbor) { + neighbors.push(neighbor); + } + } + } + return neighbors; + } + + getEndsOfEdge(edge: E): [V, V] | null { + if (!this.containsEdge(edge.vertices[0], edge.vertices[1])) { + return null; + } + const v1 = this.getVertex(edge.vertices[0]); + const v2 = this.getVertex(edge.vertices[1]); + if (v1 && v2) { + return [v1, v2]; + } else { + return null; + } + } +} diff --git a/src/data-structures/hash/coordinate-map.ts b/src/data-structures/hash/coordinate-map.ts new file mode 100644 index 0000000..ada0907 --- /dev/null +++ b/src/data-structures/hash/coordinate-map.ts @@ -0,0 +1,24 @@ +export class CoordinateSet extends Map { + private readonly _joint: string = '_'; + + constructor(joint?: string) { + super(); + if (joint !== undefined) this._joint = joint; + } + + override has(key: number[]) { + return super.has(key.join(this._joint)); + } + + override set(key: number[], value: V) { + return super.set(key.join(this._joint), value); + } + + override get(key: number[]) { + return super.get(key.join(this._joint)); + } + + override delete(key: number[]) { + return super.delete(key.join(this._joint)); + } +} \ No newline at end of file diff --git a/src/data-structures/hash/coordinate-set.ts b/src/data-structures/hash/coordinate-set.ts new file mode 100644 index 0000000..7878bdb --- /dev/null +++ b/src/data-structures/hash/coordinate-set.ts @@ -0,0 +1,20 @@ +export class CoordinateSet extends Set { + private readonly _joint: string = '_'; + + constructor(joint?: string) { + super(); + if (joint !== undefined) this._joint = joint; + } + + override has(value: number[]) { + return super.has(value.join(this._joint)); + } + + override add(value: number[]) { + return super.add(value.join(this._joint)); + } + + override delete(value: number[]) { + return super.delete(value.join(this._joint)); + } +} \ No newline at end of file diff --git a/src/data-structures/hash/hash-table.ts b/src/data-structures/hash/hash-table.ts new file mode 100644 index 0000000..509db18 --- /dev/null +++ b/src/data-structures/hash/hash-table.ts @@ -0,0 +1 @@ +export {}; diff --git a/src/data-structures/hash/index.ts b/src/data-structures/hash/index.ts new file mode 100644 index 0000000..5d31238 --- /dev/null +++ b/src/data-structures/hash/index.ts @@ -0,0 +1 @@ +export * from './hash-table'; diff --git a/src/data-structures/hash/pair.ts b/src/data-structures/hash/pair.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/src/data-structures/hash/pair.ts @@ -0,0 +1 @@ +export {}; diff --git a/src/data-structures/hash/tree-map.ts b/src/data-structures/hash/tree-map.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/src/data-structures/hash/tree-map.ts @@ -0,0 +1 @@ +export {}; diff --git a/src/data-structures/hash/tree-set.ts b/src/data-structures/hash/tree-set.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/src/data-structures/hash/tree-set.ts @@ -0,0 +1 @@ +export {}; diff --git a/src/data-structures/heap/heap.ts b/src/data-structures/heap/heap.ts new file mode 100644 index 0000000..c60e08a --- /dev/null +++ b/src/data-structures/heap/heap.ts @@ -0,0 +1,136 @@ +import {PriorityQueue} from '../priority-queue'; + +export interface HeapOptions { + priority?: (element: T) => number; +} + +export interface HeapItem { + priority: number; + element: T | null; +} + + +/** + * @copyright 2021 Pablo Rios + * @license MIT + * + * @abstract + * @class Heap + */ +export abstract class Heap { + protected abstract _pq: PriorityQueue>; + protected _priorityCb: (element: T) => number; + + /** + * Creates a priority queue + * @public + * @params {object} [options] + */ + protected constructor(options?: HeapOptions) { + if (options) { + const {priority} = options; + if (priority !== undefined && typeof priority !== 'function') { + throw new Error('.constructor expects a valid priority function'); + } + this._priorityCb = priority || ((el) => +el); + } else { + this._priorityCb = (el) => +el; + } + } + + /** + * @public + * @returns {number} + */ + get size(): number { + return this._pq.size; + } + + /** + * @public + * @returns {boolean} + */ + isEmpty(): boolean { + return this._pq.size < 1; + } + + /** + * Returns an element with highest priority in the queue + * @public + * @returns {object} + */ + peek(): HeapItem | null { + return this._pq.peek(); + } + + /** + * Returns an element with lowest priority in the queue + * @public + * @returns {object} + */ + peekLast(): HeapItem | null { + return this._pq.leaf(); + } + + /** + * Adds an element to the queue + * @public + * @param {any} element + * @param priority + * @throws {Error} if priority is not a valid number + */ + offer(element: T, priority?: number): Heap { + if (typeof element === 'number') { + priority = element; + } else { + if (priority === undefined) { + throw new Error('.offer expects a numeric priority'); + } + } + + if (priority && Number.isNaN(+priority)) { + throw new Error('.offer expects a numeric priority'); + } + + if (Number.isNaN(+priority) && Number.isNaN(this._priorityCb(element))) { + throw new Error( + '.offer expects a numeric priority ' + + 'or a constructor callback that returns a number' + ); + } + + const _priority = !Number.isNaN(+priority) ? priority : this._priorityCb(element); + this._pq.offer({priority: _priority, element}); + return this; + } + + /** + * Removes and returns an element with highest priority in the queue + * @public + * @returns {object} + */ + poll(): HeapItem | null { + const top = this._pq.poll(); + if (!top) { + return null; + } + return top; + } + + /** + * Returns a sorted list of elements + * @public + * @returns {array} + */ + toArray(): HeapItem[] { + return this._pq.toArray(); + } + + /** + * Clears the queue + * @public + */ + clear(): void { + this._pq.clear(); + } +} diff --git a/src/data-structures/heap/index.ts b/src/data-structures/heap/index.ts new file mode 100644 index 0000000..76302cd --- /dev/null +++ b/src/data-structures/heap/index.ts @@ -0,0 +1,3 @@ +export * from './max-heap'; +export * from './min-heap'; +export * from './heap'; diff --git a/src/data-structures/heap/max-heap.ts b/src/data-structures/heap/max-heap.ts new file mode 100644 index 0000000..b207cdf --- /dev/null +++ b/src/data-structures/heap/max-heap.ts @@ -0,0 +1,22 @@ +/** + * @copyright 2020 Pablo Rios + * @license MIT + */ + +import {Heap, HeapItem, HeapOptions} from './heap'; +import {PriorityQueue} from '../priority-queue'; + +/** + * @class MaxHeap + * @extends Heap + */ +export class MaxHeap extends Heap { + protected _pq: PriorityQueue>; + + constructor(options?: HeapOptions) { + super(options); + this._pq = new PriorityQueue>({ + comparator: (a, b) => b.priority - a.priority + }); + } +} diff --git a/src/data-structures/heap/min-heap.ts b/src/data-structures/heap/min-heap.ts new file mode 100644 index 0000000..e639089 --- /dev/null +++ b/src/data-structures/heap/min-heap.ts @@ -0,0 +1,24 @@ +/** + * @copyright 2020 Pablo Rios + * @license MIT + */ + +import {Heap, HeapItem, HeapOptions} from './heap'; +import {PriorityQueue} from '../priority-queue'; + +/** + * @class MinHeap + * @extends Heap + */ +export class MinHeap extends Heap { + protected _pq: PriorityQueue>; + + constructor(options?: HeapOptions) { + super(options); + this._pq = new PriorityQueue>({ + comparator: (a, b) => a.priority - b.priority + }); + } +} + + diff --git a/src/data-structures/index.ts b/src/data-structures/index.ts new file mode 100644 index 0000000..1c8a80e --- /dev/null +++ b/src/data-structures/index.ts @@ -0,0 +1,10 @@ +export * from './hash'; +export * from './linked-list'; +export * from './stack'; +export * from './queue'; +export * from './graph'; +export * from './binary-tree'; +export * from './heap'; +export * from './priority-queue'; +export * from './matrix'; + diff --git a/src/data-structures/linked-list/doubly-linked-list.ts b/src/data-structures/linked-list/doubly-linked-list.ts new file mode 100644 index 0000000..fe411a6 --- /dev/null +++ b/src/data-structures/linked-list/doubly-linked-list.ts @@ -0,0 +1,258 @@ +// 操作 常见名称 Ada Java JavaScript C++ Python Perl PHP Ruby +// 尾部插入 inject, snoc Append offerLast push push_back append push array_push push +// 头部插入 push, cons Prepend offerFirst unshift push_front appendleft unshift array_unshift unshift +// 尾部删除 eject Delete_Last pollLast pop pop_back pop pop array_pop pop +// 头部删除 pop Delete_First pollFirst shift pop_front popleft shift array_shift shift +// 查看尾部 Last_Element peekLast [length - 1] back [-1] $array[-1] end last +// 查看头部 First_Element peekFirst [0] front [0] $array[0] reset first + +export class DoublyLinkedListNode { + val: T; + next: DoublyLinkedListNode | null; + prev: DoublyLinkedListNode | null; + + constructor(nodeValue: T) { + this.val = nodeValue; + this.next = null; + this.prev = null; + } +} + +export type DoublyLinkedListGetBy = 'node' | 'val'; + +export class DoublyLinkedList { + private _first: DoublyLinkedListNode | null = null; + private _last: DoublyLinkedListNode | null = null; + private _size = 0; + get size(): number { + return this._size; + } + + set size(v: number) { + this._size = v; + } + + /** + * Adds a node at the beginning of the linked list + * @param val Value to be stored at the beginning of the linked list + */ + offerFirst(val: T): boolean { + const newNode = new DoublyLinkedListNode(val); + if (this._size === 0) { + this._first = newNode; + this._last = newNode; + } else { + if (this._first) this._first.prev = newNode; + newNode.next = this._first; + this._first = newNode; + } + this._size++; + return true; + } + + /** + * Adds a node to the end of the linked list + * @param val Value to be stored in the Doubly linked list node + */ + offerLast(val: T): boolean { + const newNode = new DoublyLinkedListNode(val); + if (this._size === 0) { + this._first = newNode; + this._last = newNode; + } else { + if (this._last) this._last.next = newNode; + newNode.prev = this._last; + this._last = newNode; + } + this._size++; + return true; + } + + peekFirst(): T | null; + peekFirst(by: 'val'): T | null; + peekFirst(by: 'node'): DoublyLinkedListNode | null; + peekFirst(by?: DoublyLinkedListGetBy): T | DoublyLinkedListNode | null { + switch (by) { + case 'node': + return this._first ?? null; + case 'val': + return this._first?.val ?? null; + default: + return this._first?.val ?? null; + } + } + + peekLast(): T | null; + peekLast(by: 'val'): T | null; + peekLast(by: 'node'): DoublyLinkedListNode | null; + peekLast(by: DoublyLinkedListGetBy = 'val'): T | DoublyLinkedListNode | null { + switch (by) { + case 'node': + return this._last ?? null; + case 'val': + return this._last?.val ?? null; + default: + return this._last?.val ?? null; + } + } + + pollFirst(): T | null; + pollFirst(by: 'val'): T | null; + pollFirst(by: 'node'): DoublyLinkedListNode | null; + /** + * Removes a node form the beginning of the linked list and will return the node val + */ + pollFirst(by: DoublyLinkedListGetBy = 'val'): T | DoublyLinkedListNode | null { + if (this._size === 0) return null; + const oldHead = this._first; + if (this._size === 1) { + this._first = null; + this._last = null; + } else { + this._first = oldHead?.next ?? null; + if (this._first) this._first.prev = null; + if (oldHead) oldHead.next = null; + } + this._size--; + switch (by) { + case 'node': + return oldHead ?? null; + case 'val': + return oldHead?.val ?? null; + default: + return oldHead?.val ?? null; + } + } + + pollLast(): T | null; + pollLast(by: 'val'): T | null; + pollLast(by: 'node'): DoublyLinkedListNode | null; + /** + * Removes a node at the end of the linked list and will return the node value + */ + pollLast(by: DoublyLinkedListGetBy = 'val'): DoublyLinkedListNode | T | null { + if (this._size === 0) return null; + const polled = this._last; + if (this._size === 1) { + this._first = null; + this._last = null; + } else { + this._last = polled?.prev ?? null; + if (this._last) this._last.next = null; + if (polled) polled.prev = null; + } + this._size--; + switch (by) { + case 'node': + return polled ?? null; + case 'val': + return polled?.val ?? null; + default: + return polled?.val ?? null; + } + } + + get(index: number): T | null; + get(index: number, by: 'node'): DoublyLinkedListNode | null; + get(index: number, by: 'val'): T | null; + /** + * Returns the node at the specified index of the linked list. + * If index = 0; first element in the list is returned. + * If index = 3; fourth element in the list is returned. + * @param index Index of the node to be retrieved + * @param by Return value type + */ + get(index: number, by: DoublyLinkedListGetBy = 'val'): T | DoublyLinkedListNode | null { + if (index < 0 || index >= this._size) return null; + let count, current; + if (index <= this._size / 2) { + count = 0; + current = this._first; + while (count !== index) { + current = current?.next; + count++; + } + } else { + count = this._size - 1; + current = this._last; + while (count !== index) { + current = current?.prev; + count--; + } + } + switch (by) { + case 'node': + return current ?? null; + case 'val': + return current?.val ?? null; + default: + return current?.val ?? null; + } + } + + /** + * Updates the value of the node at the specified index. + * If index = 0; Value of the first element in the list is updated. + * If index = 3; Value of the fourth element in the list is updated. + * @param index Index of the node to be updated + * @param val New value of the node + */ + set(index: number, val: T): boolean { + const foundNode = this.get(index, 'node'); + if (foundNode !== null) { + foundNode.val = val; + return true; + } + return false; + } + + isEmpty() { + return this._size === 0; + } + + // --- start extra methods --- + /** + * Inserts a new node at the specified index. + * @param index Index at which the new node has to be inserted + * @param val Value of the new node to be inserted + */ + insert(index: number, val: T): boolean { + if (index < 0 || index > this._size) return false; + if (index === 0) return !!this.offerFirst(val); + if (index === this._size) return !!this.offerLast(val); + + const newNode = new DoublyLinkedListNode(val); + const prevNode = this.get(index - 1, 'node'); + const nextNode = prevNode?.next; + + if (prevNode) prevNode.next = newNode; + newNode.prev = prevNode; + newNode.next = nextNode ?? null; + if (nextNode) nextNode.prev = newNode; + this._size++; + return true; + } + + /** + * Removes a node at the specified index and returns its value. + * @param index Index at which the node has to be removed. + */ + remove(index: number): T | null { + if (index < 0 || index > this._size - 1) return null; + else if (index === 0) return this.pollFirst(); + else if (index === this._size - 1) return this.pollLast('node')?.val ?? null; + else { + const prevNode = this.get(index - 1, 'node'); + const removeNode = prevNode?.next; + const nextNode = removeNode?.next; + if (prevNode) prevNode.next = nextNode ?? null; + if (nextNode) nextNode.prev = prevNode; + if (removeNode) removeNode.next = null; + if (removeNode) removeNode.prev = null; + this._size--; + return removeNode?.val ?? null; + } + } + + // --- end extra methods --- +} diff --git a/src/data-structures/linked-list/index.ts b/src/data-structures/linked-list/index.ts new file mode 100644 index 0000000..0bdd0b6 --- /dev/null +++ b/src/data-structures/linked-list/index.ts @@ -0,0 +1,2 @@ +export * from './singly-linked-list'; +export * from './doubly-linked-list'; diff --git a/src/data-structures/linked-list/singly-linked-list.ts b/src/data-structures/linked-list/singly-linked-list.ts new file mode 100644 index 0000000..29ea7de --- /dev/null +++ b/src/data-structures/linked-list/singly-linked-list.ts @@ -0,0 +1,750 @@ +/** Type used for filter and find methods, returning a boolean */ +type TTestFunction = ( + data: NodeData, + index: number, + list: SinglyLinkedList, +) => boolean; + +/** Type used for map and forEach methods, returning anything */ +type TMapFunction = ( + data: any, + index: number, + list: SinglyLinkedList, +) => any; + +/** + * The class which represents one link or node in a linked list + * ```ts + * const node = new SinglyLinkedListNode(1, null, null, null); + * ``` + */ +export class SinglyLinkedListNode { + constructor( + /** Data stored on the node */ + public val: NodeData, + /** The previous node in the list */ + public prev: SinglyLinkedListNode | null, + /** The next link in the list */ + public next: SinglyLinkedListNode | null, + /** The list this node belongs to */ + public list: SinglyLinkedList | null, + ) { + } + + /** + * Alias to .val + * ```ts + * new LinkedList(1, 2, 3).head.value; // 1 + * ``` + */ + public get value() { + return this.val; + } + + /** + * Get the index of this node + * ```ts + * new LinkedList(1, 2, 3).head.index; // 0 + * ``` + */ + public get index() { + if (!this.list) { + return undefined; + } + return this.list.findIndex((value) => value === this.value); + } + + /** + * Insert a new node before this one + * ```ts + * new LinkedList(2, 3).head.insertBefore(1); // 1 <=> 2 <=> 3 + * ``` + * @param val Data to save in the node + */ + public insertBefore(val: NodeData): SinglyLinkedList { + return this.list !== null + ? this.list.insertBefore(this, val) + : new SinglyLinkedList(val, this.val); + } + + /** + * Insert new val after this node + * ```ts + * new LinkedList(1, 2).tail.insertAfter(3); // 1 <=> 2 <=> 3 + * ``` + * @param val Data to be saved in the node + */ + public insertAfter(val: NodeData): SinglyLinkedList { + return this.list !== null + ? this.list.insertAfter(this, val) + : new SinglyLinkedList(this.val, val); + } + + /** + * Remove this node + * ```ts + * new LinkedList(1, 2, 3, 4).tail.remove(); // 1 <=> 2 <=> 3 + * ``` + */ + public remove(): SinglyLinkedListNode { + if (this.list === null) { + throw new ReferenceError('Node does not belong to any list'); + } + return this.list.removeNode(this); + } +} + + +/** + * A doubly linked list + * ```ts + * const list = new LinkedList(1, 2, 3); + * const listFromArray = LinkedList.from([1, 2, 3]); + * ``` + */ +export class SinglyLinkedList { + + /** + * The length of the list + */ + public get length(): number { + return this.size; + } + + /** + * Convert any iterable to a new linked list + * ```javascript + * const array = [1, 2, 3]; + * const list = LinkedList.from(array); + * ``` + * @param iterable Any iterable datatype like Array or Map + */ + public static from(iterable: Iterable): SinglyLinkedList { + return new SinglyLinkedList(...iterable); + } + + /** The head of the list, the first node */ + public head: SinglyLinkedListNode | null; + + /** The tail of the list, the last node */ + public tail: SinglyLinkedListNode | null; + + /** Internal size reference */ + private size: number; + + constructor(...args: NodeData[]) { + this.head = null; + this.tail = null; + this.size = 0; + + for (let i = 0; i < arguments.length; i++) { + this.append(args[i]); + } + } + + /** + * Get the node val at a specified index, zero based + * ```ts + * new LinkedList(1, 2, 3).get(0); // 1 + * ``` + * @param index to retrieve val at + */ + public get(index: number): NodeData | undefined { + const node = this.getNode(index); + return node !== undefined ? node.val : undefined; + } + + /** + * Get the node at index, zero based + * ```ts + * new LinkedList(1, 2, 3).getNode(0); + * // { prev: null, val: 1, next: SinglyLinkedListNode } + * ``` + */ + public getNode(index: number): SinglyLinkedListNode | undefined { + if (this.head === null || index < 0 || index >= this.length) { + return undefined; + } + const asc = index < this.length / 2; + const stopAt = asc ? index : this.length - index - 1; + const nextNode = asc ? 'next' : 'prev'; + let currentNode = asc ? this.head : this.tail; + // TODO after no-non-null-assertion not ensure the logic + for (let currentIndex = 0; currentIndex < stopAt; currentIndex++) { + if (currentNode) { + currentNode = currentNode[nextNode]; + } + } + return currentNode || undefined; + } + + /** + * Return the first node and its index in the list that + * satisfies the testing function + * ```ts + * new LinkedList(1, 2, 3).findNodeIndex(val => val === 1); + * // { node: SinglyLinkedListNode, index: 0 } + * ``` + * @param f A function to be applied to the val of each node + */ + public findNodeIndex(f: TTestFunction): ({ + node: SinglyLinkedListNode, + index: number, + }) | undefined { + let currentIndex = 0; + let currentNode = this.head; + while (currentNode) { + if (f(currentNode.val, currentIndex, this)) { + return { + index: currentIndex, + node: currentNode, + }; + } + currentNode = currentNode.next; + currentIndex += 1; + } + return undefined; + } + + /** + * Returns the first node in the list that + * satisfies the provided testing function. Otherwise undefined is returned. + * ```ts + * new LinkedList(1, 2, 3).findNode(val => val === 1); + * // { prev: null, val: 1, next: SinglyLinkedListNode } + * ``` + * @param f Function to test val against + */ + public findNode(f: TTestFunction): SinglyLinkedListNode | undefined { + const nodeIndex = this.findNodeIndex(f); + return nodeIndex !== undefined ? nodeIndex.node : undefined; + } + + /** + * Returns the value of the first element in the list that + * satisfies the provided testing function. Otherwise undefined is returned. + * ```ts + * new LinkedList(1, 2, 3).find(val => val === 1); // 1 + * ``` + * @param f Function to test val against + */ + public find(f: TTestFunction): NodeData | undefined { + const nodeIndex = this.findNodeIndex(f); + return nodeIndex !== undefined ? nodeIndex.node.val : undefined; + } + + /** + * Returns the index of the first node in the list that + * satisfies the provided testing function. Ohterwise -1 is returned. + * ```ts + * new LinkedList(1, 2, 3).findIndex(val => val === 3); // 2 + * ``` + * @param f Function to test val against + */ + public findIndex(f: TTestFunction): number { + const nodeIndex = this.findNodeIndex(f); + return nodeIndex !== undefined ? nodeIndex.index : -1; + } + + /** + * Append one or any number of nodes to the end of the list. + * This modifies the list in place and returns the list itself + * to make this method chainable. + * ```ts + * new LinkedList(1).append(2).append(3, 4); // 1 <=> 2 <=> 3 <=> 4 + * ``` + * @param args Data to be stored in the node, takes any number of arguments + */ + public append(...args: NodeData[]): SinglyLinkedList { + for (const val of args) { + const node = new SinglyLinkedListNode(val, this.tail, null, this); + if (this.head === null) { + this.head = node; + } + if (this.tail !== null) { + this.tail.next = node; + } + this.tail = node; + this.size += 1; + } + return this; + } + + /** + * Synonym for append + * ```ts + * new LinkedList(1).push(2).push(3, 4); // 1 <=> 2 <=> 3 <=> 4 + * ``` + * @param args Data to be stored, takes any number of arguments + */ + public push(...args: NodeData[]): number { + this.append(...args); + return this.length; + } + + /** + * Prepend any number of val arguments to the list. The + * argument list is prepended as a block to reduce confusion: + * ```javascript + * new LinkedList(3, 4).prepend(0, 1, 2); // [0, 1, 2, 3, 4] + * ``` + * @param args Data to be stored in the node, accepts any number of arguments + */ + public prepend(...args: NodeData[]): SinglyLinkedList { + const reverseArgs = Array.from(args).reverse(); + for (const val of reverseArgs) { + const node = new SinglyLinkedListNode(val, null, this.head, this); + if (this.tail === null) { + this.tail = node; + } + if (this.head !== null) { + this.head.prev = node; + } + this.head = node; + this.size += 1; + } + return this; + } + + /** + * Insert a new node at a given index position. If index is + * out of bounds, the node is appended, if index is negative + * or 0, it will be prepended. + * ```ts + * new LinkedList(1, 3).insertAt(1, 2); // 1 <=> 2 <=> 3 + * ``` + * @param index The index to insert the new node at + * @param val Data to be stored on the new node + */ + public insertAt(index: number, val: NodeData): SinglyLinkedList { + if (this.head === null) { + return this.append(val); + } + if (index <= 0) { + return this.prepend(val); + } + + let currentNode = this.head; + let currentIndex = 0; + while (currentIndex < index - 1 && currentNode.next !== null) { + currentIndex += 1; + currentNode = currentNode.next; + } + currentNode.insertAfter(val); + return this; + } + + /** + * Remove the specified node from the list and return the removed + * node afterwards. + * ```ts + * const list = new LinkedList(1, 2, 3); + * list.removeNode(list.tail); // { prev: null, val: 3, next: null, list: null } + * ``` + * @param node The node to be removed + */ + public removeNode(node: SinglyLinkedListNode): SinglyLinkedListNode { + if (node.list !== this) { + throw new ReferenceError('Node does not belong to this list'); + } + + if (node.prev !== null) { + node.prev.next = node.next; + } + + if (node.next !== null) { + node.next.prev = node.prev; + } + + if (this.head === node) { + this.head = node.next; + } + + if (this.tail === node) { + this.tail = node.prev; + } + + this.size -= 1; + node.next = null; + node.prev = null; + node.list = null; + return node; + } + + /** + * Remove the node at the specified index + * ```ts + * new LinkedList(1, 2, 3).removeAt(2); // { prev: null, val: 3, next: null, list: null } + * ``` + * @param index Index at which to remove + */ + public removeAt(index: number): SinglyLinkedListNode | undefined { + const node = this.getNode(index); + return node !== undefined ? this.removeNode(node) : undefined; + } + + /** + * Insert a new node before the reference node + * ```ts + * const list = new LinkedList(1, 3); + * list.insertBefore(list.tail, 2); // 1 <=> 2 <=> 3 + * ``` + * @param referenceNode The node reference + * @param val Data to save in the node + */ + public insertBefore( + referenceNode: SinglyLinkedListNode, + val: NodeData, + ): SinglyLinkedList { + const node = new SinglyLinkedListNode(val, referenceNode.prev, referenceNode, this); + if (referenceNode.prev === null) { + this.head = node; + } + if (referenceNode.prev !== null) { + referenceNode.prev.next = node; + } + referenceNode.prev = node; + this.size += 1; + return this; + } + + /** + * Sorts the linked list using the provided compare function + * @param compare A function used to compare the val of two nodes. It should return + * a boolean. True will insert a before b, false will insert b before a. + * (a, b) => a < b or (1, 2) => 1 < 2 === true, 2 will be inserted after 1, + * the sort order will be ascending. + */ + public sort(compare: (a: NodeData, b: NodeData) => boolean): SinglyLinkedList { + if (this.head === null || this.tail === null) { + return this; + } + if (this.length < 2) { + return this; + } + + const quicksort = ( + start: SinglyLinkedListNode, + end: SinglyLinkedListNode, + ) => { + if (start === end) { + return; + } + const pivotData = end.val; + let current: SinglyLinkedListNode | null = start; + let split: SinglyLinkedListNode = start; + while (current && current !== end) { + const sort = compare(current.val, pivotData); + if (sort) { + if (current !== split) { + const temp = split.val; + split.val = current.val; + current.val = temp; + } + // TODO after no-non-null-assertion not ensure the logic + if (split.next) { + split = split.next; + } + } + current = current.next; + } + end.val = split.val; + split.val = pivotData; + + if (start.next === end.prev) { + return; + } + + if (split.prev && split !== start) { + quicksort(start, split.prev); + } + if (split.next && split !== end) { + quicksort(split.next, end); + } + }; + + quicksort(this.head, this.tail); + return this; + } + + /** + * Insert a new node after this one + * ```ts + * const list = new LinkedList(2, 3); + * list.insertAfter(list.head, 1); // 1 <=> 2 <=> 3 + * ``` + * @param referenceNode The reference node + * @param val Data to be saved in the node + */ + public insertAfter( + referenceNode: SinglyLinkedListNode, + val: NodeData, + ): SinglyLinkedList { + const node = new SinglyLinkedListNode(val, referenceNode, referenceNode.next, this); + if (referenceNode.next === null) { + this.tail = node; + } + if (referenceNode.next !== null) { + referenceNode.next.prev = node; + } + referenceNode.next = node; + this.size += 1; + return this; + } + + /** + * Remove the first node from the list and return the val of the removed node + * or undefined + * ```ts + * new LinkedList(1, 2, 3).shift(); // 1 + * ``` + */ + public shift(): NodeData | undefined { + return this.removeFromAnyEnd(this.head); + } + + /** + * Remove the last node from the list and return the val of the removed node + * or undefined if the list was empty + * ```ts + * new LinkedList(1, 2, 3).pop(); // 3 + * ``` + */ + public pop(): NodeData | undefined { + return this.removeFromAnyEnd(this.tail); + } + + /** + * Merge the current list with another. Both lists will be + * equal after merging. + * ```ts + * const list = new LinkedList(1, 2); + * const otherList = new LinkedList(3); + * list.merge(otherList); + * (list === otherList); // true + * ``` + * @param list The list to be merged + */ + public merge(list: SinglyLinkedList): void { + if (this.tail !== null) { + this.tail.next = list.head; + } + if (list.head !== null) { + list.head.prev = this.tail; + } + this.head = this.head || list.head; + this.tail = list.tail || this.tail; + this.size += list.size; + list.size = this.size; + list.head = this.head; + list.tail = this.tail; + } + + /** + * Removes all nodes from a list + * + * ```ts + * list.clear(); + * ``` + */ + public clear() { + this.head = null; + this.tail = null; + this.size = 0; + return this; + } + + /** + * The slice() method returns a shallow copy of a + * portion of a list into a new list object selected + * from start to end (end not included). + * The original list will not be modified. + * ```ts + * const list = new LinkedList(1, 2, 3, 4, 5); + * const newList = list.slice(0, 3); // 1 <=> 2 <=> 3 + * ``` + * @param start Start index + * @param end End index, optional + */ + // eslint-disable-next-line @typescript-eslint/ban-types + public slice(start: number, end?: number): SinglyLinkedList { + const list = new SinglyLinkedList(); + let finish = end; + + if (this.head === null || this.tail === null) { + return list; + } + if (finish === undefined || finish < start) { + finish = this.length; + } + + let head: SinglyLinkedListNode | null | undefined = this.getNode(start); + for (let i = 0; i < finish - start && head !== null && head !== undefined; i++) { + list.append(head.val); + head = head.next; + } + return list; + } + + /** + * The reverse() function reverses the list in place and returns the list + * itself. + * ```ts + * new LinkedList(1, 2, 3).reverse(); // 3 <=> 2 <=> 1 + * ``` + */ + public reverse(): SinglyLinkedList { + let currentNode = this.head; + while (currentNode) { + const next = currentNode.next; + currentNode.next = currentNode.prev; + currentNode.prev = next; + currentNode = currentNode.prev; + } + const tail = this.tail; + this.tail = this.head; + this.head = tail; + return this; + } + + /** + * The forEach() method executes a provided function once for each list node. + * ```ts + * new LinkedList(1, 2, 3).forEach(val => log(val)); // 1 2 3 + * ``` + * @param f Function to execute for each element, taking up to three arguments. + * @param reverse Indicates if the list should be walked in reverse order, default is false + */ + public forEach(f: TMapFunction, reverse = false): void { + let currentIndex = reverse ? this.length - 1 : 0; + let currentNode = reverse ? this.tail : this.head; + const modifier = reverse ? -1 : 1; + const nextNode = reverse ? 'prev' : 'next'; + while (currentNode) { + f(currentNode.val, currentIndex, this); + currentNode = currentNode[nextNode]; + currentIndex += modifier; + } + } + + /** + * The map() method creates a new list with the results of + * calling a provided function on every node in the calling list. + * ```ts + * new LinkedList(1, 2, 3).map(val => val + 10); // 11 <=> 12 <=> 13 + * ``` + * @param f Function that produces an node of the new list, taking up to three arguments + * @param reverse Indicates if the list should be mapped in reverse order, default is false + */ + // eslint-disable-next-line @typescript-eslint/ban-types + public map(f: TMapFunction, reverse = false): SinglyLinkedList { + const list = new SinglyLinkedList(); + this.forEach((val, index) => list.append(f(val, index, this)), reverse); + return list; + } + + /** + * The filter() method creates a new list with all nodes + * that pass the test implemented by the provided function. + * ```ts + * new LinkedList(1, 2, 3, 4, 5).filter(val => val < 4); // 1 <=> 2 <=> 3 + * ``` + * @param f Function to test each node val in the list. Return true to keep the node + * @param reverse Indicates if the list should be filtered in reverse order, default is false + */ + // eslint-disable-next-line @typescript-eslint/ban-types + public filter(f: TTestFunction, reverse = false): SinglyLinkedList { + const list = new SinglyLinkedList(); + this.forEach((val, index) => { + if (f(val, index, this)) { + list.append(val); + } + }, reverse); + return list; + } + + /** + * Reduce over each node in the list + * ```ts + * new LinkedList(1, 2, 3).reduce(n => n += 1, 0); // 3 + * ``` + * @param f A reducer function + * @param start An initial value + * @returns The final state of the accumulator + */ + public reduce( + f: ( + accumulator: any, + currentNode: NodeData, + index: number, + list: SinglyLinkedList, + ) => any, + start?: any, + reverse = false, + ): any { + let currentIndex = reverse ? this.length - 1 : 0; + const modifier = reverse ? -1 : 1; + const nextNode = reverse ? 'prev' : 'next'; + let currentElement = reverse ? this.tail : this.head; + let result; + + if (start !== undefined) { + result = start; + } else if (currentElement) { + result = currentElement.val; + currentElement = currentElement[nextNode]; + } else { + throw new TypeError('Reduce of empty LinkedList with no initial value'); + } + + while (currentElement) { + result = f(result, currentElement.val, currentIndex, this); + currentIndex += modifier; + currentElement = currentElement[nextNode]; + } + + return result; + } + + /** + * Convert the linked list to an array + * ```ts + * new LinkedList(1, 2, 3).toArray(); // [1, 2, 3] + * ``` + */ + public toArray(): NodeData[] { + return [...this]; + } + + /** + * Convert a linked list to string + * ```ts + * new LinkedList('one', 'two', 'three').toString(' <=> ') === 'one <=> two <=> three'; + * ``` + * @param separator Optional string to be placed in between val nodes, default is one space + */ + public toString(separator = ' '): string { + return this.reduce((s, val) => `${s}${separator}${val}`); + } + + /** + * The iterator implementation + * ```ts + * const list = new LinkedList(1, 2, 3); + * for (const val of list) { log(val); } // 1 2 3 + * ``` + */ + public* [Symbol.iterator](): IterableIterator { + let element = this.head; + + while (element !== null) { + yield element.val; + element = element.next; + } + } + + /** Private helper function to reduce duplication of pop() and shift() methods */ + private removeFromAnyEnd(node: SinglyLinkedListNode | null) { + return node !== null ? this.removeNode(node).val : undefined; + } +} diff --git a/src/data-structures/linked-list/skip-linked-list.ts b/src/data-structures/linked-list/skip-linked-list.ts new file mode 100644 index 0000000..693da49 --- /dev/null +++ b/src/data-structures/linked-list/skip-linked-list.ts @@ -0,0 +1 @@ +export {} \ No newline at end of file diff --git a/src/data-structures/matrix/index.ts b/src/data-structures/matrix/index.ts new file mode 100644 index 0000000..718eb30 --- /dev/null +++ b/src/data-structures/matrix/index.ts @@ -0,0 +1,3 @@ +export * from './matrix'; +export * from './vector2d'; +export * from './matrix2d'; diff --git a/src/data-structures/matrix/matrix.ts b/src/data-structures/matrix/matrix.ts new file mode 100644 index 0000000..25d168c --- /dev/null +++ b/src/data-structures/matrix/matrix.ts @@ -0,0 +1,13 @@ +// todo need to be improved +export class MatrixNTI2D { + private readonly _matrix: Array>; + + constructor(options: { row: number, col: number, initialVal?: T }) { + const {row, col, initialVal} = options; + this._matrix = new Array(row).fill(undefined).map(() => new Array(col).fill(initialVal || 0)); + } + + toArray(): Array> { + return this._matrix; + } +} diff --git a/src/data-structures/matrix/matrix2d.ts b/src/data-structures/matrix/matrix2d.ts new file mode 100644 index 0000000..20e81c2 --- /dev/null +++ b/src/data-structures/matrix/matrix2d.ts @@ -0,0 +1,125 @@ +import Vector2D from './vector2d' + +export class Matrix2D { + private readonly _matrix: number[][]; + + constructor(value?: number[][] | Vector2D) { + if (typeof value === 'undefined') { + this._matrix = Matrix2D.identity + } else if (value instanceof Vector2D) { + this._matrix = Matrix2D.identity + this._matrix[0][0] = value.x + this._matrix[1][0] = value.y + this._matrix[2][0] = value.w + } else { + this._matrix = value + } + } + + /** + * Return the matrix values + */ + public get m(): number[][] { + return this._matrix + } + + public static get empty(): number[][] { + return [[], [], []] + } + + public get toVector(): Vector2D { + return new Vector2D(this._matrix[0][0], this._matrix[1][0]) + } + + /** + * Initialize an identity matrix + */ + public static get identity(): number[][] { + return [ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1]] + } + + public static add(matrix1: Matrix2D, matrix2: Matrix2D): Matrix2D { + const result = Matrix2D.empty + for (let i = 0; i < 3; i++) { + for (let j = 0; j < 3; j++) { + result[i][j] = matrix1.m[i][j] + matrix2.m[i][j] + } + } + return new Matrix2D(result); + } + + public static subtract(matrix1: Matrix2D, matrix2: Matrix2D): Matrix2D { + const result = Matrix2D.empty + for (let i = 0; i < 3; i++) { + for (let j = 0; j < 3; j++) { + result[i][j] = matrix1.m[i][j] - matrix2.m[i][j] + } + } + return new Matrix2D(result); + } + + public static multiply(matrix1: Matrix2D, matrix2: Matrix2D): Matrix2D { + const result = Matrix2D.empty + for (let i = 0; i < 3; i++) { + for (let j = 0; j < 3; j++) { + result[i][j] = 0; + for (let k = 0; k < 3; k++) { + result[i][j] += matrix1.m[i][k] * matrix2.m[k][j]; + } + } + } + return new Matrix2D(result); + } + + public static multiplyByValue(matrix: Matrix2D, value: number): Matrix2D { + const result = Matrix2D.empty + for (let i = 0; i < 3; i++) { + for (let j = 0; j < 3; j++) { + result[i][j] = matrix.m[i][j] * value + } + } + return new Matrix2D(result); + } + + public static multiplyByVector(matrix: Matrix2D, vector: Vector2D): Vector2D { + return Matrix2D.multiply(matrix, new Matrix2D(vector)).toVector + } + + public static view(width: number, height: number): Matrix2D { + const scaleStep = 1 // Scale every vector * scaleStep + const centerX = width / 2 + const centerY = height / 2 + const flipX = Math.cos(Math.PI) // rotate 180deg / 3.14radian around X-axis + + return new Matrix2D([ + [scaleStep, 0, centerX], + [0, flipX * scaleStep, centerY], + [0, 0, 1]]) + } + + public static scale(factor: number) { + return Matrix2D.multiplyByValue(new Matrix2D(), factor) + } + + public static rotate(radians: number) { + const cos = Math.cos(radians) + const sin = Math.sin(radians) + + return new Matrix2D([ + [cos, -sin, 0], + [sin, cos, 0], + [0, 0, 1]]) + } + + public static translate(vector: Vector2D): Matrix2D { + return new Matrix2D([ + [1, 0, vector.x], + [0, 1, vector.y], + [0, 0, vector.w]]) + } +} + +export default Matrix2D \ No newline at end of file diff --git a/src/data-structures/matrix/navigator.ts b/src/data-structures/matrix/navigator.ts new file mode 100644 index 0000000..a80b5b6 --- /dev/null +++ b/src/data-structures/matrix/navigator.ts @@ -0,0 +1,99 @@ +type Direction = 'up' | 'right' | 'down' | 'left'; +type Turning = { [key in Direction]: Direction }; + +export class Character { + direction: Direction; + turn: () => Character; + + constructor(direction: Direction, turning: Turning) { + this.direction = direction; + this.turn = () => new Character(turning[direction], turning); + } +} + +interface NavigatorParams { + matrix: T[][], + turning: Turning, + onMove: (cur: [number, number]) => void + init: { + cur: [number, number], + charDir: Direction, + VISITED: T, + } +} + +export class Navigator { + private readonly _matrix: T[][]; + private readonly _cur: [number, number]; + private _character: Character; + private readonly _VISITED: T; + onMove: (cur: [number, number]) => void; + + constructor({matrix, turning, onMove, init: {cur, charDir, VISITED}}: NavigatorParams) { + this._matrix = matrix; + this._cur = cur; + this._character = new Character(charDir, turning); + this.onMove = onMove; + this.onMove && this.onMove(this._cur); + this._VISITED = VISITED; + this._matrix[this._cur[0]][this._cur[1]] = this._VISITED; + } + + start() { + while (this.check(this._character.direction) || this.check(this._character.turn().direction)) { + const {direction} = this._character; + if (this.check(direction)) { + this.move(direction); + } else if (this.check(this._character.turn().direction)) { + this._character = this._character.turn(); + } + } + } + + check(direction: Direction) { + let forward: T | undefined, row: T[] | undefined; + const matrix = this._matrix; + const [i, j] = this._cur; + switch (direction) { + case 'up': + row = matrix[i - 1]; + if (!row) return false; + forward = row[j]; + break; + case 'right': + forward = matrix[i][j + 1]; + break; + case 'down': + row = matrix[i + 1]; + if (!row) return false; + forward = row[j]; + break; + case 'left': + forward = matrix[i][j - 1]; + break; + } + return forward !== undefined && forward !== this._VISITED; + } + + move(direction: Direction) { + switch (direction) { + case 'up': + this._cur[0]--; + break; + case 'right': + this._cur[1]++; + break; + case 'down': + this._cur[0]++; + break; + case 'left': + this._cur[1]--; + break; + } + + const [i, j] = this._cur; + this._matrix[i][j] = this._VISITED; + this.onMove && this.onMove(this._cur); + } +} + diff --git a/src/data-structures/matrix/vector2d.ts b/src/data-structures/matrix/vector2d.ts new file mode 100644 index 0000000..27a1e3d --- /dev/null +++ b/src/data-structures/matrix/vector2d.ts @@ -0,0 +1,189 @@ +class Vector2D { + public static add(vector1: Vector2D, vector2: Vector2D): Vector2D { + return new Vector2D(vector1.x + vector2.x, vector1.y + vector2.y) + } + + public static subtract(vector1: Vector2D, vector2: Vector2D): Vector2D { + return new Vector2D(vector1.x - vector2.x, vector1.y - vector2.y) + } + + public static subtractValue(vector: Vector2D, value: number): Vector2D { + return new Vector2D(vector.x - value, vector.y - value) + } + + public static multiply(vector: Vector2D, value: number): Vector2D { + return new Vector2D(vector.x * value, vector.y * value) + } + + public static divide(vector: Vector2D, value: number): Vector2D { + return new Vector2D(vector.x / value, vector.y / value) + } + + public static equals(vector1: Vector2D, vector2: Vector2D): boolean { + return vector1.x === vector2.x && vector1.y === vector2.y + } + + public static equalsRounded(vector1: Vector2D, vector2: Vector2D, roundingFactor = 12): boolean { + const vector = Vector2D.abs(Vector2D.subtract(vector1, vector2)) + if (vector.x < roundingFactor && vector.y < roundingFactor) { + return true + } + + return false + } + + /** + * Normalizes the vector if it matches a certain condition + */ + public static normalize(vector: Vector2D): Vector2D { + const length = vector.length + if (length > 2.220446049250313e-16) { // Epsilon + return Vector2D.divide(vector, length) + } + + return vector + } + + /** + * Adjusts x and y so that the length of the vector does not exceed max + */ + public static truncate(vector: Vector2D, max: number): Vector2D { + if (vector.length > max) { + return Vector2D.multiply(Vector2D.normalize(vector), max) + } + + return vector + } + + /** + * The vector that is perpendicular to this one + */ + public static perp(vector: Vector2D): Vector2D { + return new Vector2D(-vector.y, vector.x) + } + + /** + * returns the vector that is the reverse of this vector + */ + public static reverse(vector: Vector2D): Vector2D { + return new Vector2D(-vector.x, -vector.y) + } + + public static abs(vector: Vector2D): Vector2D { + return new Vector2D(Math.abs(vector.x), Math.abs(vector.y)) + } + + /** + * The dot product of v1 and v2 + */ + public static dot(vector1: Vector2D, vector2: Vector2D): number { + return (vector1.x * vector2.x) + (vector1.y * vector2.y) + } + + /** + * The distance between this and the vector + */ + public static distance(vector1: Vector2D, vector2: Vector2D): number { + const ySeparation = vector2.y - vector1.y + const xSeparation = vector2.x - vector1.x + return Math.sqrt((ySeparation * ySeparation) + (xSeparation * xSeparation)) + } + + /** + * The distance between this and the vector squared + */ + public static distanceSq(vector1: Vector2D, vector2: Vector2D): number { + const ySeparation = vector2.y - vector1.y + const xSeparation = vector2.x - vector1.x + return (ySeparation * ySeparation) + (xSeparation * xSeparation) + } + + /** + * Returns positive if v2 is clockwise of this vector, negative if counterclockwise + * (assuming the Y axis is pointing down, X axis to right like a Window app) + */ + public static sign(vector1: Vector2D, vector2: Vector2D): number { + if (vector1.y * vector2.x > vector1.x * vector2.y) { + return -1 + } + + return 1 + } + + /** + * Returns the angle between origin and the given vector in radians + * @param vector + */ + public static angle(vector: Vector2D): number { + const origin = new Vector2D(0, -1) + const radian = Math.acos(Vector2D.dot(vector, origin) / (vector.length * origin.length)) + return Vector2D.sign(vector, origin) === 1 ? ((Math.PI * 2) - radian) : radian + } + + public static random(maxX: number, maxY: number): Vector2D { + const randX = Math.floor(Math.random() * maxX - (maxX / 2)) + const randY = Math.floor(Math.random() * maxY - (maxY / 2)) + return new Vector2D(randX, randY) + } + + // /** + // * Transform vectors based on the current tranformation matrices: translation, rotation and scale + // * @param vectors The vectors to transform + // */ + // public static transform(vector: Vector2D, transformation: Matrix2D): Vector2D { + // return Matrix2D.multiplyByVector(transformation, vector) + // } + + // /** + // * Transform vectors based on the current tranformation matrices: translation, rotation and scale + // * @param vectors The vectors to transform + // */ + // public static transformList(vectors: Vector2D[], transformation: Matrix2D): Vector2D[] { + // return vectors.map(vector => Matrix2D.multiplyByVector(transformation, vector)) + // } + + constructor( + public x: number = 0, + public y: number = 0, + public w: number = 1 // needed for matrix multiplication + ) { + } + + /** + * Check wether both x and y are zero + */ + public zero(): void { + this.x = 0 + this.y = 0 + } + + /** + * Set x and y both to zero + */ + public get isZero(): boolean { + return this.x === 0 && this.y === 0 + } + + /** + * The length / magnitude of the vector + */ + public get length(): number { + return Math.sqrt((this.x * this.x) + (this.y * this.y)) + } + + /** + * The squared length of the vector + */ + public get lengthSq(): number { + return (this.x * this.x) + (this.y * this.y) + } + + /** + * Return the vector with rounded values + */ + public get rounded(): Vector2D { + return new Vector2D(Math.round(this.x), Math.round(this.y)) + } +} + +export default Vector2D \ No newline at end of file diff --git a/src/data-structures/priority-queue/index.ts b/src/data-structures/priority-queue/index.ts new file mode 100644 index 0000000..abfb1a8 --- /dev/null +++ b/src/data-structures/priority-queue/index.ts @@ -0,0 +1,3 @@ +export * from './priority-queue'; +export * from './min-priority-queue'; +export * from './max-priority-queue'; diff --git a/src/data-structures/priority-queue/max-priority-queue.ts b/src/data-structures/priority-queue/max-priority-queue.ts new file mode 100644 index 0000000..539e79a --- /dev/null +++ b/src/data-structures/priority-queue/max-priority-queue.ts @@ -0,0 +1,12 @@ +import {PriorityQueue, PriorityQueueOptions} from './priority-queue'; + +export class MaxPriorityQueue extends PriorityQueue { + constructor(options: PriorityQueueOptions) { + super({ + nodes: options.nodes, comparator: (a: T, b: T) => { + const aKey = a as unknown as number, bKey = b as unknown as number; + return bKey - aKey; + } + }); + } +} \ No newline at end of file diff --git a/src/data-structures/priority-queue/min-priority-queue.ts b/src/data-structures/priority-queue/min-priority-queue.ts new file mode 100644 index 0000000..74c4226 --- /dev/null +++ b/src/data-structures/priority-queue/min-priority-queue.ts @@ -0,0 +1,12 @@ +import {PriorityQueue, PriorityQueueOptions} from './priority-queue'; + +export class MinPriorityQueue extends PriorityQueue { + constructor(options: PriorityQueueOptions) { + super({ + nodes: options.nodes, comparator: (a: T, b: T) => { + const aKey = a as unknown as number, bKey = b as unknown as number; + return aKey - bKey; + } + }); + } +} \ No newline at end of file diff --git a/src/data-structures/priority-queue/priority-queue.ts b/src/data-structures/priority-queue/priority-queue.ts new file mode 100644 index 0000000..127769a --- /dev/null +++ b/src/data-structures/priority-queue/priority-queue.ts @@ -0,0 +1,208 @@ +export type PriorityQueueComparator = (a: T, b: T) => number; + +export interface PriorityQueueOptions { + nodes?: T[]; + isFix?: boolean; + comparator: PriorityQueueComparator; +} + +export type PriorityQueueDFSOrderPattern = 'pre' | 'in' | 'post'; + +export class PriorityQueue { + protected nodes: T[] = []; + + get size(): number { + return this.nodes.length; + } + + protected readonly _comparator: PriorityQueueComparator = (a: T, b: T) => { + const aKey = a as unknown as number, bKey = b as unknown as number; + return aKey - bKey; + }; + + constructor(options: PriorityQueueOptions) { + const {nodes, comparator, isFix = true} = options; + this._comparator = comparator; + + if (nodes && nodes instanceof Array && nodes.length > 0) { + // TODO support distinct + this.nodes = Array.isArray(nodes) ? [...nodes] : []; + isFix && this._fix(); + } + } + + protected _compare(a: number, b: number) { + return this._comparator(this.nodes[a], this.nodes[b]) > 0; + } + + protected _swap(a: number, b: number) { + const temp = this.nodes[a]; + this.nodes[a] = this.nodes[b]; + this.nodes[b] = temp; + } + + protected _isValidIndex(index: number): boolean { + return index > -1 && index < this.nodes.length; + } + + protected _getParent(child: number): number { + return Math.floor((child - 1) / 2); + } + + protected _getLeft(parent: number): number { + return (2 * parent) + 1; + } + + protected _getRight(parent: number): number { + return (2 * parent) + 2; + } + + protected _getComparedChild(parent: number) { + let min = parent; + const left = this._getLeft(parent), right = this._getRight(parent); + + if (left < this.size && this._compare(min, left)) { + min = left; + } + if (right < this.size && this._compare(min, right)) { + min = right; + } + return min; + } + + protected _heapifyUp(start: number) { + while (start > 0 && this._compare(this._getParent(start), start)) { + const parent = this._getParent(start); + this._swap(start, parent); + start = parent; + } + } + + protected _heapifyDown(start: number) { + let min = this._getComparedChild(start); + while (this._compare(start, min)) { + this._swap(min, start); + start = min; + min = this._getComparedChild(start); + } + } + + protected _fix() { + for (let i = Math.floor(this.size / 2); i > -1; i--) this._heapifyDown(i); + } + + offer(node: T) { + this.nodes.push(node); + this._heapifyUp(this.size - 1); + } + + peek(): T | null { + return this.size ? this.nodes[0] : null; + } + + poll(): T | null { + let res: T | null = null; + if (this.size > 1) { + this._swap(0, this.nodes.length - 1); + res = this.nodes.pop() ?? null; + this._heapifyDown(0); + } else if (this.size === 1) { + res = this.nodes.pop() ?? null; + } + return res; + } + + leaf(): T | null { + return this.nodes[this.size - 1] ?? null; + } + + isEmpty() { + return this.size === 0; + } + + clear() { + this.nodes = []; + } + + toArray(): T[] { + return [...this.nodes]; + } + + clone(): PriorityQueue { + return new PriorityQueue({nodes: this.nodes, comparator: this._comparator}); + } + + // --- start additional methods --- + isValid(): boolean { + const isValidRecursive = (parentIndex: number): boolean => { + let isValidLeft = true; + let isValidRight = true; + + if (this._getLeft(parentIndex) !== -1) { + const leftChildIndex = (parentIndex * 2) + 1; + if (!this._compare(parentIndex, leftChildIndex)) return false; + isValidLeft = isValidRecursive(leftChildIndex); + } + + if (this._getRight(parentIndex) !== -1) { + const rightChildIndex = (parentIndex * 2) + 2; + if (!this._compare(parentIndex, rightChildIndex)) return false; + isValidRight = isValidRecursive(rightChildIndex); + } + + return isValidLeft && isValidRight; + }; + + return isValidRecursive(0); + } + + sort(): T[] { + const visitedNode: T[] = []; + while (this.size !== 0) { + const top = this.poll(); + if (top) visitedNode.push(top); + } + return visitedNode; + } + + DFS(dfsMode: PriorityQueueDFSOrderPattern): (T | null)[] { + const visitedNode: (T | null)[] = []; + + const traverse = (cur: number) => { + const leftChildIndex = this._getLeft(cur); + const rightChildIndex = this._getRight(cur); + switch (dfsMode) { + case 'in': + this._isValidIndex(leftChildIndex) && traverse(leftChildIndex); + visitedNode.push(this.nodes[cur] ?? null); + this._isValidIndex(rightChildIndex) && traverse(rightChildIndex); + break; + case 'pre': + visitedNode.push(this.nodes[cur] ?? null); + this._isValidIndex(leftChildIndex) && traverse(leftChildIndex); + this._isValidIndex(rightChildIndex) && traverse(rightChildIndex); + break; + case 'post': + this._isValidIndex(leftChildIndex) && traverse(leftChildIndex); + this._isValidIndex(rightChildIndex) && traverse(rightChildIndex); + visitedNode.push(this.nodes[cur] ?? null); + break; + } + }; + + this._isValidIndex(0) && traverse(0); + return visitedNode; + } + + static heapify(options: PriorityQueueOptions) { + const heap = new PriorityQueue(options); + heap._fix(); + return heap; + } + + static isPriorityQueueified(options: Omit, 'isFix'>) { + return new PriorityQueue({...options, isFix: true}).isValid(); + } + + // --- end additional methods --- +} \ No newline at end of file diff --git a/src/data-structures/queue/deque.ts b/src/data-structures/queue/deque.ts new file mode 100644 index 0000000..ab9acf4 --- /dev/null +++ b/src/data-structures/queue/deque.ts @@ -0,0 +1,139 @@ +import {DoublyLinkedList} from '../linked-list'; + +// O(n) time complexity of obtaining the value +// O(1) time complexity of adding at the beginning and the end +export class Deque extends DoublyLinkedList { + +} + +// O(1) time complexity of obtaining the value +// O(n) time complexity of adding at the beginning and the end +// todo tested slowest one +export class ObjectDeque { + protected _nodes: { [key: number]: T } = {}; + protected _capacity = Number.MAX_SAFE_INTEGER; + protected _first: number = -1; + protected _last: number = -1; + protected _size: number = 0; + + constructor(capacity?: number) { + if (capacity !== undefined) this._capacity = capacity; + } + + size() { + return this._size; + } + + offerFirst(value: T) { + if (this._size === 0) { + const mid = Math.floor(this._capacity / 2); + this._first = mid; + this._last = mid; + } else { + this._first--; + } + this._nodes[this._first] = value; + this._size++; + } + + offerLast(value: T) { + if (this._size === 0) { + const mid = Math.floor(this._capacity / 2); + this._first = mid; + this._last = mid; + } else { + this._last++; + } + this._nodes[this._last] = value; + this._size++; + } + + pollFirst() { + if (!this._size) return; + let value = this.peekFirst(); + delete this._nodes[this._first]; + this._first++; + this._size--; + return value; + } + + peekFirst() { + if (this._size) return this._nodes[this._first]; + } + + pollLast() { + if (!this._size) return; + let value = this.peekLast(); + delete this._nodes[this._last]; + this._last--; + this._size--; + + return value; + } + + peekLast() { + if (this._size) return this._nodes[this._last]; + } + + get(index: number) { + return this._nodes[this._first + index] || null; + } + + isEmpty() { + return this._size <= 0; + } +} + +// O(1) time complexity of obtaining the value +// O(n) time complexity of adding at the beginning and the end +export class ArrayDeque { + protected _nodes: T[] = []; + + get size() { + return this._nodes.length; + } + + offerLast(value: T) { + return this._nodes.push(value); + } + + pollLast(): T | null { + return this._nodes.pop() ?? null; + } + + pollFirst(): T | null { + return this._nodes.shift() ?? null; + } + + offerFirst(value: T) { + return this._nodes.unshift(value); + } + + peekFirst(): T | null { + return this._nodes[0] ?? null; + } + + peekLast(): T | null { + return this._nodes[this._nodes.length - 1] ?? null; + } + + get(index: number): T | null { + return this._nodes[index] ?? null; + } + + set(index: number, value: T) { + return this._nodes[index] = value; + } + + insert(index: number, value: T) { + return this._nodes.splice(index, 0, value); + } + + remove(index: number) { + return this._nodes.splice(index, 1); + } + + isEmpty() { + return this._nodes.length === 0; + } +} \ No newline at end of file diff --git a/src/data-structures/queue/index.ts b/src/data-structures/queue/index.ts new file mode 100644 index 0000000..cadd6a9 --- /dev/null +++ b/src/data-structures/queue/index.ts @@ -0,0 +1 @@ +export * from './queue'; diff --git a/src/data-structures/queue/queue.ts b/src/data-structures/queue/queue.ts new file mode 100644 index 0000000..9349e1b --- /dev/null +++ b/src/data-structures/queue/queue.ts @@ -0,0 +1,123 @@ +/** + * @license MIT + * @copyright 2020 Pablo + * + * @class + */ +export class Queue { + protected _nodes: T[]; + protected _offset: number; + + /** + * Creates a queue. + * @param {array} [elements] + */ + constructor(elements?: T[]) { + this._nodes = elements || []; + this._offset = 0; + } + + /** + * Adds an element at the back of the queue. + * @public + * @param {any} element + */ + offer(element: T): Queue { + this._nodes.push(element); + return this; + } + + /** + * Dequeues the front element in the queue. + * @public + * @returns {any} + */ + poll(): T | null { + if (this.size() === 0) return null; + + const first = this.peek(); + this._offset += 1; + + if (this._offset * 2 < this._nodes.length) return first; + + // only remove dequeued elements when reaching half size + // to decrease latency of shifting elements. + this._nodes = this._nodes.slice(this._offset); + this._offset = 0; + return first; + } + + /** + * Returns the front element of the queue. + * @public + * @returns {any} + */ + peek(): T | null { + return this.size() > 0 ? this._nodes[this._offset] : null; + } + + /** + * Returns the back element of the queue. + * @public + * @returns {any} + */ + peekLast(): T | null { + return this.size() > 0 ? this._nodes[this._nodes.length - 1] : null; + } + + /** + * Returns the number of elements in the queue. + * @public + * @returns {number} + */ + size(): number { + return this._nodes.length - this._offset; + } + + /** + * Checks if the queue is empty. + * @public + * @returns {boolean} + */ + isEmpty(): boolean { + return this.size() === 0; + } + + /** + * Returns the remaining elements in the queue as an array. + * @public + * @returns {array} + */ + toArray(): T[] { + return this._nodes.slice(this._offset); + } + + /** + * Clears the queue. + * @public + */ + clear(): void { + this._nodes = []; + this._offset = 0; + } + + /** + * Creates a shallow copy of the queue. + * @public + * @return {Queue} + */ + clone(): Queue { + return new Queue(this._nodes.slice(this._offset)); + } + + /** + * Creates a queue from an existing array. + * @public + * @static + * @param {array} elements + * @return {Queue} + */ + static fromArray(elements: T[]): Queue { + return new Queue(elements); + } +} diff --git a/src/data-structures/stack/index.ts b/src/data-structures/stack/index.ts new file mode 100644 index 0000000..d39a8e6 --- /dev/null +++ b/src/data-structures/stack/index.ts @@ -0,0 +1 @@ +export * from './stack'; diff --git a/src/data-structures/stack/stack.ts b/src/data-structures/stack/stack.ts new file mode 100644 index 0000000..333878a --- /dev/null +++ b/src/data-structures/stack/stack.ts @@ -0,0 +1,104 @@ +/** + * @license MIT + * @copyright 2020 Pablo Rios + * + * @class + */ +export class Stack { + protected _elements: T[]; + + /** + * Creates a stack. + * @param {array} [elements] + */ + constructor(elements?: T[]) { + this._elements = Array.isArray(elements) ? elements : []; + } + + /** + * Checks if the stack is empty. + * @public + * @returns {boolean} + */ + isEmpty(): boolean { + return this._elements.length === 0; + } + + /** + * Returns the number of elements in the stack. + * @public + * @returns {number} + */ + size(): number { + return this._elements.length; + } + + /** + * Returns the top element in the stack. + * @public + * @returns {object} + */ + peek(): T | null { + if (this.isEmpty()) return null; + + return this._elements[this._elements.length - 1]; + } + + /** + * Adds an element to the top of the stack. + * @public + * @param {object} element + */ + push(element: T): Stack { + this._elements.push(element); + return this; + } + + /** + * Removes and returns the top element in the stack. + * @public + * @returns {object} + */ + pop(): T | null { + if (this.isEmpty()) return null; + + return this._elements.pop() || null; + } + + /** + * Returns the remaining elements as an array. + * @public + * @returns {array} + */ + toArray(): T[] { + return this._elements.slice(); + } + + /** + * Clears all elements from the stack. + * @public + */ + clear(): void { + this._elements = []; + } + + /** + * Creates a shallow copy from the stack. + * @public + * @return {Stack} + */ + clone(): Stack { + return new Stack(this._elements.slice()); + } + + /** + * Creates a stack from an existing array + * @public + * @static + * @param {array} [elements] + * @return {Stack} + */ + static fromArray(elements: T[]): Stack { + return new Stack(elements); + } +} diff --git a/src/data-structures/trampoline.ts b/src/data-structures/trampoline.ts new file mode 100644 index 0000000..5799af9 --- /dev/null +++ b/src/data-structures/trampoline.ts @@ -0,0 +1,91 @@ +export type ArgumentTypes any> = + T extends (...args: infer A) => any + ? A + : never; + +export const THUNK_SYMBOL: unique symbol = Symbol('thunk'); + +export interface Thunk extends Function { + __THUNK__: typeof THUNK_SYMBOL; + + (): T; +} + +export type ThunkOrValue = T | Thunk; + +export type UnwrapThunkDeep = { + 0: T extends Thunk ? UnwrapThunkDeep : T; +}[ + T extends ThunkOrValue ? 0 : never + ]; + +export const isThunk = (value: any): value is Thunk => { + return typeof value === 'function' && value.__THUNK__ === THUNK_SYMBOL; +}; + +export const toThunk = (fn: () => R): Thunk => { + const thunk = () => fn(); + thunk.__THUNK__ = THUNK_SYMBOL; + return thunk; +}; +export type UnwrapPromise = T extends Promise ? Exclude> : T; + +export type Unbox = UnwrapThunkDeep>; + +export type Cont = (...args: A) => Thunk>; + +export interface Trampoline any)> { + (...args: ArgumentTypes): Unbox>; + + cont: Cont, ReturnType>; +} + +export interface TrampolineAsync any)> { + (...args: ArgumentTypes): Promise>>; + + cont: Cont, ReturnType>; +} + +export const trampoline = any)>(fn: F): Trampoline => { + const cont = (...args: ArgumentTypes) => toThunk(() => fn(...args)); + + return Object.assign( + (...args: ArgumentTypes): Unbox> => { + let result: ThunkOrValue> = fn(...args); + + while (isThunk>(result)) { + result = result(); + } + + return result; + }, + {cont}, + ); +}; + +export const trampolineAsync = any)>(fn: F): TrampolineAsync => { + const cont = (...args: ArgumentTypes) => toThunk(() => fn(...args)); + + return Object.assign( + async (...args: ArgumentTypes): Promise>> => { + let result: ThunkOrValue> = await fn(...args); + + while (isThunk>(result)) { + result = await result(); + } + + return result; + }, + {cont}, + ); +}; + + +const factorial = trampoline((n: number, acc: number = 1): ThunkOrValue => { + return n + // Note: calling factorial.cont instead of factorial directly + ? factorial.cont(n - 1, acc * n) + : acc; +}); + +// factorial(32768) diff --git a/src/data-structures/trie/index.ts b/src/data-structures/trie/index.ts new file mode 100644 index 0000000..8d6f206 --- /dev/null +++ b/src/data-structures/trie/index.ts @@ -0,0 +1 @@ +export * from './trie'; diff --git a/src/data-structures/trie/trie.ts b/src/data-structures/trie/trie.ts new file mode 100644 index 0000000..1cd8455 --- /dev/null +++ b/src/data-structures/trie/trie.ts @@ -0,0 +1,153 @@ +export class TrieNode { + protected _children: Map = new Map(); + + get children(): Map { + return this._children; + } + + set children(v: Map) { + this._children = v; + } + + protected _isEnd = false; + + get isEnd(): boolean { + return this._isEnd; + } + + set isEnd(v: boolean) { + this._isEnd = v; + } +} + +export class Trie { + protected _root: TrieNode; + get root() { + return this._root; + } + + set root(v: TrieNode) { + this._root = v; + } + + constructor() { + this._root = new TrieNode(); + } + + put(input: string): boolean { + let cur = this._root; + for (const c of input) { + let nodeC = cur.children.get(c); + if (!nodeC) { + nodeC = new TrieNode(); + cur.children.set(c, nodeC); + } + cur = nodeC; + } + cur.isEnd = true; + return true; + } + + + has(input: string): boolean { + let cur = this._root; + for (const c of input) { + const nodeC = cur.children.get(c); + if (!nodeC) return false; + cur = nodeC; + } + return cur.isEnd; + } + + remove(word: string) { + let isDeleted = false; + const dfs = (cur: TrieNode, i: number): boolean => { + const char = word[i]; + const child = cur.children.get(char); + if (child) { + if (i === word.length - 1) { + if (child.isEnd) { + if (child.children.size > 0) { + child.isEnd = false; + } else { + cur.children.delete(char); + } + isDeleted = true; + return true; + } + return false; + } + const res = dfs(child, i + 1); + if (res && !cur.isEnd && child.children.size === 0) { + cur.children.delete(char); + return true; + } + return false; + } + return false; + } + + dfs(this.root, 0); + return isDeleted; + } + + // --- start additional methods --- + /** + * Only can present as a prefix, not a word + * @param input + */ + isAbsPrefix(input: string): boolean { + let cur = this._root; + for (const c of input) { + const nodeC = cur.children.get(c); + if (!nodeC) return false; + cur = nodeC; + } + return !cur.isEnd; + } + + /** + * Can present as a prefix or word + * @param input + */ + isPrefix(input: string): boolean { + let cur = this._root; + for (const c of input) { + const nodeC = cur.children.get(c); + if (!nodeC) return false; + cur = nodeC; + } + return true; + } + + + getAll(prefix = ''): string[] { + const words: string[] = []; + + function dfs(node: TrieNode, word: string) { + for (const char of node.children.keys()) { + const charNode = node.children.get(char); + if (charNode !== undefined) { + dfs(charNode, word.concat(char)); + } + } + if (node.isEnd) { + words.push(word); + } + } + + let startNode = this._root; + + if (prefix) { + for (const c of prefix) { + const nodeC = startNode.children.get(c); + if (nodeC) startNode = nodeC; + } + } + + dfs(startNode, prefix); + return words; + } + + // --- end additional methods --- +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..aed7ad8 --- /dev/null +++ b/src/index.ts @@ -0,0 +1 @@ +export * from './data-structures'; \ No newline at end of file diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..04bca77 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1 @@ +export * from './utils'; diff --git a/src/types/patches/index.d.ts b/src/types/patches/index.d.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/types/utils.ts b/src/types/utils.ts new file mode 100644 index 0000000..cc89256 --- /dev/null +++ b/src/types/utils.ts @@ -0,0 +1,158 @@ +export type AnyFunction = (...args: A) => R; +export type Primitive = + | number + | string + | boolean + | symbol + | undefined + | null + | void + | AnyFunction + | Date; + +export type Cast = { [M in keyof TComplex]: T }; + + +export type DeepLeavesWrap = + T extends string ? Cast + : T extends number ? Cast + : T extends boolean ? Cast + : T extends undefined ? Cast + : T extends null ? Cast + : T extends void ? Cast + : T extends symbol ? Cast + : T extends AnyFunction ? Cast + : T extends Date ? Cast + : { + [K in keyof T]: + T[K] extends (infer U)[] ? DeepLeavesWrap[] + : DeepLeavesWrap; + } + + +type Json = null | string | number | boolean | Json [] | { [name: string]: Json } + +export type JSONSerializable = { + [key: string]: any +} + +export type JSONValue = string | number | boolean | undefined | JSONObject; + +export interface JSONObject { + [key: string]: JSONValue; +} + +export type TypeName = T extends string + ? 'string' + : T extends number + ? 'number' + : T extends boolean + ? 'boolean' + : T extends undefined + ? 'undefined' + : T extends AnyFunction + ? 'function' + : 'object'; + +export type JsonKeys = keyof { + [P in keyof T]: number +} + +const arr = ['1', 2, 4, 5, 6] as const; +type Range = typeof arr[number]; +const a: Range = 2; + + +/** + * A function that emits a side effect and does not return anything. + */ +export type Procedure = (...args: any[]) => void; + +export type DebounceOptions = { + isImmediate?: boolean; + maxWait?: number; +}; + +export interface DebouncedFunction { + (this: ThisParameterType, ...args: Parameters): void; + + cancel: () => void; +} + +export type MonthKey = + 'January' | + 'February' | + 'March' | + 'April' | + 'May' | + 'June' | + 'July' | + 'August' | + 'September' | + 'October' | + 'November' | + 'December'; + +export type Month = { [key in MonthKey]: string } + +export type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never; + +export class TreeNode { + id: string; + name?: string | undefined; + value?: T | undefined; + children?: TreeNode[] | undefined; + + constructor(id: string, name?: string, value?: T, children?: TreeNode[]) { + this.id = id; + this.name = name || ''; + this.value = value || undefined; + this.children = children || []; + } + + // TODO get set + // get name (): string | undefined { + // return this.name; + // } + // + // set name (name: string | undefined) { + // this.name = name; + // } + + addChildren(children: TreeNode | TreeNode []) { + if (!this.children) { + this.children = []; + } + if (children instanceof Array) { + this.children = this.children.concat(children); + } else { + this.children.push(children); + } + } + + getHeight() { + // eslint-disable-next-line @typescript-eslint/no-this-alias + const beginRoot = this; + let maxDepth = 1; + if (beginRoot) { + const bfs = (node: TreeNode, level: number) => { + if (level > maxDepth) { + maxDepth = level; + } + const {children} = node; + if (children) { + for (let i = 0, len = children.length; i < len; i++) { + bfs(children[i], level + 1); + } + } + }; + bfs(beginRoot, 1); + } + return maxDepth; + } + +} + +export type OrderType = 'InOrder' | 'PreOrder' | 'PostOrder' + + diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..e9ea9dd --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,605 @@ +import _ from 'lodash'; +import {AnyFunction} from './types'; + +export type JSONSerializable = { + [key: string]: any +} +export type JSONValue = string | number | boolean | undefined | JSONObject; + +export interface JSONObject { + [key: string]: JSONValue; +} + +export function randomText(length: number) { + let result = ''; + const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + const charactersLength = characters.length; + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; +} + +export const uuidV4 = function () { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); +}; + +export class IncrementId { + private _id: string; + private readonly _prefix: string; + + constructor(prefix?: string) { + this._prefix = prefix ? prefix : ''; + this._id = this._prefix + '0'; + } + + getId() { + const {_id, _prefix} = this; + if (!_id) { + this._id = _prefix + '0'; + } else { + const idNumStr = _id.substr(_prefix.length, _id.length - _prefix.length); + const newIdNum = parseInt(idNumStr, 10) + 1; + this._id = _prefix + newIdNum.toString(); + } + return this._id; + } +} + +export function incrementId(prefix?: string) { + const _prefix = prefix ? prefix : ''; + let _id = _prefix + '0'; + return function id() { + const idNumStr = _id.substr(_prefix.length, _id.length - _prefix.length); + const newIdNum = parseInt(idNumStr, 10) + 1; + _id = _prefix + newIdNum.toString(); + return _id; + }; +} + +export const getValue = (obj: T, names: K[]): Array => { + return names.map(i => obj[i]); +}; + +export const isObject = (object: string | JSONObject | boolean | AnyFunction | number) => { + return object != null && typeof object === 'object'; +}; + +export const looseEqual = (a: any, b: any): boolean => { + return a == b; +}; + +export const strictEqual = (a: any, b: any): boolean => { + return a === b; +}; + +export const strictObjectIsEqual = (a: any, b: any): boolean => { + return Object.is(a, b); +}; + +export const deepObjectStrictEqual = (object1: JSONSerializable, object2: JSONSerializable) => { + const keys1 = Object.keys(object1); + const keys2 = Object.keys(object2); + if (keys1.length !== keys2.length) { + return false; + } + for (const key of keys1) { + const val1 = object1[key]; + const val2 = object2[key]; + const areObjects = isObject(val1) && isObject(val2); + if ( + areObjects && !deepObjectStrictEqual(val1, val2) || + !areObjects && val1 !== val2 + ) { + return false; + } + } + return true; +}; + +export const isTypeEqual = (obj: unknown) => { + const m = obj as unknown as T; +}; + +export function reverseColor(oldColor: string) { + const oldColorTemp = '0x' + oldColor.replace(/#/g, ''); + const str = '000000' + (0xFFFFFF - Number(oldColorTemp)).toString(16); + return '#' + str.substring(str.length - 6, str.length); +} + +export const isSameStructure = (objA: unknown, objB: unknown) => { + const objATraversable = objA as JSONSerializable; + const objBTraversable = objB as JSONSerializable; + const objAKeys = Object.keys(objATraversable); + const objBKeys = Object.keys(objBTraversable); + let isSame = true; + if (objAKeys.length !== objBKeys.length) { + return isSame = false; + } else { + objAKeys.forEach((i) => { + if (!objBKeys.includes(i)) { + return isSame = false; + } + }); + return isSame; + } +}; + +export const isLeafParent = (obj: JSONObject) => { + let isLeaf = true; + Object.values(obj).forEach(value => { + if (typeof value === 'object' && value instanceof Array) { + value.forEach(item => { + if (typeof item === 'object') { + return false; + } + }); + return isLeaf = true; + } + if (!['string', 'boolean', 'number', 'undefined', 'function'].includes(typeof value) && (value !== null)) { + return isLeaf = false; + } + }); + return isLeaf; +}; + +export const addDays = (date: Date, days: number): Date => { + date.setDate(date.getDate() + days); + return date; +}; + +export class WaitManager { + private _time1 = 1000; + get time1(): number { + return this._time1 / this._nXSpeed; + } + + private _time2 = 2000; + get time2(): number { + return this._time2 / this._nXSpeed; + } + + private _time3 = 3000; + get time3(): number { + return this._time3 / this._nXSpeed; + } + + private _time4 = 4000; + get time4(): number { + return this._time4 / this._nXSpeed; + } + + private _time10 = 10000; + get time10(): number { + return this._time10 / this._nXSpeed; + } + + private _time20 = 20000; + get time20(): number { + return this._time20 / this._nXSpeed; + } + + private _time30 = 20000; + + get time50(): number { + return this._time30 / this._nXSpeed; + } + + private _time60 = 60000; + get time60(): number { + return this._time60 / this._nXSpeed; + } + + private _cusTime = 1000; + get cusTime(): number { + return this._cusTime / this._nXSpeed; + } + + set cusTime(v: number) { + this._cusTime = v; + } + + private readonly _nXSpeed: number = 1; + + constructor(nXSpeed?: number) { + if (nXSpeed === undefined) nXSpeed = 1; + this._nXSpeed = nXSpeed; + } +} + +export const wait = async (ms: number, resolveValue?: any) => { + return new Promise((resolve, reject) => { + setTimeout(() => { + const finalResolveValue = resolveValue || true; + resolve(finalResolveValue); + }, ms); + }); +}; + +export class AuthAPIError extends Error { + protected serverErrorStack; + protected serverErrorCode; + + constructor(serverErrorMessage: string, serverErrorCode?: string, serverErrorStack?: string) { + super(serverErrorMessage); + if (serverErrorStack) { + this.serverErrorStack = serverErrorStack; + } + if (serverErrorCode) { + this.serverErrorCode = serverErrorCode; + } + this.name = new.target.name; + if (typeof (Error as any).captureStackTrace === 'function') { + (Error as any).captureStackTrace(this, new.target); + } + if (typeof Object.setPrototypeOf === 'function') { + Object.setPrototypeOf(this, new.target.prototype); + } else { + (this as any).__proto__ = new.target.prototype; + } + } +} + +export class BunnyAPIError extends Error { + protected serverErrorStack; + protected serverErrorCode; + + constructor(serverErrorMessage: string, serverErrorCode?: string, serverErrorStack?: string) { + super(serverErrorMessage); + if (serverErrorStack) { + this.serverErrorStack = serverErrorStack; + } + if (serverErrorCode) { + this.serverErrorCode = serverErrorCode; + } + this.name = new.target.name; + if (typeof (Error as any).captureStackTrace === 'function') { + (Error as any).captureStackTrace(this, new.target); + } + if (typeof Object.setPrototypeOf === 'function') { + Object.setPrototypeOf(this, new.target.prototype); + } else { + (this as any).__proto__ = new.target.prototype; + } + } +} + +export class NomicsAPIError extends Error { + protected serverErrorStack; + protected serverErrorCode; + + constructor(serverErrorMessage: string, serverErrorCode?: string, serverErrorStack?: string) { + super(serverErrorMessage); + if (serverErrorStack) { + this.serverErrorStack = serverErrorStack; + } + if (serverErrorCode) { + this.serverErrorCode = serverErrorCode; + } + this.name = new.target.name; + if (typeof (Error as any).captureStackTrace === 'function') { + (Error as any).captureStackTrace(this, new.target); + } + if (typeof Object.setPrototypeOf === 'function') { + Object.setPrototypeOf(this, new.target.prototype); + } else { + (this as any).__proto__ = new.target.prototype; + } + } +} + +export function extractValue(data: { key: string, value: Item }[]) { + let result: Item[] = []; + if (data && data.length > 0) { + result = data.map(item => item.value); + } + return result; +} + +export function keyValueToArray(data: { [key: string]: Item }) { + const itemArray: Array = []; + const keys = Object.keys(data); + for (const i of keys) { + itemArray.push({...data[i], _id: i}); + } + return itemArray; +} + +export function minuted(time: number) { + const minutes = Math.floor(time / 60000).toString(); + const seconds = Math.floor((time % 60000) / 1000).toString().padStart(2, '0'); + return `${minutes}:${seconds}`; +} + +export function randomDate(start?: Date, end?: Date, specificProbabilityStart?: Date, specificProbability?: number) { + if (!start) start = new Date('1970-1-1'); + if (!end) end = new Date(); + + if (specificProbabilityStart) { + if (!specificProbability) specificProbability = 0.5; + if (Math.random() <= specificProbability) { + return new Date(specificProbabilityStart.getTime() + Math.random() * (end.getTime() - specificProbabilityStart.getTime())); + } + } + + return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime())); +} + + +export const capitalizeWords = (str: string) => { + return str.replace(/(?:^|\s)\S/g, (a: string) => a.toUpperCase()); +}; + +export const capitalizeFirstLetter = (str: string) => { + return str.charAt(0).toUpperCase() + str.slice(1); +}; + +export const comparerArray = (otherArray: T[], limitKeys?: string[]) => { + return function (current: T) { + return otherArray.filter(function (other: T) { + if (!limitKeys) { + return _.isEqual(current, other); + } else { + // TODO + } + }).length == 0; + }; +}; + +export const onlyInA = (a: T[], b: T[]) => { + return a.filter(comparerArray(b)); +}; + +export const onlyInB = (a: T[], b: T[]) => { + return b.filter(comparerArray(a)); +}; + +export const diffAB = (a: T[], b: T[]) => { + return onlyInA(a, b).concat(onlyInB(a, b)); +}; + +export class StringUtil { + // camelCase + static toCamelCase(str: string) { + return _.camelCase(str); + } + + // snake_case + static toSnakeCase(str: string) { + return _.snakeCase(str); + } + + // PascalCase + static toPascalCase(str: string) { + return _.startCase(_.camelCase(str)).replace(/ /g, ''); + } + + // CONSTANT_CASE + static toConstantCase(str: string) { + return _.upperCase(str).replace(/ /g, '_'); + } + + // kebab-case + static toKebabCase(str: string) { + return _.kebabCase(str); + } + + // lowercase + static toLowerCase(str: string) { + return _.lowerCase(str).replace(/ /g, ''); + } + + // Title Case + static toTitleCase(str: string) { + return _.startCase(_.camelCase(str)); + } + + // Sentence case + static toSentenceCase(str: string) { + return _.upperFirst(_.lowerCase(str)); + } + + // path/case + static toPathCase(str: string) { + return _.lowerCase(str).replace(/ /g, '/'); + } + + // dot.case + static toDotCase(str: string) { + return _.lowerCase(str).replace(/ /g, '.'); + } +} + +type ToCase = 'camel' | 'snake' | 'pascal' | 'constant' | 'kebab' | 'lower' | 'title' | 'sentence' | 'path' | 'dot'; +export const deepKeysConvert = (obj: any, toType?: ToCase): any => { + const _toType = toType || 'snake'; + if (Array.isArray(obj)) { + return obj.map(v => deepKeysConvert(v, _toType)); + } else if (obj !== null && obj.constructor === Object) { + return Object.keys(obj).reduce( + (result, key) => { + let newKey = ''; + switch (_toType) { + case 'camel': + newKey = StringUtil.toCamelCase(key); + break; + case 'snake': + newKey = StringUtil.toSnakeCase(key); + break; + case 'pascal': + newKey = StringUtil.toPascalCase(key); + break; + case 'constant': + newKey = StringUtil.toConstantCase(key); + break; + case 'kebab': + newKey = StringUtil.toKebabCase(key); + break; + case 'lower': + newKey = StringUtil.toLowerCase(key); + break; + case 'title': + newKey = StringUtil.toTitleCase(key); + break; + case 'sentence': + newKey = StringUtil.toSentenceCase(key); + break; + case 'path': + newKey = StringUtil.toPathCase(key); + break; + case 'dot': + newKey = StringUtil.toDotCase(key); + break; + default: + newKey = StringUtil.toDotCase(key); + break; + } + return { + ...result, + [newKey]: deepKeysConvert(obj[key], _toType), + }; + }, + {}, + ); + } + return obj; +}; + +export const deepRemoveByKey = (obj: any, keysToBeRemoved: string[]) => { + const result = _.transform(obj, function (result: JSONSerializable, value: any, key: string) { + if (_.isObject(value)) { + value = deepRemoveByKey(value, keysToBeRemoved); + } + if (!keysToBeRemoved.includes(key)) { + _.isArray(obj) ? result.push(value) : result[key] = value; + } + }); + return result as typeof obj; +}; + +export const deepRenameKeys = (obj: JSONSerializable, keysMap: { [key in string]: string }) => { + return _.transform(obj, function (result: JSONSerializable, value: any, key: string | number) { + const currentKey = keysMap[key] || key; + result[currentKey] = _.isObject(value) ? deepRenameKeys(value, keysMap) : value; + }); +}; + +export const deepReplaceValues = (obj: JSONSerializable, keyReducerMap: { [key in string]: (item: JSONSerializable) => any }) => { + const newObject = _.clone(obj) as JSONSerializable; + _.each(obj, (val: any, key: string) => { + for (const item in keyReducerMap) { + if (key === item) { + newObject[key] = keyReducerMap[item](newObject); + } else if (typeof (val) === 'object' || val instanceof Array) { + newObject[key] = deepReplaceValues(val, keyReducerMap); + } + } + }); + return newObject; +}; + +// function getCallStackSize() { +// let count = 0, fn = arguments.callee; +// while ( (fn = fn.caller) ) { +// count++; +// } +// return count; +// } +// TODO determine depth and pass root node as a param through callback +export const deepAdd = (obj: JSONSerializable, keyReducerMap: { [key in string]: (item: JSONSerializable) => any }, isItemRootParent?: boolean) => { + const newObject = _.clone(obj) as JSONObject | []; + if (_.isObject(newObject) && !_.isArray(newObject)) { + for (const item in keyReducerMap) { + newObject[item] = keyReducerMap[item](newObject); + } + } + _.each(obj, (val: any, key: string | number) => { + if (_.isObject(val)) { + for (const item in keyReducerMap) { + // @ts-ignore + newObject[key] = deepAdd(val, keyReducerMap, isItemRootParent); + } + } + }); + return newObject; +}; + +const styleString = (color: string) => `color: ${color}; font-weight: bold`; + + +const styleHeader = (header: string) => `%c[${header}]`; + +export const bunnyConsole = { + log: (headerLog = 'bunny', ...args: any[]) => { + return console.log(styleHeader(headerLog), styleString('black'), ...args); + }, + warn: (headerLog = 'bunny', ...args: any[]) => { + return console.warn(styleHeader(headerLog), styleString('orange'), ...args); + }, + error: (headerLog = 'bunny', ...args: any[]) => { + return console.error(styleHeader(headerLog), styleString('red'), ...args); + } +}; + + +export const timeStart = () => { + return performance ? performance.now() : new Date().getTime(); +}; + +export const timeEnd = (startTime: number, headerLog?: string, consoleConditionFn?: (timeSpent: number) => boolean) => { + const timeSpent = (performance ? performance.now() : new Date().getTime()) - startTime; + const isPassCondition = consoleConditionFn ? consoleConditionFn(timeSpent) : true; + if (isPassCondition) { + bunnyConsole.log(headerLog ? headerLog : 'time spent', timeSpent.toFixed(2)); + } +}; + +export const arrayRemove = function (array: T[], predicate: (item: T, index: number, array: T[]) => boolean): T[] { + let i = -1, len = array ? array.length : 0; + const result = []; + + while (++i < len) { + const value = array[i]; + if (predicate(value, i, array)) { + result.push(value); + Array.prototype.splice.call(array, i--, 1); + len--; + } + } + + return result; +}; + +export function memo() { + const cache: { [k: string]: any } = {}; + // eslint-disable-next-line @typescript-eslint/ban-types + return function (target: Object, propertyKey: string, descriptor: PropertyDescriptor) { + const originalMethod = descriptor.value; + descriptor.value = function (...args: any[]) { + const cacheKey = `__cacheKey__${args.toString()}`; + // eslint-disable-next-line no-prototype-builtins + if (!cache.hasOwnProperty(cacheKey)) { + cache[cacheKey] = originalMethod.apply(this, args); + } + return cache[cacheKey]; + } + } +} + +export function zip(array1: T[], array2: T1[], options?: { isToObj: boolean }) { + const zipped: [T, T1][] = []; + const zippedObjCoords: { x: T, y: T1 }[] = []; + const {isToObj} = options ? options : {isToObj: false}; + for (let i = 0; i < array1.length; i++) { + if (isToObj) { + zippedObjCoords.push({x: array1[i], y: array2[i]}) + } else { + zipped.push([array1[i], array2[i]]); + } + } + return isToObj ? zippedObjCoords : zipped; +} + diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..ccaff8d --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,52 @@ +{ + "compilerOptions": { + "declaration": true, + "outDir": "./dist", + "module": "commonjs", + "target": "es6", + "lib": [ + "es6", + "dom", + "dom.iterable", + "esnext" + ], + "strict": true, + "esModuleInterop": true, + "moduleResolution": "node", + "declarationDir": "./dist/types", + "skipLibCheck": true + + // "allowJs": true, + // "allowSyntheticDefaultImports": true, + // "forceConsistentCasingInFileNames": true, + // "noFallthroughCasesInSwitch": true, + // "resolveJsonModule": true, + // "isolatedModules": true, + // "noEmit": true, + }, + "include": [ + "src" + ], + "exclude": [ + "node_modules", + "dist" + ] +} + + +//{ +// "compilerOptions": { +// "declaration": true, +// "outDir": "./dist", +// "module": "commonjs", +// "target": "es5", +// "lib": ["es6", "dom"], +// "strict": true, +// "esModuleInterop": true, +// "moduleResolution": "node", +// "declarationDir": "./dist/types", +// "skipLibCheck": true +// }, +// "include": ["src"], +// "exclude": ["node_modules", "dist"] +//}