diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f709510 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +# Editor configuration, see http://editorconfig.org +root = true +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true +[*.md] +max_line_length = off +trim_trailing_whitespace = false +[*.yml] +[*.{yml,yaml}] +indent_size = 2 diff --git a/.gitignore b/.gitignore index 2b7beb6..f5b57d9 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,6 @@ __tests__/ notes/ docs/ -backup/ \ No newline at end of file +backup/ + +.editorconfig diff --git a/.npmignore b/.npmignore index 2127763..749024f 100644 --- a/.npmignore +++ b/.npmignore @@ -6,9 +6,10 @@ /docs /backup +.editorconfig .dependency-cruiser.js jest.config.js package-lock.json rename-clear-files.sh tsconfig.json -webpack.config.js \ No newline at end of file +webpack.config.js diff --git a/src/data-structures/binary-tree/abstract-binary-tree.ts b/src/data-structures/binary-tree/abstract-binary-tree.ts index ef34d19..3d20063 100644 --- a/src/data-structures/binary-tree/abstract-binary-tree.ts +++ b/src/data-structures/binary-tree/abstract-binary-tree.ts @@ -8,1448 +8,1448 @@ import {trampoline} from '../../utils'; import type { - AbstractBinaryTreeNodeNested, - AbstractBinaryTreeNodeProperties, - AbstractBinaryTreeNodeProperty, - BinaryTreeDeletedResult, - BinaryTreeNodeId, - BinaryTreeNodePropertyName, - DFSOrderPattern, - NodeOrPropertyName + AbstractBinaryTreeNodeNested, + AbstractBinaryTreeNodeProperties, + AbstractBinaryTreeNodeProperty, + BinaryTreeDeletedResult, + BinaryTreeNodeId, + BinaryTreeNodePropertyName, + DFSOrderPattern, + NodeOrPropertyName } from '../../types'; import {AbstractBinaryTreeOptions, FamilyPosition, LoopType} from '../../types'; import {IAbstractBinaryTree, IAbstractBinaryTreeNode} from '../../interfaces'; export abstract class AbstractBinaryTreeNode = AbstractBinaryTreeNodeNested> implements IAbstractBinaryTreeNode { - /** - * The constructor function initializes a BinaryTreeNode object with an id and an optional value. - * @param {BinaryTreeNodeId} id - The `id` parameter is of type `BinaryTreeNodeId` and represents the unique identifier - * of the binary tree node. It is used to distinguish one node from another in the binary tree. - * @param {T} [val] - The "val" parameter is an optional parameter of type T. It represents the value that will be - * stored in the binary tree node. If no value is provided, it will be set to undefined. - */ - protected constructor(id: BinaryTreeNodeId, val?: T) { - this._id = id; - this._val = val; + /** + * The constructor function initializes a BinaryTreeNode object with an id and an optional value. + * @param {BinaryTreeNodeId} id - The `id` parameter is of type `BinaryTreeNodeId` and represents the unique identifier + * of the binary tree node. It is used to distinguish one node from another in the binary tree. + * @param {T} [val] - The "val" parameter is an optional parameter of type T. It represents the value that will be + * stored in the binary tree node. If no value is provided, it will be set to undefined. + */ + protected constructor(id: BinaryTreeNodeId, val?: T) { + this._id = id; + this._val = val; + } + + private _id: BinaryTreeNodeId; + + get id(): BinaryTreeNodeId { + return this._id; + } + + set id(v: BinaryTreeNodeId) { + this._id = v; + } + + private _val: T | undefined; + + get val(): T | undefined { + return this._val; + } + + set val(value: T | undefined) { + this._val = value; + } + + private _left: NEIGHBOR | null | undefined; + + get left(): NEIGHBOR | null | undefined { + return this._left; + } + + set left(v: NEIGHBOR | null | undefined) { + if (v) { + v.parent = this as unknown as NEIGHBOR; } + this._left = v; + } - private _id: BinaryTreeNodeId; + private _right: NEIGHBOR | null | undefined; - get id(): BinaryTreeNodeId { - return this._id; + get right(): NEIGHBOR | null | undefined { + return this._right; + } + + set right(v: NEIGHBOR | null | undefined) { + if (v) { + v.parent = this as unknown as NEIGHBOR; } + this._right = v; + } - set id(v: BinaryTreeNodeId) { - this._id = v; - } + private _parent: NEIGHBOR | null | undefined; - private _val: T | undefined; + get parent(): NEIGHBOR | null | undefined { + return this._parent; + } - get val(): T | undefined { - return this._val; - } + set parent(v: NEIGHBOR | null | undefined) { + this._parent = v; + } - set val(value: T | undefined) { - this._val = value; - } + private _height = 0; - private _left: NEIGHBOR | null | undefined; + get height(): number { + return this._height; + } - get left(): NEIGHBOR | null | undefined { - return this._left; - } + set height(v: number) { + this._height = v; + } - set left(v: NEIGHBOR | null | undefined) { - if (v) { - v.parent = this as unknown as NEIGHBOR; - } - this._left = v; - } - - private _right: NEIGHBOR | null | undefined; - - get right(): NEIGHBOR | null | undefined { - return this._right; - } - - set right(v: NEIGHBOR | null | undefined) { - if (v) { - v.parent = this as unknown as NEIGHBOR; - } - this._right = v; - } - - private _parent: NEIGHBOR | null | undefined; - - get parent(): NEIGHBOR | null | undefined { - return this._parent; - } - - set parent(v: NEIGHBOR | null | undefined) { - this._parent = v; - } - - private _height = 0; - - get height(): number { - return this._height; - } - - set height(v: number) { - this._height = v; - } - - /** - * The function determines the position of a node in a family tree structure. - * @returns a value of type `FamilyPosition`. - */ - get familyPosition(): FamilyPosition { - const that = this as unknown as NEIGHBOR; - if (that.parent) { - if (that.parent.left === that) { - if (that.left || that.right) { - return FamilyPosition.ROOT_LEFT; - } else { - return FamilyPosition.LEFT; - } - } else if (that.parent.right === that) { - if (that.left || that.right) { - return FamilyPosition.ROOT_RIGHT; - } else { - return FamilyPosition.RIGHT; - } - } else { - return FamilyPosition.MAL_NODE; - } + /** + * The function determines the position of a node in a family tree structure. + * @returns a value of type `FamilyPosition`. + */ + get familyPosition(): FamilyPosition { + const that = this as unknown as NEIGHBOR; + if (that.parent) { + if (that.parent.left === that) { + if (that.left || that.right) { + return FamilyPosition.ROOT_LEFT; } else { - if (that.left || that.right) { - return FamilyPosition.ROOT; - } else { - return FamilyPosition.ISOLATED; - } + return FamilyPosition.LEFT; } + } else if (that.parent.right === that) { + if (that.left || that.right) { + return FamilyPosition.ROOT_RIGHT; + } else { + return FamilyPosition.RIGHT; + } + } else { + return FamilyPosition.MAL_NODE; + } + } else { + if (that.left || that.right) { + return FamilyPosition.ROOT; + } else { + return FamilyPosition.ISOLATED; + } } + } } export abstract class AbstractBinaryTree = AbstractBinaryTreeNode> implements IAbstractBinaryTree { - /** - * The protected constructor initializes the options for an abstract binary tree. - * @param {AbstractBinaryTreeOptions} [options] - An optional object that contains configuration options for the binary - * tree. - */ - protected constructor(options?: AbstractBinaryTreeOptions) { - if (options !== undefined) { - const {loopType = LoopType.ITERATIVE} = options; - this._loopType = loopType; + /** + * The protected constructor initializes the options for an abstract binary tree. + * @param {AbstractBinaryTreeOptions} [options] - An optional object that contains configuration options for the binary + * tree. + */ + protected constructor(options?: AbstractBinaryTreeOptions) { + if (options !== undefined) { + const {loopType = LoopType.ITERATIVE} = options; + this._loopType = loopType; + } + this.clear(); + } + + private _root: N | null = null; + + get root(): N | null { + return this._root; + } + + private _size: number = 0; + + get size(): number { + return this._size; + } + + private _loopType: LoopType = LoopType.ITERATIVE; + + get loopType(): LoopType { + return this._loopType; + } + + private _visitedId: BinaryTreeNodeId[] = []; + + get visitedId(): BinaryTreeNodeId[] { + return this._visitedId; + } + + private _visitedVal: N['val'][] = []; + + get visitedVal(): N['val'][] { + return this._visitedVal; + } + + private _visitedNode: N[] = []; + + get visitedNode(): N[] { + return this._visitedNode; + } + + private _visitedLeftSum: number[] = []; + + get visitedLeftSum(): number[] { + return this._visitedLeftSum; + } + + abstract createNode(id: BinaryTreeNodeId, val?: N['val']): N | null ; + + /** + * The `swapLocation` function swaps the location of two nodes in a binary tree. + * @param {N} srcNode - The source node that you want to swap with the destination node. + * @param {N} destNode - The `destNode` parameter represents the destination node where the values from `srcNode` will + * be swapped to. + * @returns The `destNode` is being returned. + */ + swapLocation(srcNode: N, destNode: N): N { + const {id, val, height} = destNode; + const tempNode = this.createNode(id, val); + + if (tempNode) { + tempNode.height = height; + + destNode.id = srcNode.id; + destNode.val = srcNode.val; + destNode.height = srcNode.height; + + srcNode.id = tempNode.id; + srcNode.val = tempNode.val; + srcNode.height = tempNode.height; + } + + return destNode; + } + + /** + * The clear() function resets the root, size, and maxId properties to their initial values. + */ + clear() { + this._root = null; + this._size = 0; + this._clearResults(); + } + + /** + * The function checks if the size of an object is equal to zero and returns a boolean value. + * @returns A boolean value indicating whether the size of the object is 0 or not. + */ + isEmpty(): boolean { + return this.size === 0; + } + + /** + * When all leaf nodes are null, it will no longer be possible to add new entity nodes to this binary tree. + * In this scenario, null nodes serve as "sentinel nodes," "virtual nodes," or "placeholder nodes." + */ + + /** + * The `add` function adds a new node to a binary tree, either by ID or by creating a new node with a given value. + * @param {BinaryTreeNodeId | N | null} idOrNode - The `idOrNode` parameter can be either a `BinaryTreeNodeId`, which + * is a number representing the ID of a binary tree node, or it can be a `N` object, which represents a binary tree + * node itself. It can also be `null` if no node is specified. + * @param [val] - The `val` parameter is an optional value that can be assigned to the `val` property of the new node + * being added to the binary tree. + * @returns The function `add` returns either the inserted node (`N`), `null`, or `undefined`. + */ + add(idOrNode: BinaryTreeNodeId | N | null, val?: N['val']): N | null | undefined { + const _bfs = (root: N, newNode: N | null): N | undefined | null => { + const queue: Array = [root]; + while (queue.length > 0) { + const cur = queue.shift(); + if (cur) { + if (newNode && cur.id === newNode.id) return; + const inserted = this._addTo(newNode, 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: N | null | undefined, needInsert: N | null; + + if (idOrNode === null) { + needInsert = null; + } else if (typeof idOrNode === 'number') { + needInsert = this.createNode(idOrNode, val); + } else if (idOrNode instanceof AbstractBinaryTreeNode) { + needInsert = idOrNode; + } else { + return; + } + + const existNode = idOrNode ? this.get(idOrNode, 'id') : undefined; + + if (this.root) { + if (existNode) { + existNode.val = val; + inserted = existNode; + } else { + inserted = _bfs(this.root, needInsert); + } + } else { + this._setRoot(needInsert); + if (needInsert !== null) { + this._setSize(1); + } else { + this._setSize(0); + } + inserted = this.root; + } + return inserted; + } + + /** + * The `addMany` function adds multiple nodes to a tree data structure and returns an array of the inserted nodes or + * null/undefined values. + * @param {(BinaryTreeNodeId|N)[]} idsOrNodes - An array of BinaryTreeNodeId or N objects. These can be either the ID + * of a binary tree node or the actual node object itself. + * @param {N['val'][]} [data] - Optional array of values to be added to the nodes. If provided, the length of this + * array should be the same as the length of the `idsOrNodes` array. + * @returns The function `addMany` returns an array of values `(N | null | undefined)[]`. + */ + addMany(idsOrNodes: (BinaryTreeNodeId | N | null)[], data?: N['val'][]): (N | null | undefined)[] { + // TODO not sure addMany not be run multi times + const inserted: (N | null | undefined)[] = []; + const map: Map = new Map(); + + for (const idOrNode of idsOrNodes) map.set(idOrNode, (map.get(idOrNode) ?? 0) + 1); + + for (let i = 0; i < idsOrNodes.length; i++) { + const idOrNode = idsOrNodes[i]; + if (map.has(idOrNode)) { + if (idOrNode instanceof AbstractBinaryTreeNode) { + inserted.push(this.add(idOrNode.id, idOrNode.val)); + continue; } - this.clear(); - } - - private _root: N | null = null; - - get root(): N | null { - return this._root; - } - - private _size: number = 0; - - get size(): number { - return this._size; - } - - private _loopType: LoopType = LoopType.ITERATIVE; - - get loopType(): LoopType { - return this._loopType; - } - - private _visitedId: BinaryTreeNodeId[] = []; - - get visitedId(): BinaryTreeNodeId[] { - return this._visitedId; - } - - private _visitedVal: N['val'][] = []; - - get visitedVal(): N['val'][] { - return this._visitedVal; - } - - private _visitedNode: N[] = []; - - get visitedNode(): N[] { - return this._visitedNode; - } - - private _visitedLeftSum: number[] = []; - - get visitedLeftSum(): number[] { - return this._visitedLeftSum; - } - - abstract createNode(id: BinaryTreeNodeId, val?: N['val']): N | null ; - - /** - * The `swapLocation` function swaps the location of two nodes in a binary tree. - * @param {N} srcNode - The source node that you want to swap with the destination node. - * @param {N} destNode - The `destNode` parameter represents the destination node where the values from `srcNode` will - * be swapped to. - * @returns The `destNode` is being returned. - */ - swapLocation(srcNode: N, destNode: N): N { - const {id, val, height} = destNode; - const tempNode = this.createNode(id, val); - - if (tempNode) { - tempNode.height = height; - - destNode.id = srcNode.id; - destNode.val = srcNode.val; - destNode.height = srcNode.height; - - srcNode.id = tempNode.id; - srcNode.val = tempNode.val; - srcNode.height = tempNode.height; - } - - return destNode; - } - - /** - * The clear() function resets the root, size, and maxId properties to their initial values. - */ - clear() { - this._root = null; - this._size = 0; - this._clearResults(); - } - - /** - * The function checks if the size of an object is equal to zero and returns a boolean value. - * @returns A boolean value indicating whether the size of the object is 0 or not. - */ - isEmpty(): boolean { - return this.size === 0; - } - - /** - * When all leaf nodes are null, it will no longer be possible to add new entity nodes to this binary tree. - * In this scenario, null nodes serve as "sentinel nodes," "virtual nodes," or "placeholder nodes." - */ - - /** - * The `add` function adds a new node to a binary tree, either by ID or by creating a new node with a given value. - * @param {BinaryTreeNodeId | N | null} idOrNode - The `idOrNode` parameter can be either a `BinaryTreeNodeId`, which - * is a number representing the ID of a binary tree node, or it can be a `N` object, which represents a binary tree - * node itself. It can also be `null` if no node is specified. - * @param [val] - The `val` parameter is an optional value that can be assigned to the `val` property of the new node - * being added to the binary tree. - * @returns The function `add` returns either the inserted node (`N`), `null`, or `undefined`. - */ - add(idOrNode: BinaryTreeNodeId | N | null, val?: N['val']): N | null | undefined { - const _bfs = (root: N, newNode: N | null): N | undefined | null => { - const queue: Array = [root]; - while (queue.length > 0) { - const cur = queue.shift(); - if (cur) { - if (newNode && cur.id === newNode.id) return; - const inserted = this._addTo(newNode, 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: N | null | undefined, needInsert: N | null; if (idOrNode === null) { - needInsert = null; - } else if (typeof idOrNode === 'number') { - needInsert = this.createNode(idOrNode, val); - } else if (idOrNode instanceof AbstractBinaryTreeNode) { - needInsert = idOrNode; - } else { - return; + inserted.push(this.add(null)); + continue; } - const existNode = idOrNode ? this.get(idOrNode, 'id') : undefined; + const val = data?.[i]; - if (this.root) { - if (existNode) { - existNode.val = val; - inserted = existNode; - } else { - inserted = _bfs(this.root, needInsert); - } - } else { - this._setRoot(needInsert); - if (needInsert !== null) { - this._setSize(1); - } else { - this._setSize(0); - } - inserted = this.root; + inserted.push(this.add(idOrNode, val)); + map.delete(idOrNode); + } + + } + return inserted; + } + + /** + * The `fill` function clears the binary tree and adds multiple nodes with the given IDs or nodes and optional data. + * @param {(BinaryTreeNodeId | N)[]} idsOrNodes - The `idsOrNodes` parameter is an array that can contain either + * `BinaryTreeNodeId` or `N` values. + * @param {N[] | Array} [data] - The `data` parameter is an optional array of values that will be assigned to + * the nodes being added. If provided, the length of the `data` array should be equal to the length of the `idsOrNodes` + * array. Each value in the `data` array will be assigned to the + * @returns The method is returning a boolean value. + */ + fill(idsOrNodes: (BinaryTreeNodeId | N | null)[], data?: N[] | Array): boolean { + this.clear(); + return idsOrNodes.length === this.addMany(idsOrNodes, data).length; + } + + /** + * The `remove` function removes a node from a binary search tree and returns the deleted node along with the parent + * node that needs to be balanced. + * @param {N | BinaryTreeNodeId} nodeOrId - The `nodeOrId` parameter can be either a node object (`N`) or a binary tree + * node ID (`BinaryTreeNodeId`). + * @param {boolean} [ignoreCount] - The `ignoreCount` parameter is an optional boolean parameter that determines + * whether to ignore the count of the nodes in the binary tree. If `ignoreCount` is set to `true`, the count of the + * nodes in the binary tree will not be updated after removing a node. If `ignoreCount` + * @returns The function `remove` returns an array of `BinaryTreeDeletedResult` objects. + */ + remove(nodeOrId: N | BinaryTreeNodeId, ignoreCount?: boolean): BinaryTreeDeletedResult[] { + const bstDeletedResult: BinaryTreeDeletedResult[] = []; + if (!this.root) return bstDeletedResult; + + const curr: N | null = (typeof nodeOrId === 'number') ? this.get(nodeOrId) : nodeOrId; + if (!curr) return bstDeletedResult; + + const parent: N | null = curr?.parent ? curr.parent : null; + let needBalanced: N | null = null, orgCurrent = curr; + + if (!curr.left) { + if (!parent) { + if (curr.right !== undefined) this._setRoot(curr.right); + } else { + const {familyPosition: fp} = curr; + if (fp === FamilyPosition.LEFT || fp === FamilyPosition.ROOT_LEFT) { + parent.left = curr.right; + } else if (fp === FamilyPosition.RIGHT || fp === FamilyPosition.ROOT_RIGHT) { + parent.right = curr.right; } - return inserted; - } - - /** - * The `addMany` function adds multiple nodes to a tree data structure and returns an array of the inserted nodes or - * null/undefined values. - * @param {(BinaryTreeNodeId|N)[]} idsOrNodes - An array of BinaryTreeNodeId or N objects. These can be either the ID - * of a binary tree node or the actual node object itself. - * @param {N['val'][]} [data] - Optional array of values to be added to the nodes. If provided, the length of this - * array should be the same as the length of the `idsOrNodes` array. - * @returns The function `addMany` returns an array of values `(N | null | undefined)[]`. - */ - addMany(idsOrNodes: (BinaryTreeNodeId | N | null)[], data?: N['val'][]): (N | null | undefined)[] { - // TODO not sure addMany not be run multi times - const inserted: (N | null | undefined)[] = []; - const map: Map = new Map(); - - for (const idOrNode of idsOrNodes) map.set(idOrNode, (map.get(idOrNode) ?? 0) + 1); - - for (let i = 0; i < idsOrNodes.length; i++) { - const idOrNode = idsOrNodes[i]; - if (map.has(idOrNode)) { - if (idOrNode instanceof AbstractBinaryTreeNode) { - inserted.push(this.add(idOrNode.id, idOrNode.val)); - continue; - } - - if (idOrNode === null) { - inserted.push(this.add(null)); - continue; - } - - const val = data?.[i]; - - inserted.push(this.add(idOrNode, val)); - map.delete(idOrNode); - } - + needBalanced = parent; + } + } else { + const leftSubTreeRightMost = curr.left ? this.getRightMost(curr.left) : null; + if (leftSubTreeRightMost) { + const parentOfLeftSubTreeMax = leftSubTreeRightMost.parent; + orgCurrent = this.swapLocation(curr, leftSubTreeRightMost); + if (parentOfLeftSubTreeMax) { + if (parentOfLeftSubTreeMax.right === leftSubTreeRightMost) parentOfLeftSubTreeMax.right = leftSubTreeRightMost.left; + else parentOfLeftSubTreeMax.left = leftSubTreeRightMost.left; + needBalanced = parentOfLeftSubTreeMax; } - return inserted; + } } + this._setSize(this.size - 1); - /** - * The `fill` function clears the binary tree and adds multiple nodes with the given IDs or nodes and optional data. - * @param {(BinaryTreeNodeId | N)[]} idsOrNodes - The `idsOrNodes` parameter is an array that can contain either - * `BinaryTreeNodeId` or `N` values. - * @param {N[] | Array} [data] - The `data` parameter is an optional array of values that will be assigned to - * the nodes being added. If provided, the length of the `data` array should be equal to the length of the `idsOrNodes` - * array. Each value in the `data` array will be assigned to the - * @returns The method is returning a boolean value. - */ - fill(idsOrNodes: (BinaryTreeNodeId | N | null)[], data?: N[] | Array): boolean { - this.clear(); - return idsOrNodes.length === this.addMany(idsOrNodes, data).length; + bstDeletedResult.push({deleted: orgCurrent, needBalanced}); + return bstDeletedResult; + } + + /** + * The function calculates the depth of a node in a binary tree. + * @param {N | BinaryTreeNodeId | null} beginRoot - The `beginRoot` parameter can be one of the following: + * @returns the depth of the given node or binary tree. + */ + getDepth(beginRoot: N | BinaryTreeNodeId | null): number { + if (typeof beginRoot === 'number') beginRoot = this.get(beginRoot, 'id'); + + let depth = 0; + while (beginRoot?.parent) { + depth++; + beginRoot = beginRoot.parent; } + return depth; + } + + /** + * The `getHeight` function calculates the maximum height of a binary tree, either recursively or iteratively. + * @param {N | BinaryTreeNodeId | null} [beginRoot] - The `beginRoot` parameter is optional and can be of type `N` (a + * generic type representing a node in a binary tree), `BinaryTreeNodeId` (a type representing the ID of a binary tree + * node), or `null`. + * @returns the height of the binary tree. + */ + getHeight(beginRoot?: N | BinaryTreeNodeId | null): number { + beginRoot = beginRoot ?? this.root; + + if (typeof beginRoot === 'number') beginRoot = this.get(beginRoot, 'id'); + if (!beginRoot) return -1; + + if (this._loopType === LoopType.RECURSIVE) { + const _getMaxHeight = (cur: N | 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 { + if (!beginRoot) { + return -1; + } + + const stack: { node: N; depth: number }[] = [{node: beginRoot, depth: 0}]; + let maxHeight = 0; + + while (stack.length > 0) { + const {node, depth} = stack.pop()!; - /** - * The `remove` function removes a node from a binary search tree and returns the deleted node along with the parent - * node that needs to be balanced. - * @param {N | BinaryTreeNodeId} nodeOrId - The `nodeOrId` parameter can be either a node object (`N`) or a binary tree - * node ID (`BinaryTreeNodeId`). - * @param {boolean} [ignoreCount] - The `ignoreCount` parameter is an optional boolean parameter that determines - * whether to ignore the count of the nodes in the binary tree. If `ignoreCount` is set to `true`, the count of the - * nodes in the binary tree will not be updated after removing a node. If `ignoreCount` - * @returns The function `remove` returns an array of `BinaryTreeDeletedResult` objects. - */ - remove(nodeOrId: N | BinaryTreeNodeId, ignoreCount?: boolean): BinaryTreeDeletedResult[] { - const bstDeletedResult: BinaryTreeDeletedResult[] = []; - if (!this.root) return bstDeletedResult; - - const curr: N | null = (typeof nodeOrId === 'number') ? this.get(nodeOrId) : nodeOrId; - if (!curr) return bstDeletedResult; - - const parent: N | null = curr?.parent ? curr.parent : null; - let needBalanced: N | null = null, orgCurrent = curr; - - if (!curr.left) { - if (!parent) { - if (curr.right !== undefined) this._setRoot(curr.right); - } else { - const {familyPosition: fp} = curr; - if (fp === FamilyPosition.LEFT || fp === FamilyPosition.ROOT_LEFT) { - parent.left = curr.right; - } else if (fp === FamilyPosition.RIGHT || fp === FamilyPosition.ROOT_RIGHT) { - parent.right = curr.right; - } - needBalanced = parent; - } - } else { - const leftSubTreeRightMost = curr.left ? this.getRightMost(curr.left) : null; - if (leftSubTreeRightMost) { - const parentOfLeftSubTreeMax = leftSubTreeRightMost.parent; - orgCurrent = this.swapLocation(curr, leftSubTreeRightMost); - if (parentOfLeftSubTreeMax) { - if (parentOfLeftSubTreeMax.right === leftSubTreeRightMost) parentOfLeftSubTreeMax.right = leftSubTreeRightMost.left; - else parentOfLeftSubTreeMax.left = leftSubTreeRightMost.left; - needBalanced = parentOfLeftSubTreeMax; - } - } - } - this._setSize(this.size - 1); - - bstDeletedResult.push({deleted: orgCurrent, needBalanced}); - return bstDeletedResult; - } - - /** - * The function calculates the depth of a node in a binary tree. - * @param {N | BinaryTreeNodeId | null} beginRoot - The `beginRoot` parameter can be one of the following: - * @returns the depth of the given node or binary tree. - */ - getDepth(beginRoot: N | BinaryTreeNodeId | null): number { - if (typeof beginRoot === 'number') beginRoot = this.get(beginRoot, 'id'); - - let depth = 0; - while (beginRoot?.parent) { - depth++; - beginRoot = beginRoot.parent; - } - return depth; - } - - /** - * The `getHeight` function calculates the maximum height of a binary tree, either recursively or iteratively. - * @param {N | BinaryTreeNodeId | null} [beginRoot] - The `beginRoot` parameter is optional and can be of type `N` (a - * generic type representing a node in a binary tree), `BinaryTreeNodeId` (a type representing the ID of a binary tree - * node), or `null`. - * @returns the height of the binary tree. - */ - getHeight(beginRoot?: N | BinaryTreeNodeId | null): number { - beginRoot = beginRoot ?? this.root; - - if (typeof beginRoot === 'number') beginRoot = this.get(beginRoot, 'id'); - if (!beginRoot) return -1; - - if (this._loopType === LoopType.RECURSIVE) { - const _getMaxHeight = (cur: N | 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 { - if (!beginRoot) { - return -1; - } - - const stack: { node: N; depth: number }[] = [{node: beginRoot, depth: 0}]; - let maxHeight = 0; - - while (stack.length > 0) { - const {node, depth} = stack.pop()!; - - if (node.left) { - stack.push({node: node.left, depth: depth + 1}); - } - - if (node.right) { - stack.push({node: node.right, depth: depth + 1}); - } - - maxHeight = Math.max(maxHeight, depth); - } - - return maxHeight; - } - } - - /** - * The `getMinHeight` function calculates the minimum height of a binary tree using either a recursive or iterative - * approach. - * @param {N | null} [beginRoot] - The `beginRoot` parameter is an optional parameter of type `N` or `null`. It - * represents the starting node from which to calculate the minimum height of a binary tree. If no value is provided - * for `beginRoot`, the `this.root` property is used as the default value. - * @returns The function `getMinHeight` returns the minimum height of the binary tree. - */ - getMinHeight(beginRoot?: N | null): number { - beginRoot = beginRoot || this.root; - if (!beginRoot) return -1; - - if (this._loopType === LoopType.RECURSIVE) { - const _getMinHeight = (cur: N | 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: N[] = []; - let node: N | null | undefined = beginRoot, last: N | null = null; - const depths: Map = 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) { - const leftMinHeight = node.left ? depths.get(node.left) ?? -1 : -1; - const 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; - } - } - - /** - * The function checks if a binary tree is perfectly balanced by comparing the minimum height and the height of the - * tree. - * @param {N | null} [beginRoot] - The parameter `beginRoot` is of type `N` or `null`. It represents the root node of a - * tree or null if the tree is empty. - * @returns The method is returning a boolean value. - */ - isPerfectlyBalanced(beginRoot?: N | null): boolean { - return (this.getMinHeight(beginRoot) + 1 >= this.getHeight(beginRoot)); - } - - /** - * The function `getNodes` returns an array of nodes that match a given property name and value in a binary tree. - * @param {BinaryTreeNodeId | N} nodeProperty - The `nodeProperty` parameter can be either a `BinaryTreeNodeId` or a - * generic type `N`. It represents the property of the binary tree node that you want to search for. - * @param {BinaryTreeNodePropertyName} [propertyName] - The `propertyName` parameter is an optional parameter that - * specifies the property name to use when searching for nodes. If not provided, it defaults to 'id'. - * @param {boolean} [onlyOne] - The `onlyOne` parameter is an optional boolean parameter that determines whether to - * return only one node that matches the given `nodeProperty` or `propertyName`. If `onlyOne` is set to `true`, the - * function will stop traversing the tree and return the first matching node. If `only - * @returns an array of nodes (type N). - */ - getNodes(nodeProperty: BinaryTreeNodeId | N, propertyName ?: BinaryTreeNodePropertyName, onlyOne ?: boolean): N[] { - if (!this.root) return []; - propertyName = propertyName ?? 'id'; - - const result: N[] = []; - - if (this.loopType === LoopType.RECURSIVE) { - const _traverse = (cur: N) => { - 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: N[] = [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; - } - - /** - * The function checks if a binary tree node has a specific property. - * @param {BinaryTreeNodeId | N} nodeProperty - The `nodeProperty` parameter can be either a `BinaryTreeNodeId` or `N`. - * It represents the property of the binary tree node that you want to check. - * @param {BinaryTreeNodePropertyName} [propertyName] - The `propertyName` parameter is an optional parameter that - * specifies the name of the property to be checked in the nodes. If not provided, it defaults to 'id'. - * @returns a boolean value. - */ - has(nodeProperty: BinaryTreeNodeId | N, propertyName ?: BinaryTreeNodePropertyName): boolean { - propertyName = propertyName ?? 'id'; - // TODO may support finding node by value equal - return this.getNodes(nodeProperty, propertyName).length > 0; - } - - /** - * The function returns the first node that matches the given property name and value, or null if no matching node is - * found. - * @param {BinaryTreeNodeId | N} nodeProperty - The `nodeProperty` parameter can be either a `BinaryTreeNodeId` or `N`. - * It represents the property of the binary tree node that you want to search for. - * @param {BinaryTreeNodePropertyName} [propertyName] - The `propertyName` parameter is an optional parameter that - * specifies the property name to be used for searching the binary tree nodes. If this parameter is not provided, the - * default value is set to `'id'`. - * @returns either the value of the specified property of the node, or the node itself if no property name is provided. - * If no matching node is found, it returns null. - */ - get(nodeProperty: BinaryTreeNodeId | N, propertyName ?: BinaryTreeNodePropertyName): N | null { - propertyName = propertyName ?? 'id'; - // TODO may support finding node by value equal - return this.getNodes(nodeProperty, propertyName, true)[0] ?? null; - } - - /** - * The function getPathToRoot takes a node and returns an array of nodes representing the path from the given node to - * the root node. - * @param {N} node - The parameter `node` represents a node in a tree data structure. - * @returns The function `getPathToRoot` returns an array of nodes (`N[]`). - */ - getPathToRoot(node: N): N[] { - // TODO to support get path through passing id - const result: N[] = []; - while (node.parent) { - result.unshift(node); - node = node.parent; - } - result.unshift(node); - return result; - } - - getLeftMost(): N | null; - - getLeftMost(node: N): N; - - /** - * The `getLeftMost` function returns the leftmost node in a binary tree, starting from a specified node or the root if - * no node is specified. - * @param {N | BinaryTreeNodeId | null} [beginRoot] - The `beginRoot` parameter is optional and can be of type `N` (a - * generic type representing a node in a binary tree), `BinaryTreeNodeId` (a type representing the ID of a binary tree - * node), or `null`. - * @returns The function `getLeftMost` returns the leftmost node in a binary tree. If the `beginRoot` parameter is - * provided, it starts the traversal from that node. If `beginRoot` is not provided or is `null`, it starts the - * traversal from the root of the binary tree. If there are no nodes in the binary tree, it returns `null`. - */ - getLeftMost(beginRoot?: N | BinaryTreeNodeId | null): N | null { - if (typeof beginRoot === 'number') beginRoot = this.get(beginRoot, 'id'); - - beginRoot = beginRoot ?? this.root; - if (!beginRoot) return beginRoot; - - if (this._loopType === LoopType.RECURSIVE) { - - const _traverse = (cur: N): N => { - if (!cur.left) return cur; - return _traverse(cur.left); - } - - return _traverse(beginRoot); - } else { - // Indirect implementation of iteration using tail recursion optimization - const _traverse = trampoline((cur: N) => { - if (!cur.left) return cur; - return _traverse.cont(cur.left); - }); - - return _traverse(beginRoot); - } - } - - getRightMost(): N | null; - - getRightMost(node: N): N; - - /** - * The `getRightMost` function returns the rightmost node in a binary tree, either recursively or iteratively using - * tail recursion optimization. - * @param {N | null} [node] - The `node` parameter is an optional parameter of type `N` or `null`. It represents the - * starting node from which we want to find the rightmost node. If no node is provided, the `node` parameter defaults - * to `this.root`, which is the root node of the data structure - * @returns The function `getRightMost` returns the rightmost node (`N`) in a binary tree. If the `node` parameter is - * not provided, it defaults to the root node of the tree. If the tree is empty or the `node` parameter is `null`, the - * function returns `null`. - */ - getRightMost(node?: N | null): N | null { - // TODO support get right most by passing id in - node = node ?? this.root; - if (!node) return node; - - if (this._loopType === LoopType.RECURSIVE) { - const _traverse = (cur: N): N => { - 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: N) => { - if (!cur.right) return cur; - return _traverse.cont(cur.right); - }); - - return _traverse(node); - } - } - - /** - * The function checks if a binary search tree is valid by traversing it either recursively or iteratively. - * @param {N | null} node - The `node` parameter represents the root node of a binary search tree (BST). - * @returns a boolean value. - */ - isSubtreeBST(node: N | null): boolean { - // TODO there is a bug - if (!node) return true; - - if (this._loopType === LoopType.RECURSIVE) { - const dfs = (cur: N | 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: N | null | undefined = node; - while (curr || stack.length > 0) { - while (curr) { - stack.push(curr); - curr = curr.left; - } - curr = stack.pop()!; - if (!(curr) || prev >= curr.id) return false; - prev = curr.id; - curr = curr.right; - } - return true; - } - } - - /** - * The function isBST checks if the binary search tree is valid. - * @returns The `isBST()` function is returning a boolean value. - */ - isBST(): boolean { - return this.isSubtreeBST(this.root); - } - - /** - * The function calculates the size of a subtree by traversing it either recursively or iteratively. - * @param {N | null | undefined} subTreeRoot - The `subTreeRoot` parameter represents the root node of a subtree in a - * binary tree. - * @returns the size of the subtree rooted at `subTreeRoot`. - */ - getSubTreeSize(subTreeRoot: N | null | undefined) { - // TODO support id passed in - let size = 0; - if (!subTreeRoot) return size; - - if (this._loopType === LoopType.RECURSIVE) { - const _traverse = (cur: N) => { - size++; - cur.left && _traverse(cur.left); - cur.right && _traverse(cur.right); - } - - _traverse(subTreeRoot); - return size; - } else { - const stack: N[] = [subTreeRoot]; - - while (stack.length > 0) { - const cur = stack.pop()!; - size++; - cur.right && stack.push(cur.right); - cur.left && stack.push(cur.left); - } - - return size; - } - } - - /** - * The function `subTreeSum` calculates the sum of a specified property in a binary tree or subtree. - * @param {N | BinaryTreeNodeId | null} subTreeRoot - The `subTreeRoot` parameter represents the root node of a binary - * tree or the ID of a binary tree node. It can also be `null` if there is no subtree. - * @param {BinaryTreeNodePropertyName} [propertyName] - propertyName is an optional parameter that specifies the - * property of the binary tree node to use for calculating the sum. It can be either 'id' or 'val'. If propertyName is - * not provided, it defaults to 'id'. - * @returns a number, which is the sum of the values of the specified property in the subtree rooted at `subTreeRoot`. - */ - subTreeSum(subTreeRoot: N | BinaryTreeNodeId | null, propertyName ?: BinaryTreeNodePropertyName): number { - propertyName = propertyName ?? 'id'; - if (typeof subTreeRoot === 'number') subTreeRoot = this.get(subTreeRoot, 'id'); - - if (!subTreeRoot) return 0; - - let sum = 0; - - const _sumByProperty = (cur: N) => { - let needSum: number; - switch (propertyName) { - case 'id': - needSum = cur.id; - 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: N): void => { - sum += _sumByProperty(cur); - cur.left && _traverse(cur.left); - cur.right && _traverse(cur.right); - } - - _traverse(subTreeRoot); - } else { - const stack: N[] = [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; - } - - /** - * The function `subTreeAdd` adds a delta value to a specified property of each node in a subtree. - * @param {N | BinaryTreeNodeId | null} subTreeRoot - The `subTreeRoot` parameter represents the root node of a binary - * tree or the ID of a node in the binary tree. It can also be `null` if there is no subtree to add to. - * @param {number} delta - The `delta` parameter is a number that represents the amount by which the property value of - * each node in the subtree should be incremented. - * @param {BinaryTreeNodePropertyName} [propertyName] - The `propertyName` parameter is an optional parameter that - * specifies the property of the binary tree node that should be modified. If not provided, it defaults to 'id'. - * @returns a boolean value. - */ - subTreeAdd(subTreeRoot: N | BinaryTreeNodeId | null, delta: number, propertyName ?: BinaryTreeNodePropertyName): boolean { - propertyName = propertyName ?? 'id'; - if (typeof subTreeRoot === 'number') subTreeRoot = this.get(subTreeRoot, 'id'); - - if (!subTreeRoot) return false; - - const _addByProperty = (cur: N) => { - switch (propertyName) { - case 'id': - cur.id += delta; - break; - default: - cur.id += delta; - break; - } - } - - if (this._loopType === LoopType.RECURSIVE) { - const _traverse = (cur: N) => { - _addByProperty(cur); - cur.left && _traverse(cur.left); - cur.right && _traverse(cur.right); - }; - - _traverse(subTreeRoot); - } else { - const stack: N[] = [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'): N['val'][]; - - BFS(nodeOrPropertyName: 'node'): N[]; - - /** - * The BFS function performs a breadth-first search on a binary tree, accumulating properties of each node based on a - * specified property name. - * @param {NodeOrPropertyName} [nodeOrPropertyName] - The parameter `nodeOrPropertyName` is an optional parameter that - * represents either a node or a property name. If a node is provided, the breadth-first search (BFS) algorithm will be - * performed starting from that node. If a property name is provided, the BFS algorithm will be performed starting from - * the - * @returns an instance of the `AbstractBinaryTreeNodeProperties` class with generic type `N`. - */ - BFS(nodeOrPropertyName ?: NodeOrPropertyName): AbstractBinaryTreeNodeProperties { - nodeOrPropertyName = nodeOrPropertyName ?? 'id'; - this._clearResults(); - const queue: Array = [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'): N[]; - - DFS(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'node'): N[]; - - /** - * The DFS function performs a depth-first search traversal on a binary tree and returns the accumulated properties of - * each node based on the specified pattern and property name. - * @param {'in' | 'pre' | 'post'} [pattern] - The "pattern" parameter is used to specify the traversal order of the - * binary tree. It can have three possible values: - * @param {NodeOrPropertyName} [nodeOrPropertyName] - The `nodeOrPropertyName` parameter is a string that represents - * the name of a property of the nodes in the binary tree. This property will be used to accumulate values during the - * depth-first search traversal. If no `nodeOrPropertyName` is provided, the default value is `'id'`. - * @returns an instance of the AbstractBinaryTreeNodeProperties class, which contains the accumulated properties of the - * binary tree nodes based on the specified pattern and node or property name. - */ - DFS(pattern ?: 'in' | 'pre' | 'post', nodeOrPropertyName ?: NodeOrPropertyName): AbstractBinaryTreeNodeProperties { - pattern = pattern ?? 'in'; - nodeOrPropertyName = nodeOrPropertyName ?? 'id'; - this._clearResults(); - const _traverse = (node: N) => { - 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[]; - - - // --- start additional methods --- - - DFSIterative(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'id'): BinaryTreeNodeId[]; - - DFSIterative(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'val'): N[]; - - DFSIterative(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'node'): N[]; - - /** - * The DFSIterative function performs an iterative depth-first search traversal on a binary tree, with the option to - * specify the traversal pattern and the property name to accumulate results by. - * @param {'in' | 'pre' | 'post'} [pattern] - The "pattern" parameter determines the order in which the nodes of the - * binary tree are visited during the depth-first search. It can have one of the following values: - * @param {NodeOrPropertyName} [nodeOrPropertyName] - The `nodeOrPropertyName` parameter is used to specify the - * property of the nodes that you want to retrieve or perform operations on during the depth-first search traversal. By - * default, it is set to `'id'`, which means that the traversal will accumulate results based on the `id` property of - * the - * @returns an object of type AbstractBinaryTreeNodeProperties. - */ - DFSIterative(pattern ?: 'in' | 'pre' | 'post', nodeOrPropertyName ?: NodeOrPropertyName): AbstractBinaryTreeNodeProperties { - pattern = pattern || 'in'; - nodeOrPropertyName = nodeOrPropertyName || 'id'; - this._clearResults(); - if (!this.root) return this._getResultByPropertyName(nodeOrPropertyName); - // 0: visit, 1: print - const stack: { opt: 0 | 1, node: N | 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: N | null): BinaryTreeNodeId[]; - - levelIterative(node: N | null, nodeOrPropertyName?: 'id'): BinaryTreeNodeId[]; - - levelIterative(node: N | null, nodeOrPropertyName?: 'val'): N['val'][]; - - levelIterative(node: N | null, nodeOrPropertyName?: 'node'): N[]; - - /** - * The `levelIterative` function performs a level-order traversal on a binary tree and returns the values of the nodes - * in an array, based on a specified property name. - * @param {N | null} node - The `node` parameter is a BinaryTreeNode object representing the starting - * node for the level order traversal. It can be null if no specific node is provided, in which case the root node of - * the tree is used as the starting node. - * @param {NodeOrPropertyName} [nodeOrPropertyName] - The `nodeOrPropertyName` parameter is an optional parameter that - * can be either a `BinaryTreeNode` property name or the string `'id'`. If a property name is provided, the function - * will accumulate results based on that property. If no property name is provided, the function will default to - * accumulating results - * @returns The function `levelIterative` returns an object of type `AbstractBinaryTreeNodeProperties`. - */ - levelIterative(node: N | null, nodeOrPropertyName ?: NodeOrPropertyName): AbstractBinaryTreeNodeProperties { - nodeOrPropertyName = nodeOrPropertyName || 'id'; - node = node || this.root; - if (!node) return []; - - this._clearResults(); - const queue: N[] = [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: N | null): BinaryTreeNodeId[][]; - - listLevels(node: N | null, nodeOrPropertyName?: 'id'): BinaryTreeNodeId[][]; - - listLevels(node: N | null, nodeOrPropertyName?: 'val'): N['val'][][]; - - listLevels(node: N | null, nodeOrPropertyName?: 'node'): N[][]; - - /** - * The `listLevels` function collects nodes from a binary tree by a specified property and organizes them into levels. - * @param {N | null} node - The `node` parameter is a BinaryTreeNode object or null. It represents the - * root node of a binary tree. If it is null, the function will use the root node of the current binary tree instance. - * @param {NodeOrPropertyName} [nodeOrPropertyName] - The `nodeOrPropertyName` parameter is an optional parameter that - * specifies the property of the `BinaryTreeNode` object to collect at each level. It can be one of the following - * values: - * @returns The function `listLevels` returns a 2D array of `AbstractBinaryTreeNodeProperty` objects. - */ - listLevels(node: N | null, nodeOrPropertyName?: NodeOrPropertyName): AbstractBinaryTreeNodeProperty[][] { - nodeOrPropertyName = nodeOrPropertyName || 'id'; - node = node || this.root; - if (!node) return []; - - const levelsNodes: AbstractBinaryTreeNodeProperty[][] = []; - - const collectByProperty = (node: N, 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; - default: - levelsNodes[level].push(node.id); - break; - } - } - - if (this.loopType === LoopType.RECURSIVE) { - const _recursive = (node: N, 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: [N, 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; - } - - /** - * The function returns the predecessor of a given node in a binary tree. - * @param node - The parameter `node` is a BinaryTreeNode object, representing a node in a binary tree. - * @returns the predecessor of the given node in a binary tree. - */ - getPredecessor(node: N): N { if (node.left) { - let predecessor: N | null | undefined = node.left; - while (!(predecessor) || predecessor.right && predecessor.right !== node) { - if (predecessor) { - predecessor = predecessor.right; - } - } - return predecessor; - } else { - return node; + stack.push({node: node.left, depth: depth + 1}); } + + if (node.right) { + stack.push({node: node.right, depth: depth + 1}); + } + + maxHeight = Math.max(maxHeight, depth); + } + + return maxHeight; + } + } + + /** + * The `getMinHeight` function calculates the minimum height of a binary tree using either a recursive or iterative + * approach. + * @param {N | null} [beginRoot] - The `beginRoot` parameter is an optional parameter of type `N` or `null`. It + * represents the starting node from which to calculate the minimum height of a binary tree. If no value is provided + * for `beginRoot`, the `this.root` property is used as the default value. + * @returns The function `getMinHeight` returns the minimum height of the binary tree. + */ + getMinHeight(beginRoot?: N | null): number { + beginRoot = beginRoot || this.root; + if (!beginRoot) return -1; + + if (this._loopType === LoopType.RECURSIVE) { + const _getMinHeight = (cur: N | 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: N[] = []; + let node: N | null | undefined = beginRoot, last: N | null = null; + const depths: Map = 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) { + const leftMinHeight = node.left ? depths.get(node.left) ?? -1 : -1; + const 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; + } + } + + /** + * The function checks if a binary tree is perfectly balanced by comparing the minimum height and the height of the + * tree. + * @param {N | null} [beginRoot] - The parameter `beginRoot` is of type `N` or `null`. It represents the root node of a + * tree or null if the tree is empty. + * @returns The method is returning a boolean value. + */ + isPerfectlyBalanced(beginRoot?: N | null): boolean { + return (this.getMinHeight(beginRoot) + 1 >= this.getHeight(beginRoot)); + } + + /** + * The function `getNodes` returns an array of nodes that match a given property name and value in a binary tree. + * @param {BinaryTreeNodeId | N} nodeProperty - The `nodeProperty` parameter can be either a `BinaryTreeNodeId` or a + * generic type `N`. It represents the property of the binary tree node that you want to search for. + * @param {BinaryTreeNodePropertyName} [propertyName] - The `propertyName` parameter is an optional parameter that + * specifies the property name to use when searching for nodes. If not provided, it defaults to 'id'. + * @param {boolean} [onlyOne] - The `onlyOne` parameter is an optional boolean parameter that determines whether to + * return only one node that matches the given `nodeProperty` or `propertyName`. If `onlyOne` is set to `true`, the + * function will stop traversing the tree and return the first matching node. If `only + * @returns an array of nodes (type N). + */ + getNodes(nodeProperty: BinaryTreeNodeId | N, propertyName ?: BinaryTreeNodePropertyName, onlyOne ?: boolean): N[] { + if (!this.root) return []; + propertyName = propertyName ?? 'id'; + + const result: N[] = []; + + if (this.loopType === LoopType.RECURSIVE) { + const _traverse = (cur: N) => { + 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: N[] = [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); + } + } } - morris(): BinaryTreeNodeId[]; + return result; + } - morris(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'id'): BinaryTreeNodeId[]; + /** + * The function checks if a binary tree node has a specific property. + * @param {BinaryTreeNodeId | N} nodeProperty - The `nodeProperty` parameter can be either a `BinaryTreeNodeId` or `N`. + * It represents the property of the binary tree node that you want to check. + * @param {BinaryTreeNodePropertyName} [propertyName] - The `propertyName` parameter is an optional parameter that + * specifies the name of the property to be checked in the nodes. If not provided, it defaults to 'id'. + * @returns a boolean value. + */ + has(nodeProperty: BinaryTreeNodeId | N, propertyName ?: BinaryTreeNodePropertyName): boolean { + propertyName = propertyName ?? 'id'; + // TODO may support finding node by value equal + return this.getNodes(nodeProperty, propertyName).length > 0; + } - morris(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'val'): N[]; + /** + * The function returns the first node that matches the given property name and value, or null if no matching node is + * found. + * @param {BinaryTreeNodeId | N} nodeProperty - The `nodeProperty` parameter can be either a `BinaryTreeNodeId` or `N`. + * It represents the property of the binary tree node that you want to search for. + * @param {BinaryTreeNodePropertyName} [propertyName] - The `propertyName` parameter is an optional parameter that + * specifies the property name to be used for searching the binary tree nodes. If this parameter is not provided, the + * default value is set to `'id'`. + * @returns either the value of the specified property of the node, or the node itself if no property name is provided. + * If no matching node is found, it returns null. + */ + get(nodeProperty: BinaryTreeNodeId | N, propertyName ?: BinaryTreeNodePropertyName): N | null { + propertyName = propertyName ?? 'id'; + // TODO may support finding node by value equal + return this.getNodes(nodeProperty, propertyName, true)[0] ?? null; + } - /** - * Time complexity is O(n) - * Space complexity of Iterative DFS equals to recursive DFS which is O(n) because of the stack - */ + /** + * The function getPathToRoot takes a node and returns an array of nodes representing the path from the given node to + * the root node. + * @param {N} node - The parameter `node` represents a node in a tree data structure. + * @returns The function `getPathToRoot` returns an array of nodes (`N[]`). + */ + getPathToRoot(node: N): N[] { + // TODO to support get path through passing id + const result: N[] = []; + while (node.parent) { + result.unshift(node); + node = node.parent; + } + result.unshift(node); + return result; + } - morris(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'node'): N[]; + getLeftMost(): N | null; - /** - * The `morris` function performs an in-order, pre-order, or post-order traversal on a binary tree using the Morris - * traversal algorithm. - * @param {'in' | 'pre' | 'post'} [pattern] - The `pattern` parameter determines the traversal pattern for the binary - * tree. It can have one of three values: - * @param {NodeOrPropertyName} [nodeOrPropertyName] - The `nodeOrPropertyName` parameter is used to specify the - * property name of the nodes that you want to retrieve. It can be any valid property name of the nodes in the binary - * tree. - * @returns an array of AbstractBinaryTreeNodeProperties objects. - */ - morris(pattern?: 'in' | 'pre' | 'post', nodeOrPropertyName?: NodeOrPropertyName): AbstractBinaryTreeNodeProperties { - if (this.root === null) return []; + getLeftMost(node: N): N; - pattern = pattern || 'in'; - nodeOrPropertyName = nodeOrPropertyName || 'id'; + /** + * The `getLeftMost` function returns the leftmost node in a binary tree, starting from a specified node or the root if + * no node is specified. + * @param {N | BinaryTreeNodeId | null} [beginRoot] - The `beginRoot` parameter is optional and can be of type `N` (a + * generic type representing a node in a binary tree), `BinaryTreeNodeId` (a type representing the ID of a binary tree + * node), or `null`. + * @returns The function `getLeftMost` returns the leftmost node in a binary tree. If the `beginRoot` parameter is + * provided, it starts the traversal from that node. If `beginRoot` is not provided or is `null`, it starts the + * traversal from the root of the binary tree. If there are no nodes in the binary tree, it returns `null`. + */ + getLeftMost(beginRoot?: N | BinaryTreeNodeId | null): N | null { + if (typeof beginRoot === 'number') beginRoot = this.get(beginRoot, 'id'); - this._clearResults(); + beginRoot = beginRoot ?? this.root; + if (!beginRoot) return beginRoot; - let cur: N | null | undefined = this.root; - const _reverseEdge = (node: N | null | undefined) => { - let pre: N | null | undefined = null; - let next: N | null | undefined = null; - while (node) { - next = node.right; - node.right = pre; - pre = node; - node = next; - } - return pre; - }; - const _printEdge = (node: N | null) => { - const tail: N | null | undefined = _reverseEdge(node); - let cur: N | null | undefined = tail; - while (cur) { - this._accumulatedByPropertyName(cur, nodeOrPropertyName); - cur = cur.right; - } - _reverseEdge(tail); - }; + if (this._loopType === LoopType.RECURSIVE) { + + const _traverse = (cur: N): N => { + if (!cur.left) return cur; + return _traverse(cur.left); + } + + return _traverse(beginRoot); + } else { + // Indirect implementation of iteration using tail recursion optimization + const _traverse = trampoline((cur: N) => { + if (!cur.left) return cur; + return _traverse.cont(cur.left); + }); + + return _traverse(beginRoot); + } + } + + getRightMost(): N | null; + + getRightMost(node: N): N; + + /** + * The `getRightMost` function returns the rightmost node in a binary tree, either recursively or iteratively using + * tail recursion optimization. + * @param {N | null} [node] - The `node` parameter is an optional parameter of type `N` or `null`. It represents the + * starting node from which we want to find the rightmost node. If no node is provided, the `node` parameter defaults + * to `this.root`, which is the root node of the data structure + * @returns The function `getRightMost` returns the rightmost node (`N`) in a binary tree. If the `node` parameter is + * not provided, it defaults to the root node of the tree. If the tree is empty or the `node` parameter is `null`, the + * function returns `null`. + */ + getRightMost(node?: N | null): N | null { + // TODO support get right most by passing id in + node = node ?? this.root; + if (!node) return node; + + if (this._loopType === LoopType.RECURSIVE) { + const _traverse = (cur: N): N => { + 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: N) => { + if (!cur.right) return cur; + return _traverse.cont(cur.right); + }); + + return _traverse(node); + } + } + + /** + * The function checks if a binary search tree is valid by traversing it either recursively or iteratively. + * @param {N | null} node - The `node` parameter represents the root node of a binary search tree (BST). + * @returns a boolean value. + */ + isSubtreeBST(node: N | null): boolean { + // TODO there is a bug + if (!node) return true; + + if (this._loopType === LoopType.RECURSIVE) { + const dfs = (cur: N | 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: N | null | undefined = node; + while (curr || stack.length > 0) { + while (curr) { + stack.push(curr); + curr = curr.left; + } + curr = stack.pop()!; + if (!(curr) || prev >= curr.id) return false; + prev = curr.id; + curr = curr.right; + } + return true; + } + } + + /** + * The function isBST checks if the binary search tree is valid. + * @returns The `isBST()` function is returning a boolean value. + */ + isBST(): boolean { + return this.isSubtreeBST(this.root); + } + + /** + * The function calculates the size of a subtree by traversing it either recursively or iteratively. + * @param {N | null | undefined} subTreeRoot - The `subTreeRoot` parameter represents the root node of a subtree in a + * binary tree. + * @returns the size of the subtree rooted at `subTreeRoot`. + */ + getSubTreeSize(subTreeRoot: N | null | undefined) { + // TODO support id passed in + let size = 0; + if (!subTreeRoot) return size; + + if (this._loopType === LoopType.RECURSIVE) { + const _traverse = (cur: N) => { + size++; + cur.left && _traverse(cur.left); + cur.right && _traverse(cur.right); + } + + _traverse(subTreeRoot); + return size; + } else { + const stack: N[] = [subTreeRoot]; + + while (stack.length > 0) { + const cur = stack.pop()!; + size++; + cur.right && stack.push(cur.right); + cur.left && stack.push(cur.left); + } + + return size; + } + } + + /** + * The function `subTreeSum` calculates the sum of a specified property in a binary tree or subtree. + * @param {N | BinaryTreeNodeId | null} subTreeRoot - The `subTreeRoot` parameter represents the root node of a binary + * tree or the ID of a binary tree node. It can also be `null` if there is no subtree. + * @param {BinaryTreeNodePropertyName} [propertyName] - propertyName is an optional parameter that specifies the + * property of the binary tree node to use for calculating the sum. It can be either 'id' or 'val'. If propertyName is + * not provided, it defaults to 'id'. + * @returns a number, which is the sum of the values of the specified property in the subtree rooted at `subTreeRoot`. + */ + subTreeSum(subTreeRoot: N | BinaryTreeNodeId | null, propertyName ?: BinaryTreeNodePropertyName): number { + propertyName = propertyName ?? 'id'; + if (typeof subTreeRoot === 'number') subTreeRoot = this.get(subTreeRoot, 'id'); + + if (!subTreeRoot) return 0; + + let sum = 0; + + const _sumByProperty = (cur: N) => { + let needSum: number; + switch (propertyName) { + case 'id': + needSum = cur.id; + 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: N): void => { + sum += _sumByProperty(cur); + cur.left && _traverse(cur.left); + cur.right && _traverse(cur.right); + } + + _traverse(subTreeRoot); + } else { + const stack: N[] = [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; + } + + /** + * The function `subTreeAdd` adds a delta value to a specified property of each node in a subtree. + * @param {N | BinaryTreeNodeId | null} subTreeRoot - The `subTreeRoot` parameter represents the root node of a binary + * tree or the ID of a node in the binary tree. It can also be `null` if there is no subtree to add to. + * @param {number} delta - The `delta` parameter is a number that represents the amount by which the property value of + * each node in the subtree should be incremented. + * @param {BinaryTreeNodePropertyName} [propertyName] - The `propertyName` parameter is an optional parameter that + * specifies the property of the binary tree node that should be modified. If not provided, it defaults to 'id'. + * @returns a boolean value. + */ + subTreeAdd(subTreeRoot: N | BinaryTreeNodeId | null, delta: number, propertyName ?: BinaryTreeNodePropertyName): boolean { + propertyName = propertyName ?? 'id'; + if (typeof subTreeRoot === 'number') subTreeRoot = this.get(subTreeRoot, 'id'); + + if (!subTreeRoot) return false; + + const _addByProperty = (cur: N) => { + switch (propertyName) { + case 'id': + cur.id += delta; + break; + default: + cur.id += delta; + break; + } + } + + if (this._loopType === LoopType.RECURSIVE) { + const _traverse = (cur: N) => { + _addByProperty(cur); + cur.left && _traverse(cur.left); + cur.right && _traverse(cur.right); + }; + + _traverse(subTreeRoot); + } else { + const stack: N[] = [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'): N['val'][]; + + BFS(nodeOrPropertyName: 'node'): N[]; + + /** + * The BFS function performs a breadth-first search on a binary tree, accumulating properties of each node based on a + * specified property name. + * @param {NodeOrPropertyName} [nodeOrPropertyName] - The parameter `nodeOrPropertyName` is an optional parameter that + * represents either a node or a property name. If a node is provided, the breadth-first search (BFS) algorithm will be + * performed starting from that node. If a property name is provided, the BFS algorithm will be performed starting from + * the + * @returns an instance of the `AbstractBinaryTreeNodeProperties` class with generic type `N`. + */ + BFS(nodeOrPropertyName ?: NodeOrPropertyName): AbstractBinaryTreeNodeProperties { + nodeOrPropertyName = nodeOrPropertyName ?? 'id'; + this._clearResults(); + const queue: Array = [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'): N[]; + + DFS(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'node'): N[]; + + /** + * The DFS function performs a depth-first search traversal on a binary tree and returns the accumulated properties of + * each node based on the specified pattern and property name. + * @param {'in' | 'pre' | 'post'} [pattern] - The "pattern" parameter is used to specify the traversal order of the + * binary tree. It can have three possible values: + * @param {NodeOrPropertyName} [nodeOrPropertyName] - The `nodeOrPropertyName` parameter is a string that represents + * the name of a property of the nodes in the binary tree. This property will be used to accumulate values during the + * depth-first search traversal. If no `nodeOrPropertyName` is provided, the default value is `'id'`. + * @returns an instance of the AbstractBinaryTreeNodeProperties class, which contains the accumulated properties of the + * binary tree nodes based on the specified pattern and node or property name. + */ + DFS(pattern ?: 'in' | 'pre' | 'post', nodeOrPropertyName ?: NodeOrPropertyName): AbstractBinaryTreeNodeProperties { + pattern = pattern ?? 'in'; + nodeOrPropertyName = nodeOrPropertyName ?? 'id'; + this._clearResults(); + const _traverse = (node: N) => { + 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[]; + + + // --- start additional methods --- + + DFSIterative(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'id'): BinaryTreeNodeId[]; + + DFSIterative(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'val'): N[]; + + DFSIterative(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'node'): N[]; + + /** + * The DFSIterative function performs an iterative depth-first search traversal on a binary tree, with the option to + * specify the traversal pattern and the property name to accumulate results by. + * @param {'in' | 'pre' | 'post'} [pattern] - The "pattern" parameter determines the order in which the nodes of the + * binary tree are visited during the depth-first search. It can have one of the following values: + * @param {NodeOrPropertyName} [nodeOrPropertyName] - The `nodeOrPropertyName` parameter is used to specify the + * property of the nodes that you want to retrieve or perform operations on during the depth-first search traversal. By + * default, it is set to `'id'`, which means that the traversal will accumulate results based on the `id` property of + * the + * @returns an object of type AbstractBinaryTreeNodeProperties. + */ + DFSIterative(pattern ?: 'in' | 'pre' | 'post', nodeOrPropertyName ?: NodeOrPropertyName): AbstractBinaryTreeNodeProperties { + pattern = pattern || 'in'; + nodeOrPropertyName = nodeOrPropertyName || 'id'; + this._clearResults(); + if (!this.root) return this._getResultByPropertyName(nodeOrPropertyName); + // 0: visit, 1: print + const stack: { opt: 0 | 1, node: N | 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': - 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; + 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); + } } - /** - * The function adds a new node to a binary tree if there is an available position. - * @param {N | null} newNode - The `newNode` parameter is of type `N | null`, which means it can either be a node of - * type `N` or `null`. It represents the node that you want to add to the binary tree. - * @param {N} parent - The parent parameter is of type N, which represents a node in a binary tree. - * @returns either the left or right child node of the parent node, depending on which child is available for adding - * the new node. If a new node is added, the function also updates the size of the binary tree. If neither the left nor - * right child is available, the function returns undefined. If the parent node is null, the function also returns - * undefined. - */ - protected _addTo(newNode: N | null, parent: N): N | null | undefined { - if (parent) { - // When all leaf nodes are null, it will no longer be possible to add new entity nodes to this binary tree. - // In this scenario, null nodes serve as "sentinel nodes," "virtual nodes," or "placeholder nodes." - if (parent.left === undefined) { - parent.left = newNode; - if (newNode) { - this._setSize(this.size + 1); - } - return parent.left; - } else if (parent.right === undefined) { - parent.right = newNode; - if (newNode) { - this._setSize(this.size + 1); - } - return parent.right; + return this._getResultByPropertyName(nodeOrPropertyName); + } + + levelIterative(node: N | null): BinaryTreeNodeId[]; + + levelIterative(node: N | null, nodeOrPropertyName?: 'id'): BinaryTreeNodeId[]; + + levelIterative(node: N | null, nodeOrPropertyName?: 'val'): N['val'][]; + + levelIterative(node: N | null, nodeOrPropertyName?: 'node'): N[]; + + /** + * The `levelIterative` function performs a level-order traversal on a binary tree and returns the values of the nodes + * in an array, based on a specified property name. + * @param {N | null} node - The `node` parameter is a BinaryTreeNode object representing the starting + * node for the level order traversal. It can be null if no specific node is provided, in which case the root node of + * the tree is used as the starting node. + * @param {NodeOrPropertyName} [nodeOrPropertyName] - The `nodeOrPropertyName` parameter is an optional parameter that + * can be either a `BinaryTreeNode` property name or the string `'id'`. If a property name is provided, the function + * will accumulate results based on that property. If no property name is provided, the function will default to + * accumulating results + * @returns The function `levelIterative` returns an object of type `AbstractBinaryTreeNodeProperties`. + */ + levelIterative(node: N | null, nodeOrPropertyName ?: NodeOrPropertyName): AbstractBinaryTreeNodeProperties { + nodeOrPropertyName = nodeOrPropertyName || 'id'; + node = node || this.root; + if (!node) return []; + + this._clearResults(); + const queue: N[] = [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: N | null): BinaryTreeNodeId[][]; + + listLevels(node: N | null, nodeOrPropertyName?: 'id'): BinaryTreeNodeId[][]; + + listLevels(node: N | null, nodeOrPropertyName?: 'val'): N['val'][][]; + + listLevels(node: N | null, nodeOrPropertyName?: 'node'): N[][]; + + /** + * The `listLevels` function collects nodes from a binary tree by a specified property and organizes them into levels. + * @param {N | null} node - The `node` parameter is a BinaryTreeNode object or null. It represents the + * root node of a binary tree. If it is null, the function will use the root node of the current binary tree instance. + * @param {NodeOrPropertyName} [nodeOrPropertyName] - The `nodeOrPropertyName` parameter is an optional parameter that + * specifies the property of the `BinaryTreeNode` object to collect at each level. It can be one of the following + * values: + * @returns The function `listLevels` returns a 2D array of `AbstractBinaryTreeNodeProperty` objects. + */ + listLevels(node: N | null, nodeOrPropertyName?: NodeOrPropertyName): AbstractBinaryTreeNodeProperty[][] { + nodeOrPropertyName = nodeOrPropertyName || 'id'; + node = node || this.root; + if (!node) return []; + + const levelsNodes: AbstractBinaryTreeNodeProperty[][] = []; + + const collectByProperty = (node: N, 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; + default: + levelsNodes[level].push(node.id); + break; + } + } + + if (this.loopType === LoopType.RECURSIVE) { + const _recursive = (node: N, 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: [N, 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; + } + + /** + * The function returns the predecessor of a given node in a binary tree. + * @param node - The parameter `node` is a BinaryTreeNode object, representing a node in a binary tree. + * @returns the predecessor of the given node in a binary tree. + */ + getPredecessor(node: N): N { + if (node.left) { + let predecessor: N | null | undefined = node.left; + while (!(predecessor) || predecessor.right && predecessor.right !== node) { + if (predecessor) { + predecessor = predecessor.right; + } + } + return predecessor; + } else { + return node; + } + } + + morris(): BinaryTreeNodeId[]; + + morris(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'id'): BinaryTreeNodeId[]; + + morris(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'val'): N[]; + + /** + * Time complexity is O(n) + * Space complexity of Iterative DFS equals to recursive DFS which is O(n) because of the stack + */ + + morris(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'node'): N[]; + + /** + * The `morris` function performs an in-order, pre-order, or post-order traversal on a binary tree using the Morris + * traversal algorithm. + * @param {'in' | 'pre' | 'post'} [pattern] - The `pattern` parameter determines the traversal pattern for the binary + * tree. It can have one of three values: + * @param {NodeOrPropertyName} [nodeOrPropertyName] - The `nodeOrPropertyName` parameter is used to specify the + * property name of the nodes that you want to retrieve. It can be any valid property name of the nodes in the binary + * tree. + * @returns an array of AbstractBinaryTreeNodeProperties objects. + */ + morris(pattern?: 'in' | 'pre' | 'post', nodeOrPropertyName?: NodeOrPropertyName): AbstractBinaryTreeNodeProperties { + if (this.root === null) return []; + + pattern = pattern || 'in'; + nodeOrPropertyName = nodeOrPropertyName || 'id'; + + this._clearResults(); + + let cur: N | null | undefined = this.root; + const _reverseEdge = (node: N | null | undefined) => { + let pre: N | null | undefined = null; + let next: N | null | undefined = null; + while (node) { + next = node.right; + node.right = pre; + pre = node; + node = next; + } + return pre; + }; + const _printEdge = (node: N | null) => { + const tail: N | null | undefined = _reverseEdge(node); + let cur: N | 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 { - return; + predecessor.right = null; } - } else { - return; + } + this._accumulatedByPropertyName(cur, nodeOrPropertyName); + cur = cur.right; } - } - - /** - * The function sets the loop type for a protected variable. - * @param {LoopType} value - The value parameter is of type LoopType. - */ - protected _setLoopType(value: LoopType) { - this._loopType = value; - } - - /** - * The function sets the value of the `_visitedId` property in a protected manner. - * @param {BinaryTreeNodeId[]} value - value is an array of BinaryTreeNodeId values. - */ - protected _setVisitedId(value: BinaryTreeNodeId[]) { - this._visitedId = value; - } - - /** - * The function sets the value of the "_visitedVal" property to the given array. - * @param value - An array of type N. - */ - protected _setVisitedVal(value: Array) { - this._visitedVal = value; - } - - /** - * The function sets the value of the _visitedNode property. - * @param {N[]} value - N[] is an array of elements of type N. - */ - protected _setVisitedNode(value: N[]) { - this._visitedNode = value; - } - - /** - * The function sets the value of the `_visitedLeftSum` property to the provided array. - * @param {number[]} value - An array of numbers that represents the visited left sum. - */ - protected _setVisitedLeftSum(value: number[]) { - this._visitedLeftSum = value; - } - - - /** - * The function sets the root property of an object to a given value, and if the value is not null, it also sets the - * parent property of the value to undefined. - * @param {N | null} v - The parameter `v` is of type `N | null`, which means it can either be of type `N` or `null`. - */ - protected _setRoot(v: N | null) { - if (v) { - v.parent = undefined; + 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; } - this._root = v; - } - - /** - * The function sets the size of a protected variable. - * @param {number} v - number - */ - protected _setSize(v: number) { - this._size = v; - } - - /** - * The function `_clearResults` resets the values of several arrays used for tracking visited nodes and their - * properties. - */ - protected _clearResults() { - this._visitedId = []; - this._visitedVal = []; - this._visitedNode = []; - this._visitedLeftSum = []; - } - - /** - * The function checks if a given property of a binary tree node matches a specified value, and if so, adds the node to - * a result array. - * @param {N} cur - The current node being processed. - * @param {(N | null | undefined)[]} result - An array that stores the matching nodes. - * @param {BinaryTreeNodeId | N} nodeProperty - The `nodeProperty` parameter is either a `BinaryTreeNodeId` or a `N` - * type. It represents the property value that we are comparing against in the switch statement. - * @param {BinaryTreeNodePropertyName} [propertyName] - The `propertyName` parameter is an optional parameter that - * specifies the property name to compare against when pushing nodes into the `result` array. It can be either `'id'` - * or `'val'`. If it is not provided or is not equal to `'id'` or `'val'`, the - * @param {boolean} [onlyOne] - The `onlyOne` parameter is an optional boolean parameter that determines whether to - * stop after finding the first matching node or continue searching for all matching nodes. If `onlyOne` is set to - * `true`, the function will stop after finding the first matching node and return `true`. If `onlyOne - * @returns a boolean value indicating whether only one matching node should be pushed into the result array. - */ - protected _pushByPropertyNameStopOrNot(cur: N, result: (N | null | undefined)[], nodeProperty: BinaryTreeNodeId | N, propertyName ?: BinaryTreeNodePropertyName, onlyOne ?: boolean) { - switch (propertyName) { - case 'id': - if (cur.id === 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; + 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; } - /** - * The function `_accumulatedByPropertyName` accumulates values from a given node based on the specified property name. - * @param {N} node - The `node` parameter is of type `N`, which represents a node in a data structure. - * @param {NodeOrPropertyName} [nodeOrPropertyName] - The `nodeOrPropertyName` parameter is an optional parameter that - * can be either a string representing a property name or a reference to a `Node` object. If it is a string, it - * specifies the property name to be used for accumulating values. If it is a `Node` object, it specifies - */ - protected _accumulatedByPropertyName(node: N, nodeOrPropertyName ?: NodeOrPropertyName) { - nodeOrPropertyName = nodeOrPropertyName ?? 'id'; + return this._getResultByPropertyName(nodeOrPropertyName); + } - 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; - default: - this._visitedId.push(node.id); - break; + /** + * The function adds a new node to a binary tree if there is an available position. + * @param {N | null} newNode - The `newNode` parameter is of type `N | null`, which means it can either be a node of + * type `N` or `null`. It represents the node that you want to add to the binary tree. + * @param {N} parent - The parent parameter is of type N, which represents a node in a binary tree. + * @returns either the left or right child node of the parent node, depending on which child is available for adding + * the new node. If a new node is added, the function also updates the size of the binary tree. If neither the left nor + * right child is available, the function returns undefined. If the parent node is null, the function also returns + * undefined. + */ + protected _addTo(newNode: N | null, parent: N): N | null | undefined { + if (parent) { + // When all leaf nodes are null, it will no longer be possible to add new entity nodes to this binary tree. + // In this scenario, null nodes serve as "sentinel nodes," "virtual nodes," or "placeholder nodes." + if (parent.left === undefined) { + parent.left = newNode; + if (newNode) { + this._setSize(this.size + 1); } - } - - /** - * 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 - */ - - /** - * The function `_getResultByPropertyName` returns the corresponding property value based on the given node or property - * name. - * @param {NodeOrPropertyName} [nodeOrPropertyName] - The parameter `nodeOrPropertyName` is an optional parameter that - * can accept either a `NodeOrPropertyName` type or be undefined. - * @returns The method `_getResultByPropertyName` returns an instance of `AbstractBinaryTreeNodeProperties`. - */ - protected _getResultByPropertyName(nodeOrPropertyName ?: NodeOrPropertyName): AbstractBinaryTreeNodeProperties { - nodeOrPropertyName = nodeOrPropertyName ?? 'id'; - - switch (nodeOrPropertyName) { - case 'id': - return this._visitedId; - case 'val': - return this._visitedVal; - case 'node': - return this._visitedNode; - default: - return this._visitedId; + return parent.left; + } else if (parent.right === undefined) { + parent.right = newNode; + if (newNode) { + this._setSize(this.size + 1); } + return parent.right; + } else { + return; + } + } else { + return; } + } - // --- end additional methods --- -} \ No newline at end of file + /** + * The function sets the loop type for a protected variable. + * @param {LoopType} value - The value parameter is of type LoopType. + */ + protected _setLoopType(value: LoopType) { + this._loopType = value; + } + + /** + * The function sets the value of the `_visitedId` property in a protected manner. + * @param {BinaryTreeNodeId[]} value - value is an array of BinaryTreeNodeId values. + */ + protected _setVisitedId(value: BinaryTreeNodeId[]) { + this._visitedId = value; + } + + /** + * The function sets the value of the "_visitedVal" property to the given array. + * @param value - An array of type N. + */ + protected _setVisitedVal(value: Array) { + this._visitedVal = value; + } + + /** + * The function sets the value of the _visitedNode property. + * @param {N[]} value - N[] is an array of elements of type N. + */ + protected _setVisitedNode(value: N[]) { + this._visitedNode = value; + } + + /** + * The function sets the value of the `_visitedLeftSum` property to the provided array. + * @param {number[]} value - An array of numbers that represents the visited left sum. + */ + protected _setVisitedLeftSum(value: number[]) { + this._visitedLeftSum = value; + } + + + /** + * The function sets the root property of an object to a given value, and if the value is not null, it also sets the + * parent property of the value to undefined. + * @param {N | null} v - The parameter `v` is of type `N | null`, which means it can either be of type `N` or `null`. + */ + protected _setRoot(v: N | null) { + if (v) { + v.parent = undefined; + } + this._root = v; + } + + /** + * The function sets the size of a protected variable. + * @param {number} v - number + */ + protected _setSize(v: number) { + this._size = v; + } + + /** + * The function `_clearResults` resets the values of several arrays used for tracking visited nodes and their + * properties. + */ + protected _clearResults() { + this._visitedId = []; + this._visitedVal = []; + this._visitedNode = []; + this._visitedLeftSum = []; + } + + /** + * The function checks if a given property of a binary tree node matches a specified value, and if so, adds the node to + * a result array. + * @param {N} cur - The current node being processed. + * @param {(N | null | undefined)[]} result - An array that stores the matching nodes. + * @param {BinaryTreeNodeId | N} nodeProperty - The `nodeProperty` parameter is either a `BinaryTreeNodeId` or a `N` + * type. It represents the property value that we are comparing against in the switch statement. + * @param {BinaryTreeNodePropertyName} [propertyName] - The `propertyName` parameter is an optional parameter that + * specifies the property name to compare against when pushing nodes into the `result` array. It can be either `'id'` + * or `'val'`. If it is not provided or is not equal to `'id'` or `'val'`, the + * @param {boolean} [onlyOne] - The `onlyOne` parameter is an optional boolean parameter that determines whether to + * stop after finding the first matching node or continue searching for all matching nodes. If `onlyOne` is set to + * `true`, the function will stop after finding the first matching node and return `true`. If `onlyOne + * @returns a boolean value indicating whether only one matching node should be pushed into the result array. + */ + protected _pushByPropertyNameStopOrNot(cur: N, result: (N | null | undefined)[], nodeProperty: BinaryTreeNodeId | N, propertyName ?: BinaryTreeNodePropertyName, onlyOne ?: boolean) { + switch (propertyName) { + case 'id': + if (cur.id === 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; + } + } + + /** + * The function `_accumulatedByPropertyName` accumulates values from a given node based on the specified property name. + * @param {N} node - The `node` parameter is of type `N`, which represents a node in a data structure. + * @param {NodeOrPropertyName} [nodeOrPropertyName] - The `nodeOrPropertyName` parameter is an optional parameter that + * can be either a string representing a property name or a reference to a `Node` object. If it is a string, it + * specifies the property name to be used for accumulating values. If it is a `Node` object, it specifies + */ + protected _accumulatedByPropertyName(node: N, 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; + default: + this._visitedId.push(node.id); + break; + } + } + + /** + * 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 + */ + + /** + * The function `_getResultByPropertyName` returns the corresponding property value based on the given node or property + * name. + * @param {NodeOrPropertyName} [nodeOrPropertyName] - The parameter `nodeOrPropertyName` is an optional parameter that + * can accept either a `NodeOrPropertyName` type or be undefined. + * @returns The method `_getResultByPropertyName` returns an instance of `AbstractBinaryTreeNodeProperties`. + */ + protected _getResultByPropertyName(nodeOrPropertyName ?: NodeOrPropertyName): AbstractBinaryTreeNodeProperties { + nodeOrPropertyName = nodeOrPropertyName ?? 'id'; + + switch (nodeOrPropertyName) { + case 'id': + return this._visitedId; + case 'val': + return this._visitedVal; + case 'node': + return this._visitedNode; + default: + return this._visitedId; + } + } + + // --- end additional methods --- +} diff --git a/src/data-structures/binary-tree/avl-tree.ts b/src/data-structures/binary-tree/avl-tree.ts index a568356..f6542af 100644 --- a/src/data-structures/binary-tree/avl-tree.ts +++ b/src/data-structures/binary-tree/avl-tree.ts @@ -10,292 +10,292 @@ import type {AVLTreeNodeNested, AVLTreeOptions, BinaryTreeDeletedResult, BinaryT import {IAVLTree, IAVLTreeNode} from '../../interfaces'; export class AVLTreeNode = AVLTreeNodeNested> extends BSTNode implements IAVLTreeNode { - constructor(id: BinaryTreeNodeId, val?: T) { - super(id, val); - } + constructor(id: BinaryTreeNodeId, val?: T) { + super(id, val); + } } export class AVLTree = AVLTreeNode> extends BST implements IAVLTree { - /** - * This is a constructor function for an AVL tree data structure in TypeScript. - * @param {AVLTreeOptions} [options] - The `options` parameter is an optional object that can be passed to the - * constructor of the AVLTree class. It allows you to customize the behavior of the AVL tree by providing different - * options. - */ - constructor(options?: AVLTreeOptions) { - super(options); - } + /** + * This is a constructor function for an AVL tree data structure in TypeScript. + * @param {AVLTreeOptions} [options] - The `options` parameter is an optional object that can be passed to the + * constructor of the AVLTree class. It allows you to customize the behavior of the AVL tree by providing different + * options. + */ + constructor(options?: AVLTreeOptions) { + super(options); + } - /** - * The function creates a new AVL tree node with the given id and value. - * @param {BinaryTreeNodeId} id - The `id` parameter is the identifier for the binary tree node. It is used to uniquely - * identify each node in the tree. - * @param [val] - The `val` parameter is an optional value that can be assigned to the node. It represents the value - * that will be stored in the node. - * @returns a new AVLTreeNode object with the specified id and value. - */ - override createNode(id: BinaryTreeNodeId, val?: N['val']): N { - return new AVLTreeNode(id, val) as N; - } + /** + * The function creates a new AVL tree node with the given id and value. + * @param {BinaryTreeNodeId} id - The `id` parameter is the identifier for the binary tree node. It is used to uniquely + * identify each node in the tree. + * @param [val] - The `val` parameter is an optional value that can be assigned to the node. It represents the value + * that will be stored in the node. + * @returns a new AVLTreeNode object with the specified id and value. + */ + override createNode(id: BinaryTreeNodeId, val?: N['val']): N { + return new AVLTreeNode(id, val) as N; + } - /** - * The function overrides the add method of a binary tree node and balances the tree after inserting a new node. - * @param {BinaryTreeNodeId} id - The `id` parameter is the identifier of the binary tree node that we want to add. - * @param [val] - The `val` parameter is an optional value that can be assigned to the node being added. It is of type - * `N['val']`, which means it should be of the same type as the `val` property of the nodes in the binary tree. - * @returns The method is returning the inserted node, or null or undefined if the insertion was not successful. - */ - override add(id: BinaryTreeNodeId, val?: N['val']): N | null | undefined { - // TODO support node as a param - const inserted = super.add(id, val); - if (inserted) this.balancePath(inserted); - return inserted; - } + /** + * The function overrides the add method of a binary tree node and balances the tree after inserting a new node. + * @param {BinaryTreeNodeId} id - The `id` parameter is the identifier of the binary tree node that we want to add. + * @param [val] - The `val` parameter is an optional value that can be assigned to the node being added. It is of type + * `N['val']`, which means it should be of the same type as the `val` property of the nodes in the binary tree. + * @returns The method is returning the inserted node, or null or undefined if the insertion was not successful. + */ + override add(id: BinaryTreeNodeId, val?: N['val']): N | null | undefined { + // TODO support node as a param + const inserted = super.add(id, val); + if (inserted) this.balancePath(inserted); + return inserted; + } - /** - * The function overrides the remove method of the Binary Search Tree class, performs the removal operation, and - * then balances the tree if necessary. - * @param {BinaryTreeNodeId} id - The `id` parameter represents the identifier of the binary tree node that needs to be - * removed from the AVL tree. - * @param {boolean} [isUpdateAllLeftSum] - The `isUpdateAllLeftSum` parameter is an optional boolean parameter that - * determines whether the left sum of all nodes in the AVL tree should be updated after removing a node. If - * `isUpdateAllLeftSum` is set to `true`, the left sum of all nodes will be recalculated. - * @returns The method is returning an array of `AVLTreeDeleted` objects. - */ - override remove(id: BinaryTreeNodeId, isUpdateAllLeftSum?: boolean): BinaryTreeDeletedResult[] { - const deletedResults = super.remove(id, isUpdateAllLeftSum); - for (const {needBalanced} of deletedResults) { - if (needBalanced) { - this.balancePath(needBalanced); - } - } - return deletedResults; + /** + * The function overrides the remove method of the Binary Search Tree class, performs the removal operation, and + * then balances the tree if necessary. + * @param {BinaryTreeNodeId} id - The `id` parameter represents the identifier of the binary tree node that needs to be + * removed from the AVL tree. + * @param {boolean} [isUpdateAllLeftSum] - The `isUpdateAllLeftSum` parameter is an optional boolean parameter that + * determines whether the left sum of all nodes in the AVL tree should be updated after removing a node. If + * `isUpdateAllLeftSum` is set to `true`, the left sum of all nodes will be recalculated. + * @returns The method is returning an array of `AVLTreeDeleted` objects. + */ + override remove(id: BinaryTreeNodeId, isUpdateAllLeftSum?: boolean): BinaryTreeDeletedResult[] { + const deletedResults = super.remove(id, isUpdateAllLeftSum); + for (const {needBalanced} of deletedResults) { + if (needBalanced) { + this.balancePath(needBalanced); + } } + return deletedResults; + } - /** - * The balance factor of a given AVL tree node is calculated by subtracting the height of its left subtree from the - * height of its right subtree. - * @param node - The parameter "node" is of type N, which represents a node in an AVL tree. - * @returns The balance factor of the given AVL tree node. - */ - balanceFactor(node: N): 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; - } + /** + * The balance factor of a given AVL tree node is calculated by subtracting the height of its left subtree from the + * height of its right subtree. + * @param node - The parameter "node" is of type N, which represents a node in an AVL tree. + * @returns The balance factor of the given AVL tree node. + */ + balanceFactor(node: N): 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; + } - /** - * The function updates the height of a node in an AVL tree based on the heights of its left and right subtrees. - * @param node - The parameter `node` is an AVLTreeNode object, which represents a node in an AVL tree. - */ - updateHeight(node: N): 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); - } + /** + * The function updates the height of a node in an AVL tree based on the heights of its left and right subtrees. + * @param node - The parameter `node` is an AVLTreeNode object, which represents a node in an AVL tree. + */ + updateHeight(node: N): 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); + } - /** - * The `balancePath` function balances the AVL tree by performing appropriate rotations based on the balance factor of - * each node in the path from the given node to the root. - * @param node - The `node` parameter is an AVLTreeNode object, which represents a node in an AVL tree. - */ - balancePath(node: N): 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 - } - } - } - } - } - - /** - * The `balanceLL` function performs a left-left rotation on an AVL tree to balance it. - * @param A - The parameter A is an AVLTreeNode object. - */ - balanceLL(A: N): 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._setRoot(B); - } else { - if (parentOfA?.left === A) { - parentOfA.left = B; + /** + * The `balancePath` function balances the AVL tree by performing appropriate rotations based on the balance factor of + * each node in the path from the given node to the root. + * @param node - The `node` parameter is an AVLTreeNode object, which represents a node in an AVL tree. + */ + balancePath(node: N): 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 { - if (parentOfA) parentOfA.right = B; + 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 + } + } + } + } + } - 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); + /** + * The `balanceLL` function performs a left-left rotation on an AVL tree to balance it. + * @param A - The parameter A is an AVLTreeNode object. + */ + balanceLL(A: N): 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._setRoot(B); + } else { + if (parentOfA?.left === A) { + parentOfA.left = B; + } else { + if (parentOfA) parentOfA.right = B; + } } - /** - * The `balanceLR` function performs a left-right rotation to balance an AVL tree. - * @param A - A is an AVLTreeNode object. - */ - balanceLR(A: N): 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 (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); + } - if (C) { - if (C.left) { - C.left.parent = B; - } - if (C.right) { - C.right.parent = A; - } - C.parent = parentOfA; - } + /** + * The `balanceLR` function performs a left-right rotation to balance an AVL tree. + * @param A - A is an AVLTreeNode object. + */ + balanceLR(A: N): 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 (A === this.root) { - if (C) this._setRoot(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._setRoot(C); + } else { + if (parentOfA) { + if (parentOfA.left === A) { + parentOfA.left = C; } else { - if (parentOfA) { - if (parentOfA.left === A) { - parentOfA.left = C; - } else { - parentOfA.right = C; - } - } + 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); + } } - /** - * The `balanceRR` function performs a right-right rotation on an AVL tree to balance it. - * @param A - The parameter A is an AVLTreeNode object. - */ - balanceRR(A: N): 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 (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; + } - if (A === this.root) { - if (B) this._setRoot(B); + this.updateHeight(A); // Adjust heights + B && this.updateHeight(B); + C && this.updateHeight(C); + } + + /** + * The `balanceRR` function performs a right-right rotation on an AVL tree to balance it. + * @param A - The parameter A is an AVLTreeNode object. + */ + balanceRR(A: N): 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._setRoot(B); + } else { + if (parentOfA) { + if (parentOfA.left === A) { + parentOfA.left = B; } else { - if (parentOfA) { - if (parentOfA.left === A) { - parentOfA.left = B; - } else { - parentOfA.right = B; - } - } + 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); + } } - /** - * The `balanceRL` function performs a right-left rotation to balance an AVL tree. - * @param A - A is an AVLTreeNode object. - */ - balanceRL(A: N): 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 - } + if (B) { + A.right = B.left; // Make T2 the right subtree of A + B.left = A; + } + this.updateHeight(A); + B && this.updateHeight(B); + } - A.parent = C; - if (B) B.parent = C; + /** + * The `balanceRL` function performs a right-left rotation to balance an AVL tree. + * @param A - A is an AVLTreeNode object. + */ + balanceRL(A: N): 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 + } - if (C) { - if (C.left) { - C.left.parent = A; - } - if (C.right) { - C.right.parent = B; - } - C.parent = parentOfA; - } + 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._setRoot(C); + if (A === this.root) { + if (C) this._setRoot(C); + } else { + if (parentOfA) { + if (parentOfA.left === A) { + parentOfA.left = C; } else { - if (parentOfA) { - if (parentOfA.left === A) { - parentOfA.left = C; - } else { - parentOfA.right = C; - } - } + 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); + } } + + 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/binary-indexed-tree.ts b/src/data-structures/binary-tree/binary-indexed-tree.ts index 91a0716..5c532c0 100644 --- a/src/data-structures/binary-tree/binary-indexed-tree.ts +++ b/src/data-structures/binary-tree/binary-indexed-tree.ts @@ -7,72 +7,72 @@ */ export class BinaryIndexedTree { - /** - * The constructor initializes an array with a specified length and fills it with zeros. - * @param {number} n - The parameter `n` represents the size of the array that will be used to store the sum tree. The - * sum tree is a binary tree data structure used to efficiently calculate the sum of a range of elements in an array. - * The size of the sum tree array is `n + 1` because - */ - constructor(n: number) { - this._sumTree = new Array(n + 1).fill(0); - } + /** + * The constructor initializes an array with a specified length and fills it with zeros. + * @param {number} n - The parameter `n` represents the size of the array that will be used to store the sum tree. The + * sum tree is a binary tree data structure used to efficiently calculate the sum of a range of elements in an array. + * The size of the sum tree array is `n + 1` because + */ + constructor(n: number) { + this._sumTree = new Array(n + 1).fill(0); + } - private _sumTree: number[]; + private _sumTree: number[]; - get sumTree(): number[] { - return this._sumTree; - } + get sumTree(): number[] { + return this._sumTree; + } - static lowBit(x: number) { - return x & (-x); - } + static lowBit(x: number) { + return x & (-x); + } - /** - * The update function updates the values in a binary indexed tree by adding a delta value to the specified index and - * its ancestors. - * @param {number} i - The parameter `i` represents the index of the element in the `_sumTree` array that needs to be - * updated. - * @param {number} delta - The "delta" parameter represents the change in value that needs to be added to the element - * at index "i" in the "_sumTree" array. - */ - update(i: number, delta: number) { - while (i < this._sumTree.length) { - this._sumTree[i] += delta; - i += BinaryIndexedTree.lowBit(i); - } + /** + * The update function updates the values in a binary indexed tree by adding a delta value to the specified index and + * its ancestors. + * @param {number} i - The parameter `i` represents the index of the element in the `_sumTree` array that needs to be + * updated. + * @param {number} delta - The "delta" parameter represents the change in value that needs to be added to the element + * at index "i" in the "_sumTree" array. + */ + update(i: number, delta: number) { + while (i < this._sumTree.length) { + this._sumTree[i] += delta; + i += BinaryIndexedTree.lowBit(i); } + } - /** - * The function calculates the prefix sum of an array using a binary indexed tree. - * @param {number} i - The parameter "i" in the function "getPrefixSum" represents the index of the element in the - * array for which we want to calculate the prefix sum. - * @returns The function `getPrefixSum` returns the prefix sum of the elements in the binary indexed tree up to index - * `i`. - */ - getPrefixSum(i: number) { - let sum = 0; - while (i > 0) { - sum += this._sumTree[i]; - i -= BinaryIndexedTree.lowBit(i); - } - return sum; + /** + * The function calculates the prefix sum of an array using a binary indexed tree. + * @param {number} i - The parameter "i" in the function "getPrefixSum" represents the index of the element in the + * array for which we want to calculate the prefix sum. + * @returns The function `getPrefixSum` returns the prefix sum of the elements in the binary indexed tree up to index + * `i`. + */ + getPrefixSum(i: number) { + let sum = 0; + while (i > 0) { + sum += this._sumTree[i]; + i -= BinaryIndexedTree.lowBit(i); } + return sum; + } - /** - * The function `getRangeSum` calculates the sum of a range of numbers in an array. - * @param {number} start - The start parameter is the starting index of the range for which we want to calculate the - * sum. - * @param {number} end - The "end" parameter represents the ending index of the range for which we want to calculate - * the sum. - * @returns the sum of the elements in the range specified by the start and end indices. - */ - 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); - } + /** + * The function `getRangeSum` calculates the sum of a range of numbers in an array. + * @param {number} start - The start parameter is the starting index of the range for which we want to calculate the + * sum. + * @param {number} end - The "end" parameter represents the ending index of the range for which we want to calculate + * the sum. + * @returns the sum of the elements in the range specified by the start and end indices. + */ + 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); + } - protected _setSumTree(value: number[]) { - this._sumTree = value; - } + protected _setSumTree(value: number[]) { + this._sumTree = value; + } } diff --git a/src/data-structures/binary-tree/binary-tree.ts b/src/data-structures/binary-tree/binary-tree.ts index 725716f..abeb216 100644 --- a/src/data-structures/binary-tree/binary-tree.ts +++ b/src/data-structures/binary-tree/binary-tree.ts @@ -11,32 +11,32 @@ import {AbstractBinaryTree, AbstractBinaryTreeNode} from './abstract-binary-tree import {IBinaryTree, IBinaryTreeNode} from '../../interfaces'; export class BinaryTreeNode = BinaryTreeNodeNested> extends AbstractBinaryTreeNode implements IBinaryTreeNode { - constructor(id: BinaryTreeNodeId, val?: T) { - super(id, val); - } + constructor(id: BinaryTreeNodeId, val?: T) { + super(id, val); + } } export class BinaryTree = BinaryTreeNode> extends AbstractBinaryTree implements IBinaryTree { - /** - * This is a constructor function for a binary tree class that takes an optional options parameter. - * @param {BinaryTreeOptions} [options] - The `options` parameter is an optional object that can be passed to the - * constructor of the `BinaryTree` class. It allows you to customize the behavior of the binary tree by providing - * different configuration options. - */ - constructor(options?: BinaryTreeOptions) { - super(options); - } + /** + * This is a constructor function for a binary tree class that takes an optional options parameter. + * @param {BinaryTreeOptions} [options] - The `options` parameter is an optional object that can be passed to the + * constructor of the `BinaryTree` class. It allows you to customize the behavior of the binary tree by providing + * different configuration options. + */ + constructor(options?: BinaryTreeOptions) { + super(options); + } - /** - * The function creates a new binary tree node with an optional value. - * @param {BinaryTreeNodeId} id - The `id` parameter is the identifier for the binary tree node. It is of type - * `BinaryTreeNodeId`, which represents the unique identifier for each node in the binary tree. - * @param [val] - The `val` parameter is an optional value that can be assigned to the node. It represents the value - * stored in the node. - * @returns a new instance of a BinaryTreeNode with the specified id and value. - */ - createNode(id: BinaryTreeNodeId, val?: N['val']): N { - return new BinaryTreeNode(id, val) as N; - } -} \ No newline at end of file + /** + * The function creates a new binary tree node with an optional value. + * @param {BinaryTreeNodeId} id - The `id` parameter is the identifier for the binary tree node. It is of type + * `BinaryTreeNodeId`, which represents the unique identifier for each node in the binary tree. + * @param [val] - The `val` parameter is an optional value that can be assigned to the node. It represents the value + * stored in the node. + * @returns a new instance of a BinaryTreeNode with the specified id and value. + */ + createNode(id: BinaryTreeNodeId, val?: N['val']): N { + return new BinaryTreeNode(id, val) as N; + } +} diff --git a/src/data-structures/binary-tree/bst.ts b/src/data-structures/binary-tree/bst.ts index 6934be3..b5649c9 100644 --- a/src/data-structures/binary-tree/bst.ts +++ b/src/data-structures/binary-tree/bst.ts @@ -11,428 +11,428 @@ import {BinaryTree, BinaryTreeNode} from './binary-tree'; import {IBST, IBSTNode} from '../../interfaces'; export class BSTNode = BSTNodeNested> extends BinaryTreeNode implements IBSTNode { - constructor(id: BinaryTreeNodeId, val?: T) { - super(id, val); - } + constructor(id: BinaryTreeNodeId, val?: T) { + super(id, val); + } } export class BST = BSTNode> extends BinaryTree implements IBST { - /** - * The constructor function initializes a binary search tree object with an optional comparator function. - * @param {BSTOptions} [options] - An optional object that contains configuration options for the binary search tree. - */ - constructor(options?: BSTOptions) { - super(options); - if (options !== undefined) { - const {comparator} = options; - if (comparator !== undefined) { - this._comparator = comparator; + /** + * The constructor function initializes a binary search tree object with an optional comparator function. + * @param {BSTOptions} [options] - An optional object that contains configuration options for the binary search tree. + */ + constructor(options?: BSTOptions) { + super(options); + if (options !== undefined) { + const {comparator} = options; + if (comparator !== undefined) { + this._comparator = comparator; + } + } + } + + /** + * The function creates a new binary search tree node with the given id and value. + * @param {BinaryTreeNodeId} id - The `id` parameter is the identifier for the binary tree node. It is used to uniquely + * identify each node in the binary tree. + * @param [val] - The `val` parameter is an optional value that can be assigned to the node. It represents the value + * that will be stored in the node. + * @returns a new instance of the BSTNode class with the specified id and value. + */ + override createNode(id: BinaryTreeNodeId, val?: N['val']): N { + return new BSTNode(id, val) as N; + } + + /** + * The `add` function adds a new node to a binary tree, ensuring that duplicates are not accepted. + * @param {BinaryTreeNodeId} id - The `id` parameter is the identifier of the binary tree node that we want to add. It + * is of type `BinaryTreeNodeId`. + * @param [val] - The `val` parameter is an optional value that can be assigned to the node being added. It represents + * the value associated with the node. + * @returns The function `add` returns the inserted node (`inserted`) if it was successfully added to the binary tree. + * If the node was not added (e.g., due to a duplicate ID), it returns `null` or `undefined`. + */ + override add(id: BinaryTreeNodeId, val?: N['val']): N | null | undefined { + // TODO support node as a param + let inserted: N | null = null; + const newNode = this.createNode(id, val); + if (this.root === null) { + this._setRoot(newNode); + this._setSize(this.size + 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.val = newNode.val; } - } - } - - /** - * The function creates a new binary search tree node with the given id and value. - * @param {BinaryTreeNodeId} id - The `id` parameter is the identifier for the binary tree node. It is used to uniquely - * identify each node in the binary tree. - * @param [val] - The `val` parameter is an optional value that can be assigned to the node. It represents the value - * that will be stored in the node. - * @returns a new instance of the BSTNode class with the specified id and value. - */ - override createNode(id: BinaryTreeNodeId, val?: N['val']): N { - return new BSTNode(id, val) as N; - } - - /** - * The `add` function adds a new node to a binary tree, ensuring that duplicates are not accepted. - * @param {BinaryTreeNodeId} id - The `id` parameter is the identifier of the binary tree node that we want to add. It - * is of type `BinaryTreeNodeId`. - * @param [val] - The `val` parameter is an optional value that can be assigned to the node being added. It represents - * the value associated with the node. - * @returns The function `add` returns the inserted node (`inserted`) if it was successfully added to the binary tree. - * If the node was not added (e.g., due to a duplicate ID), it returns `null` or `undefined`. - */ - override add(id: BinaryTreeNodeId, val?: N['val']): N | null | undefined { - // TODO support node as a param - let inserted: N | null = null; - const newNode = this.createNode(id, val); - if (this.root === null) { - this._setRoot(newNode); - this._setSize(this.size + 1); - inserted = (this.root); + //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; + } + //Add to the left of the current node + cur.left = newNode; + this._setSize(this.size + 1); + 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; + } + //Add to the right of the current node + cur.right = newNode; + this._setSize(this.size + 1); + traversing = false; + inserted = (cur.right); + } else { + //Traverse the left of the current node + if (cur.right) cur = cur.right; + } + } } 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.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; - } - //Add to the left of the current node - cur.left = newNode; - this._setSize(this.size + 1); - 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; - } - //Add to the right of the current node - cur.right = newNode; - this._setSize(this.size + 1); - traversing = false; - inserted = (cur.right); - } else { - //Traverse the left of the current node - if (cur.right) cur = cur.right; - } - } - } else { - traversing = false; - } - } + traversing = false; } - return inserted; + } } + return inserted; + } - /** - * The function returns the first node in a binary tree that matches the given property name and value. - * @param {BinaryTreeNodeId | N} nodeProperty - The `nodeProperty` parameter can be either a `BinaryTreeNodeId` or a - * generic type `N`. It represents the property of the binary tree node that you want to search for. - * @param {BinaryTreeNodePropertyName} [propertyName] - The `propertyName` parameter is an optional parameter that - * specifies the property name to use for searching the binary tree nodes. If not provided, it defaults to `'id'`. - * @returns The method is returning either a BinaryTreeNodeId or N (generic type) or null. - */ - override get(nodeProperty: BinaryTreeNodeId | N, propertyName ?: BinaryTreeNodePropertyName): N | null { - propertyName = propertyName ?? 'id'; - return this.getNodes(nodeProperty, propertyName, true)[0] ?? null; - } + /** + * The function returns the first node in a binary tree that matches the given property name and value. + * @param {BinaryTreeNodeId | N} nodeProperty - The `nodeProperty` parameter can be either a `BinaryTreeNodeId` or a + * generic type `N`. It represents the property of the binary tree node that you want to search for. + * @param {BinaryTreeNodePropertyName} [propertyName] - The `propertyName` parameter is an optional parameter that + * specifies the property name to use for searching the binary tree nodes. If not provided, it defaults to `'id'`. + * @returns The method is returning either a BinaryTreeNodeId or N (generic type) or null. + */ + override get(nodeProperty: BinaryTreeNodeId | N, propertyName ?: BinaryTreeNodePropertyName): N | null { + propertyName = propertyName ?? 'id'; + return this.getNodes(nodeProperty, propertyName, true)[0] ?? null; + } - /** - * The function returns the id of the rightmost node if the comparison between two values is less than, the id of the - * leftmost node if the comparison is greater than, and the id of the rightmost node otherwise. - * @returns The method `lastKey()` returns the id of the rightmost node in the binary tree if the comparison between - * the values at index 0 and 1 is less than, otherwise it returns the id of the leftmost node. If the comparison is - * equal, it returns the id of the rightmost node. If there are no nodes in the tree, it returns 0. - */ - lastKey(): BinaryTreeNodeId { - 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; - } + /** + * The function returns the id of the rightmost node if the comparison between two values is less than, the id of the + * leftmost node if the comparison is greater than, and the id of the rightmost node otherwise. + * @returns The method `lastKey()` returns the id of the rightmost node in the binary tree if the comparison between + * the values at index 0 and 1 is less than, otherwise it returns the id of the leftmost node. If the comparison is + * equal, it returns the id of the rightmost node. If there are no nodes in the tree, it returns 0. + */ + lastKey(): BinaryTreeNodeId { + 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; + } - /** - * The function `getNodes` returns an array of nodes in a binary tree that match a given property value. - * @param {BinaryTreeNodeId | N} nodeProperty - The `nodeProperty` parameter can be either a `BinaryTreeNodeId` or an - * `N` type. It represents the property of the binary tree node that you want to compare with. - * @param {BinaryTreeNodePropertyName} [propertyName] - The `propertyName` parameter is an optional parameter that - * specifies the property name to use for comparison. If not provided, it defaults to `'id'`. - * @param {boolean} [onlyOne] - The `onlyOne` parameter is an optional boolean parameter that determines whether to - * return only one node that matches the given `nodeProperty` or all nodes that match the `nodeProperty`. If `onlyOne` - * is set to `true`, the function will return an array with only one node (if - * @returns an array of nodes (type N). - */ - override getNodes(nodeProperty: BinaryTreeNodeId | N, propertyName ?: BinaryTreeNodePropertyName, onlyOne ?: boolean): N[] { - propertyName = propertyName ?? 'id'; - if (!this.root) return []; - const result: N[] = []; + /** + * The function `getNodes` returns an array of nodes in a binary tree that match a given property value. + * @param {BinaryTreeNodeId | N} nodeProperty - The `nodeProperty` parameter can be either a `BinaryTreeNodeId` or an + * `N` type. It represents the property of the binary tree node that you want to compare with. + * @param {BinaryTreeNodePropertyName} [propertyName] - The `propertyName` parameter is an optional parameter that + * specifies the property name to use for comparison. If not provided, it defaults to `'id'`. + * @param {boolean} [onlyOne] - The `onlyOne` parameter is an optional boolean parameter that determines whether to + * return only one node that matches the given `nodeProperty` or all nodes that match the `nodeProperty`. If `onlyOne` + * is set to `true`, the function will return an array with only one node (if + * @returns an array of nodes (type N). + */ + override getNodes(nodeProperty: BinaryTreeNodeId | N, propertyName ?: BinaryTreeNodePropertyName, onlyOne ?: boolean): N[] { + propertyName = propertyName ?? 'id'; + if (!this.root) return []; + const result: N[] = []; - if (this.loopType === LoopType.RECURSIVE) { - const _traverse = (cur: N) => { - if (this._pushByPropertyNameStopOrNot(cur, result, nodeProperty, propertyName, onlyOne)) return; + if (this.loopType === LoopType.RECURSIVE) { + const _traverse = (cur: N) => { + 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); + 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 { - const queue: N[] = [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); - } - } - } + cur.left && _traverse(cur.left); + cur.right && _traverse(cur.right); } + } - return result; + _traverse(this.root); + } else { + const queue: N[] = [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); + } + } + } } - // --- start additional functions - /** - * The `lesserSum` function calculates the sum of property values in a binary tree for nodes that have a property value - * less than a given node. - * @param {N | BinaryTreeNodeId | null} beginNode - The `beginNode` parameter can be one of the following: - * @param {BinaryTreeNodePropertyName} [propertyName] - The `propertyName` parameter is an optional parameter that - * specifies the property name to use for calculating the sum. If not provided, it defaults to `'id'`. - * @returns The function `lesserSum` returns a number, which represents the sum of the values of the nodes in the - * binary tree that have a lesser value than the specified `beginNode` based on the `propertyName`. - */ - lesserSum(beginNode: N | BinaryTreeNodeId | null, propertyName ?: BinaryTreeNodePropertyName): number { - propertyName = propertyName ?? 'id'; - if (typeof beginNode === 'number') beginNode = this.get(beginNode, 'id'); - if (!beginNode) return 0; - if (!this.root) return 0; - const id = beginNode.id; - const getSumByPropertyName = (cur: N) => { - let needSum: number; - switch (propertyName) { - case 'id': - needSum = cur.id; - break; - default: - needSum = cur.id; - break; - } - return needSum; - } + return result; + } - let sum = 0; + // --- start additional functions + /** + * The `lesserSum` function calculates the sum of property values in a binary tree for nodes that have a property value + * less than a given node. + * @param {N | BinaryTreeNodeId | null} beginNode - The `beginNode` parameter can be one of the following: + * @param {BinaryTreeNodePropertyName} [propertyName] - The `propertyName` parameter is an optional parameter that + * specifies the property name to use for calculating the sum. If not provided, it defaults to `'id'`. + * @returns The function `lesserSum` returns a number, which represents the sum of the values of the nodes in the + * binary tree that have a lesser value than the specified `beginNode` based on the `propertyName`. + */ + lesserSum(beginNode: N | BinaryTreeNodeId | null, propertyName ?: BinaryTreeNodePropertyName): number { + propertyName = propertyName ?? 'id'; + if (typeof beginNode === 'number') beginNode = this.get(beginNode, 'id'); + if (!beginNode) return 0; + if (!this.root) return 0; + const id = beginNode.id; + const getSumByPropertyName = (cur: N) => { + let needSum: number; + switch (propertyName) { + case 'id': + needSum = cur.id; + break; + default: + needSum = cur.id; + break; + } + return needSum; + } - if (this.loopType === LoopType.RECURSIVE) { - const _traverse = (cur: N): 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; - } - }; + let sum = 0; - _traverse(this.root); + if (this.loopType === LoopType.RECURSIVE) { + const _traverse = (cur: N): 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 { - const queue: N[] = [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; - } - } - } + if (cur.left) _traverse(cur.left); + else return; } + }; - return sum; + _traverse(this.root); + } else { + const queue: N[] = [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; + } + } + } } - /** - * The `allGreaterNodesAdd` function adds a delta value to the specified property of all nodes in a binary tree that - * have a greater value than a given node. - * @param {N | BinaryTreeNodeId | null} node - The `node` parameter can be either of type `N` (a generic type), - * `BinaryTreeNodeId`, or `null`. It represents the node in the binary tree to which the delta value will be added. - * @param {number} delta - The `delta` parameter is a number that represents the amount by which the property value of - * each greater node should be increased. - * @param {BinaryTreeNodePropertyName} [propertyName] - The `propertyName` parameter is an optional parameter that - * specifies the property name of the nodes in the binary tree that you want to update. If not provided, it defaults to - * 'id'. - * @returns a boolean value. - */ - allGreaterNodesAdd(node: N | BinaryTreeNodeId | null, delta: number, propertyName ?: BinaryTreeNodePropertyName): boolean { - propertyName = propertyName ?? 'id'; - if (typeof node === 'number') node = this.get(node, 'id'); - if (!node) return false; - const id = node.id; - if (!this.root) return false; + return sum; + } - const _sumByPropertyName = (cur: N) => { - switch (propertyName) { - case 'id': - cur.id += delta; - break; - default: - cur.id += delta; - break; - } + /** + * The `allGreaterNodesAdd` function adds a delta value to the specified property of all nodes in a binary tree that + * have a greater value than a given node. + * @param {N | BinaryTreeNodeId | null} node - The `node` parameter can be either of type `N` (a generic type), + * `BinaryTreeNodeId`, or `null`. It represents the node in the binary tree to which the delta value will be added. + * @param {number} delta - The `delta` parameter is a number that represents the amount by which the property value of + * each greater node should be increased. + * @param {BinaryTreeNodePropertyName} [propertyName] - The `propertyName` parameter is an optional parameter that + * specifies the property name of the nodes in the binary tree that you want to update. If not provided, it defaults to + * 'id'. + * @returns a boolean value. + */ + allGreaterNodesAdd(node: N | BinaryTreeNodeId | null, delta: number, propertyName ?: BinaryTreeNodePropertyName): boolean { + propertyName = propertyName ?? 'id'; + if (typeof node === 'number') node = this.get(node, 'id'); + if (!node) return false; + const id = node.id; + if (!this.root) return false; + + const _sumByPropertyName = (cur: N) => { + switch (propertyName) { + case 'id': + cur.id += delta; + break; + default: + cur.id += delta; + break; + } + } + if (this.loopType === LoopType.RECURSIVE) { + const _traverse = (cur: N) => { + const compared = this._compare(cur.id, id); + if (compared === CP.gt) _sumByPropertyName(cur); + + if (!cur.left && !cur.right) return; + if (cur.left && this._compare(cur.left.id, id) === CP.gt) _traverse(cur.left); + if (cur.right && this._compare(cur.right.id, id) === CP.gt) _traverse(cur.right); + }; + + _traverse(this.root); + return true; + } else { + const queue: N[] = [this.root]; + while (queue.length > 0) { + const cur = queue.shift(); + if (cur) { + const compared = this._compare(cur.id, id); + if (compared === CP.gt) _sumByPropertyName(cur); + + if (cur.left && this._compare(cur.left.id, id) === CP.gt) queue.push(cur.left); + if (cur.right && this._compare(cur.right.id, id) === CP.gt) queue.push(cur.right); } - if (this.loopType === LoopType.RECURSIVE) { - const _traverse = (cur: N) => { - const compared = this._compare(cur.id, id); - if (compared === CP.gt) _sumByPropertyName(cur); + } + return true; + } + } - if (!cur.left && !cur.right) return; - if (cur.left && this._compare(cur.left.id, id) === CP.gt) _traverse(cur.left); - if (cur.right && this._compare(cur.right.id, id) === CP.gt) _traverse(cur.right); - }; + /** + * Balancing Adjustment: + * Perfectly Balanced Binary Tree: Since the balance of a perfectly balanced binary tree is already fixed, no additional balancing adjustment is needed. Any insertion or deletion operation will disrupt the perfect balance, often requiring a complete reconstruction of the tree. + * AVL Tree: After insertion or deletion operations, an AVL tree performs rotation adjustments based on the balance factor of nodes to restore the tree's balance. These rotations can be left rotations, right rotations, left-right rotations, or right-left rotations, performed as needed. + * + * Use Cases and Efficiency: + * Perfectly Balanced Binary Tree: Perfectly balanced binary trees are typically used in specific scenarios such as complete binary heaps in heap sort or certain types of Huffman trees. However, they are not suitable for dynamic operations requiring frequent insertions and deletions, as these operations often necessitate full tree reconstruction. + * AVL Tree: AVL trees are well-suited for scenarios involving frequent searching, insertion, and deletion operations. Through rotation adjustments, AVL trees maintain their balance, ensuring average and worst-case time complexity of O(log n). + */ - _traverse(this.root); - return true; + + /** + * The `perfectlyBalance` function takes a binary tree, performs a depth-first search to sort the nodes, and then + * constructs a balanced binary search tree using either a recursive or iterative approach. + * @returns The function `perfectlyBalance()` returns a boolean value. + */ + perfectlyBalance(): 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.add(midNode.id, midNode.val); + 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.add(midNode.id, midNode.val); + stack.push([m + 1, r]); + stack.push([l, m - 1]); + } + } + } + return true; + } + } + + /** + * The function `isAVLBalanced` checks if a binary tree is balanced according to the AVL tree property. + * @returns a boolean value. + */ + isAVLBalanced(): boolean { + if (!this.root) return true; + + let balanced = true; + + if (this.loopType === LoopType.RECURSIVE) { + const _height = (cur: N | 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: N[] = []; + let node: N | null | undefined = this.root, last: N | null = null; + const depths: Map = new Map(); + + while (stack.length > 0 || node) { + if (node) { + stack.push(node); + node = node.left; } else { - const queue: N[] = [this.root]; - while (queue.length > 0) { - const cur = queue.shift(); - if (cur) { - const compared = this._compare(cur.id, id); - if (compared === CP.gt) _sumByPropertyName(cur); - - if (cur.left && this._compare(cur.left.id, id) === CP.gt) queue.push(cur.left); - if (cur.right && this._compare(cur.right.id, id) === CP.gt) queue.push(cur.right); - } + node = stack[stack.length - 1] + if (!node.right || last === node.right) { + node = stack.pop(); + if (node) { + const left = node.left ? depths.get(node.left) ?? -1 : -1; + const 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; } - return true; + } else node = node.right } + } } - /** - * Balancing Adjustment: - * Perfectly Balanced Binary Tree: Since the balance of a perfectly balanced binary tree is already fixed, no additional balancing adjustment is needed. Any insertion or deletion operation will disrupt the perfect balance, often requiring a complete reconstruction of the tree. - * AVL Tree: After insertion or deletion operations, an AVL tree performs rotation adjustments based on the balance factor of nodes to restore the tree's balance. These rotations can be left rotations, right rotations, left-right rotations, or right-left rotations, performed as needed. - * - * Use Cases and Efficiency: - * Perfectly Balanced Binary Tree: Perfectly balanced binary trees are typically used in specific scenarios such as complete binary heaps in heap sort or certain types of Huffman trees. However, they are not suitable for dynamic operations requiring frequent insertions and deletions, as these operations often necessitate full tree reconstruction. - * AVL Tree: AVL trees are well-suited for scenarios involving frequent searching, insertion, and deletion operations. Through rotation adjustments, AVL trees maintain their balance, ensuring average and worst-case time complexity of O(log n). - */ + return balanced; + } + protected _comparator: BSTComparator = (a, b) => a - b; - /** - * The `perfectlyBalance` function takes a binary tree, performs a depth-first search to sort the nodes, and then - * constructs a balanced binary search tree using either a recursive or iterative approach. - * @returns The function `perfectlyBalance()` returns a boolean value. - */ - perfectlyBalance(): boolean { - const sorted = this.DFS('in', 'node'), n = sorted.length; - this.clear(); + /** + * The function compares two binary tree node IDs using a comparator function and returns whether the first ID is + * greater than, less than, or equal to the second ID. + * @param {BinaryTreeNodeId} a - a is a BinaryTreeNodeId, which represents the identifier of a binary tree node. + * @param {BinaryTreeNodeId} b - The parameter "b" in the above code refers to a BinaryTreeNodeId. + * @returns a value of type CP (ComparisonResult). The possible return values are CP.gt (greater than), CP.lt (less + * than), or CP.eq (equal). + */ + 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; + } - 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.add(midNode.id, midNode.val); - 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.add(midNode.id, midNode.val); - stack.push([m + 1, r]); - stack.push([l, m - 1]); - } - } - } - return true; - } - } - - /** - * The function `isAVLBalanced` checks if a binary tree is balanced according to the AVL tree property. - * @returns a boolean value. - */ - isAVLBalanced(): boolean { - if (!this.root) return true; - - let balanced = true; - - if (this.loopType === LoopType.RECURSIVE) { - const _height = (cur: N | 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: N[] = []; - let node: N | null | undefined = this.root, last: N | null = null; - const depths: Map = 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) { - const left = node.left ? depths.get(node.left) ?? -1 : -1; - const 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; - } - - protected _comparator: BSTComparator = (a, b) => a - b; - - /** - * The function compares two binary tree node IDs using a comparator function and returns whether the first ID is - * greater than, less than, or equal to the second ID. - * @param {BinaryTreeNodeId} a - a is a BinaryTreeNodeId, which represents the identifier of a binary tree node. - * @param {BinaryTreeNodeId} b - The parameter "b" in the above code refers to a BinaryTreeNodeId. - * @returns a value of type CP (ComparisonResult). The possible return values are CP.gt (greater than), CP.lt (less - * than), or CP.eq (equal). - */ - 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; - } - - // --- end additional functions -} \ No newline at end of file + // --- end additional functions +} diff --git a/src/data-structures/binary-tree/rb-tree.ts b/src/data-structures/binary-tree/rb-tree.ts index 298171e..9dd25b7 100644 --- a/src/data-structures/binary-tree/rb-tree.ts +++ b/src/data-structures/binary-tree/rb-tree.ts @@ -4,99 +4,99 @@ import {BST, BSTNode} from './bst'; export class RBTreeNode = RBTreeNodeNested> extends BSTNode implements IRBTreeNode { - constructor(id: BinaryTreeNodeId, val?: T, color: RBColor = RBColor.RED) { - super(id, val); - this._color = color; - } + constructor(id: BinaryTreeNodeId, val?: T, color: RBColor = RBColor.RED) { + super(id, val); + this._color = color; + } - private _color: RBColor; + private _color: RBColor; - get color(): RBColor { - return this._color; - } + get color(): RBColor { + return this._color; + } - set color(value: RBColor) { - this._color = value; - } + set color(value: RBColor) { + this._color = value; + } - // private override _parent: RBNode | null; - // override set parent(v: RBNode | null) { - // this._parent = v; - // } - // override get parent(): RBNode | null { - // return this._parent; - // } - // private override _left?: RBNode | null; - // - // override get left(): RBNode | null | undefined { - // return this._left; - // } - // - // override set left(v: RBNode | null | undefined) { - // if (v) { - // v.parent = this; - // } - // this._left = v; - // } - // - // private override _right?: RBNode | null; - // - // override get right(): RBNode | null | undefined { - // return this._right; - // } - // - // override set right(v: RBNode | null | undefined) { - // if (v) { - // v.parent = this; - // } - // this._right = v; - // } + // private override _parent: RBNode | null; + // override set parent(v: RBNode | null) { + // this._parent = v; + // } + // override get parent(): RBNode | null { + // return this._parent; + // } + // private override _left?: RBNode | null; + // + // override get left(): RBNode | null | undefined { + // return this._left; + // } + // + // override set left(v: RBNode | null | undefined) { + // if (v) { + // v.parent = this; + // } + // this._left = v; + // } + // + // private override _right?: RBNode | null; + // + // override get right(): RBNode | null | undefined { + // return this._right; + // } + // + // override set right(v: RBNode | null | undefined) { + // if (v) { + // v.parent = this; + // } + // this._right = v; + // } } export class RBTree = RBTreeNode> extends BST implements IRBTree { - constructor(options?: RBTreeOptions) { - super(options); - } + constructor(options?: RBTreeOptions) { + super(options); + } - override createNode(id: BinaryTreeNodeId, val?: N['val']): N { - return new RBTreeNode(id, val, RBColor.RED) as N; - } + override createNode(id: BinaryTreeNodeId, val?: N['val']): N { + return new RBTreeNode(id, val, RBColor.RED) as N; + } - // private override _root: BinaryTreeNode | null = null; - // - // override get root(): BinaryTreeNode | null { - // return this._root; - // } + // private override _root: BinaryTreeNode | null = null; + // + // override get root(): BinaryTreeNode | null { + // return this._root; + // } - insert(id: number, val?: N | null) { + insert(id: number, val?: N | null) { - } + } - private leftRotate(node: N) { + private leftRotate(node: N) { - } + } - private rightRotate(node: N) { + private rightRotate(node: N) { - } + } - private insertFixup(node: N) { + private insertFixup(node: N) { - } + } - private deleteFixup(node: N) { + private deleteFixup(node: N) { - } + } - private transplant(u: N, v: N) { + private transplant(u: N, v: N) { - } + } - // override remove(id: BinaryTreeNodeId, ignoreCount?: boolean): BinaryTreeDeleted[] { - // - // return [{deleted: new N(0, 0), needBalanced: null}]; - // } + // override remove(id: BinaryTreeNodeId, ignoreCount?: boolean): BinaryTreeDeleted[] { + // + // return [{deleted: new N(0, 0), needBalanced: null}]; + // } } diff --git a/src/data-structures/binary-tree/segment-tree.ts b/src/data-structures/binary-tree/segment-tree.ts index 5d8f5a7..8590afa 100644 --- a/src/data-structures/binary-tree/segment-tree.ts +++ b/src/data-structures/binary-tree/segment-tree.ts @@ -9,235 +9,235 @@ import type {SegmentTreeNodeVal} from '../../types'; export class SegmentTreeNode { - constructor(start: number, end: number, sum: number, val?: SegmentTreeNodeVal | null) { - this._start = start; - this._end = end; - this._sum = sum; - this._val = val || null; - } + constructor(start: number, end: number, sum: number, val?: SegmentTreeNodeVal | null) { + this._start = start; + this._end = end; + this._sum = sum; + this._val = val || null; + } - private _start = 0; - get start(): number { - return this._start; - } + private _start = 0; + get start(): number { + return this._start; + } - set start(v: number) { - this._start = v; - } + set start(v: number) { + this._start = v; + } - private _end = 0; + private _end = 0; - get end(): number { - return this._end; - } + get end(): number { + return this._end; + } - set end(v: number) { - this._end = v; - } + set end(v: number) { + this._end = v; + } - private _val: SegmentTreeNodeVal | null = null; + private _val: SegmentTreeNodeVal | null = null; - get val(): SegmentTreeNodeVal | null { - return this._val; - } + get val(): SegmentTreeNodeVal | null { + return this._val; + } - set val(v: SegmentTreeNodeVal | null) { - this._val = v; - } + set val(v: SegmentTreeNodeVal | null) { + this._val = v; + } - private _sum = 0; + private _sum = 0; - get sum(): number { - return this._sum; - } + get sum(): number { + return this._sum; + } - set sum(v: number) { - this._sum = v; - } + set sum(v: number) { + this._sum = v; + } - private _left: SegmentTreeNode | null = null; + private _left: SegmentTreeNode | null = null; - get left(): SegmentTreeNode | null { - return this._left; - } + get left(): SegmentTreeNode | null { + return this._left; + } - set left(v: SegmentTreeNode | null) { - this._left = v; - } + set left(v: SegmentTreeNode | null) { + this._left = v; + } - private _right: SegmentTreeNode | null = null; + private _right: SegmentTreeNode | null = null; - get right(): SegmentTreeNode | null { - return this._right; - } + get right(): SegmentTreeNode | null { + return this._right; + } - set right(v: SegmentTreeNode | null) { - this._right = v; - } + set right(v: SegmentTreeNode | null) { + this._right = v; + } } export class SegmentTree { - /** - * The constructor initializes the values, start, end, and root properties of an object. - * @param {number[]} values - An array of numbers that will be used to build a binary search tree. - * @param {number} [start] - The `start` parameter is the index of the first element in the `values` array that should - * be included in the range. If no value is provided for `start`, it defaults to 0, which means the range starts from - * the beginning of the array. - * @param {number} [end] - The "end" parameter is the index of the last element in the "values" array that should be - * included in the range. If not provided, it defaults to the index of the last element in the "values" array. - */ - 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); + /** + * The constructor initializes the values, start, end, and root properties of an object. + * @param {number[]} values - An array of numbers that will be used to build a binary search tree. + * @param {number} [start] - The `start` parameter is the index of the first element in the `values` array that should + * be included in the range. If no value is provided for `start`, it defaults to 0, which means the range starts from + * the beginning of the array. + * @param {number} [end] - The "end" parameter is the index of the last element in the "values" array that should be + * included in the range. If not provided, it defaults to the index of the last element in the "values" array. + */ + 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); + } + + private _values: number[] = []; + + get values(): number[] { + return this._values; + } + + private _start = 0; + get start(): number { + return this._start; + } + + private _end: number; + + get end(): number { + return this._end; + } + + private _root: SegmentTreeNode | null; + + get root(): SegmentTreeNode | null { + return this._root; + } + + /** + * The function builds a segment tree by recursively dividing the given range into smaller segments and creating nodes + * for each segment. + * @param {number} start - The `start` parameter represents the starting index of the segment or range for which we are + * building the segment tree. + * @param {number} end - The `end` parameter represents the ending index of the segment or range for which we are + * building the segment tree. + * @returns a SegmentTreeNode object. + */ + 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; + } + + /** + * The function updates the value of a node in a segment tree and recalculates the sum of its children if they exist. + * @param {number} index - The index parameter represents the index of the node in the segment tree that needs to be + * updated. + * @param {number} sum - The `sum` parameter represents the new value that should be assigned to the `sum` property of + * the `SegmentTreeNode` at the specified `index`. + * @param {SegmentTreeNodeVal} [val] - The `val` parameter is an optional value that can be assigned to the `val` + * property of the `SegmentTreeNode` object. It is not currently used in the code, but you can uncomment the line `// + * cur.val = val;` and pass a value for `val` in the + * @returns The function does not return anything. + */ + updateNode(index: number, sum: number, val?: SegmentTreeNodeVal) { + const root = this.root || null; + if (!root) { + return; } - - private _values: number[] = []; - - get values(): number[] { - return this._values; - } - - private _start = 0; - get start(): number { - return this._start; - } - - private _end: number; - - get end(): number { - return this._end; - } - - private _root: SegmentTreeNode | null; - - get root(): SegmentTreeNode | null { - return this._root; - } - - /** - * The function builds a segment tree by recursively dividing the given range into smaller segments and creating nodes - * for each segment. - * @param {number} start - The `start` parameter represents the starting index of the segment or range for which we are - * building the segment tree. - * @param {number} end - The `end` parameter represents the ending index of the segment or range for which we are - * building the segment tree. - * @returns a SegmentTreeNode object. - */ - 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; - } - - /** - * The function updates the value of a node in a segment tree and recalculates the sum of its children if they exist. - * @param {number} index - The index parameter represents the index of the node in the segment tree that needs to be - * updated. - * @param {number} sum - The `sum` parameter represents the new value that should be assigned to the `sum` property of - * the `SegmentTreeNode` at the specified `index`. - * @param {SegmentTreeNodeVal} [val] - The `val` parameter is an optional value that can be assigned to the `val` - * property of the `SegmentTreeNode` object. It is not currently used in the code, but you can uncomment the line `// - * cur.val = val;` and pass a value for `val` in the - * @returns The function does not return anything. - */ - 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); } - 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); - } - - /** - * The function `querySumByRange` calculates the sum of values within a given range in a segment tree. - * @param {number} indexA - The starting index of the range for which you want to calculate the sum. - * @param {number} indexB - The parameter `indexB` represents the ending index of the range for which you want to - * calculate the sum. - * @returns The function `querySumByRange` returns a number. - */ - querySumByRange(indexA: number, indexB: number): number { - const root = this.root || null; - if (!root) { - return 0; + } else { + if (cur.right) { + dfs(cur.right, index, sum, val); } + } + if (cur.left && cur.right) { + cur.sum = cur.left.sum + cur.right.sum; + } + }; - 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); + dfs(root, index, sum); + } - } 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); + /** + * The function `querySumByRange` calculates the sum of values within a given range in a segment tree. + * @param {number} indexA - The starting index of the range for which you want to calculate the sum. + * @param {number} indexB - The parameter `indexB` represents the ending index of the range for which you want to + * calculate the sum. + * @returns The function `querySumByRange` returns a number. + */ + querySumByRange(indexA: number, indexB: number): number { + const root = this.root || null; + if (!root) { + return 0; } - protected _setValues(value: number[]) { - this._values = value; - } + 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); - protected _setStart(value: number) { - this._start = value; - } + } 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); + } - protected _setEnd(value: number) { - this._end = value; - } + protected _setValues(value: number[]) { + this._values = value; + } - protected _setRoot(v: SegmentTreeNode | null) { - this._root = v; - } + protected _setStart(value: number) { + this._start = value; + } + + protected _setEnd(value: number) { + this._end = value; + } + + protected _setRoot(v: SegmentTreeNode | null) { + this._root = v; + } } diff --git a/src/data-structures/binary-tree/tree-multiset.ts b/src/data-structures/binary-tree/tree-multiset.ts index d3ebca9..327ef05 100644 --- a/src/data-structures/binary-tree/tree-multiset.ts +++ b/src/data-structures/binary-tree/tree-multiset.ts @@ -12,30 +12,30 @@ import {AVLTree, AVLTreeNode} from './avl-tree'; export class TreeMultisetNode = TreeMultisetNodeNested> extends AVLTreeNode implements ITreeMultisetNode { - /** - * The constructor function initializes a BinaryTreeNode object with an id, value, and count. - * @param {BinaryTreeNodeId} id - The `id` parameter is of type `BinaryTreeNodeId` and represents the unique identifier - * of the binary tree node. - * @param {T} [val] - The `val` parameter is an optional parameter of type `T`. It represents the value of the binary - * tree node. If no value is provided, it will be `undefined`. - * @param {number} [count=1] - The `count` parameter is a number that represents the number of times a particular value - * occurs in a binary tree node. It has a default value of 1, which means that if no value is provided for the `count` - * parameter when creating a new instance of the `BinaryTreeNode` class, - */ - constructor(id: BinaryTreeNodeId, val?: T, count: number = 1) { - super(id, val); - this._count = count; - } + /** + * The constructor function initializes a BinaryTreeNode object with an id, value, and count. + * @param {BinaryTreeNodeId} id - The `id` parameter is of type `BinaryTreeNodeId` and represents the unique identifier + * of the binary tree node. + * @param {T} [val] - The `val` parameter is an optional parameter of type `T`. It represents the value of the binary + * tree node. If no value is provided, it will be `undefined`. + * @param {number} [count=1] - The `count` parameter is a number that represents the number of times a particular value + * occurs in a binary tree node. It has a default value of 1, which means that if no value is provided for the `count` + * parameter when creating a new instance of the `BinaryTreeNode` class, + */ + constructor(id: BinaryTreeNodeId, val?: T, count: number = 1) { + super(id, val); + this._count = count; + } - private _count: number; + private _count: number; - get count(): number { - return this._count; - } + get count(): number { + return this._count; + } - set count(v: number) { - this._count = v; - } + set count(v: number) { + this._count = v; + } } /** @@ -43,666 +43,666 @@ export class TreeMultisetNode = TreeMultisetNode> extends AVLTree implements ITreeMultiset { - /** - * The constructor function for a TreeMultiset class in TypeScript, which extends another class and sets an option to - * merge duplicated values. - * @param {TreeMultisetOptions} [options] - An optional object that contains additional configuration options for the - * TreeMultiset. - */ - constructor(options?: TreeMultisetOptions) { - super({...options}); + /** + * The constructor function for a TreeMultiset class in TypeScript, which extends another class and sets an option to + * merge duplicated values. + * @param {TreeMultisetOptions} [options] - An optional object that contains additional configuration options for the + * TreeMultiset. + */ + constructor(options?: TreeMultisetOptions) { + super({...options}); + } + + private _count = 0; + + get count(): number { + return this._count; + } + + /** + * The function creates a new BSTNode with the given id, value, and count. + * @param {BinaryTreeNodeId} id - The id parameter is the unique identifier for the binary tree node. It is used to + * distinguish one node from another in the tree. + * @param {N} val - The `val` parameter represents the value that will be stored in the binary search tree node. + * @param {number} [count] - The "count" parameter is an optional parameter of type number. It represents the number of + * occurrences of the value in the binary search tree node. If not provided, the count will default to 1. + * @returns A new instance of the BSTNode class with the specified id, value, and count (if provided). + */ + override createNode(id: BinaryTreeNodeId, val?: N['val'], count?: number): N { + return new TreeMultisetNode(id, val, count) as N; + } + + /** + * The function swaps the location of two nodes in a tree data structure. + * @param {N} srcNode - The source node that we want to swap with the destination node. + * @param {N} destNode - The `destNode` parameter represents the destination node where the values from `srcNode` will + * be swapped with. + * @returns the `destNode` after swapping its values with the `srcNode`. + */ + override swapLocation(srcNode: N, destNode: N): N { + const {id, val, count, height} = destNode; + const tempNode = this.createNode(id, val, count); + if (tempNode) { + tempNode.height = height; + + destNode.id = srcNode.id; + destNode.val = srcNode.val; + destNode.count = srcNode.count; + destNode.height = srcNode.height; + + srcNode.id = tempNode.id; + srcNode.val = tempNode.val; + srcNode.count = tempNode.count; + srcNode.height = tempNode.height; } - private _count = 0; + return destNode; + } - get count(): number { - return this._count; + /** + * The `add` function adds a new node to a binary search tree, maintaining the tree's properties and balancing if + * necessary. + * @param {BinaryTreeNodeId | N} idOrNode - The `idOrNode` parameter can be either a `BinaryTreeNodeId` or a `N` (which + * represents a `BinaryTreeNode`). + * @param [val] - The `val` parameter represents the value to be added to the binary tree node. + * @param {number} [count] - The `count` parameter is an optional parameter that specifies the number of times the + * value should be added to the binary tree. If the `count` parameter is not provided, it defaults to 1. + * @returns The method `add` returns either the inserted node (`N`), `null`, or `undefined`. + */ + override add(idOrNode: BinaryTreeNodeId | N | null, val?: N['val'], count?: number): N | null | undefined { + count = count ?? 1; + let inserted: N | null | undefined = undefined, newNode: N | null; + if (idOrNode instanceof TreeMultisetNode) { + newNode = this.createNode(idOrNode.id, idOrNode.val, idOrNode.count); + } else if (idOrNode === null) { + newNode = null; + } else { + newNode = this.createNode(idOrNode, val, count); } + if (!this.root) { + this._setRoot(newNode); + this._setSize(this.size + 1); + newNode && this._setCount(this.count + newNode.count); + inserted = this.root; + } else { + let cur = this.root; + let traversing = true; + while (traversing) { + if (cur) { + if (newNode) { + if (this._compare(cur.id, newNode.id) === CP.eq) { + cur.val = newNode.val; + cur.count += newNode.count; + this._setCount(this.count + newNode.count); + traversing = false; + inserted = cur; + } else if (this._compare(cur.id, newNode.id) === CP.gt) { + // Traverse left of the node + if (cur.left === undefined) { + //Add to the left of the current node + cur.left = newNode; + this._setSize(this.size + 1); + this._setCount(this.count + newNode.count); - /** - * The function creates a new BSTNode with the given id, value, and count. - * @param {BinaryTreeNodeId} id - The id parameter is the unique identifier for the binary tree node. It is used to - * distinguish one node from another in the tree. - * @param {N} val - The `val` parameter represents the value that will be stored in the binary search tree node. - * @param {number} [count] - The "count" parameter is an optional parameter of type number. It represents the number of - * occurrences of the value in the binary search tree node. If not provided, the count will default to 1. - * @returns A new instance of the BSTNode class with the specified id, value, and count (if provided). - */ - override createNode(id: BinaryTreeNodeId, val?: N['val'], count?: number): N { - return new TreeMultisetNode(id, val, count) as N; + 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, newNode.id) === CP.lt) { + // Traverse right of the node + if (cur.right === undefined) { + //Add to the right of the current node + cur.right = newNode; + this._setSize(this.size + 1); + this._setCount(this.count + newNode.count); + + traversing = false; + inserted = (cur.right); + } else { + //Traverse the left of the current node + if (cur.right) cur = cur.right; + } + } + } else { + // TODO may need to support null inserted + } + } else { + traversing = false; + } + } } + if (inserted) this.balancePath(inserted); + return inserted; + } - /** - * The function swaps the location of two nodes in a tree data structure. - * @param {N} srcNode - The source node that we want to swap with the destination node. - * @param {N} destNode - The `destNode` parameter represents the destination node where the values from `srcNode` will - * be swapped with. - * @returns the `destNode` after swapping its values with the `srcNode`. - */ - override swapLocation(srcNode: N, destNode: N): N { - const {id, val, count, height} = destNode; - const tempNode = this.createNode(id, val, count); - if (tempNode) { - tempNode.height = height; - - destNode.id = srcNode.id; - destNode.val = srcNode.val; - destNode.count = srcNode.count; - destNode.height = srcNode.height; - - srcNode.id = tempNode.id; - srcNode.val = tempNode.val; - srcNode.count = tempNode.count; - srcNode.height = tempNode.height; + /** + * The function adds a new node to a binary tree if there is an available slot on the left or right side of the parent + * node. + * @param {N | null} newNode - The `newNode` parameter represents the node that needs to be added to the tree. It can + * be either a node object (`N`) or `null`. + * @param {N} parent - The `parent` parameter represents the parent node to which the new node will be added as a + * child. + * @returns The method returns either the `parent.left`, `parent.right`, or `undefined`. + */ + override _addTo(newNode: N | null, parent: N): N | null | undefined { + if (parent) { + if (parent.left === undefined) { + parent.left = newNode; + if (newNode !== null) { + this._setSize(this.size + 1); + this._setCount(this.count + newNode.count) } - return destNode; - } + return parent.left; + } else if (parent.right === undefined) { + + parent.right = newNode; + if (newNode !== null) { + this._setSize(this.size + 1); + this._setCount(this.count + newNode.count); + } + return parent.right; + } else { + return; + } + } else { + return; + } + } + + /** + * The `addMany` function adds multiple nodes to a binary tree and returns an array of the inserted nodes. + * @param {BinaryTreeNodeId[] | N[]} idsOrNodes - An array of BinaryTreeNodeId objects or N objects. These objects + * represent the IDs or nodes of the binary tree where the values will be added. + * @param {N['val'][]} [data] - Optional array of values to be associated with each node being added. If provided, the + * length of the `data` array should be equal to the length of the `idsOrNodes` array. + * @returns The function `addMany` returns an array of `N`, `null`, or `undefined` values. + */ + override addMany(idsOrNodes: (BinaryTreeNodeId | N)[], data?: N['val'][]): (N | null | undefined)[] { + // TODO not sure addMany not be run multi times + const inserted: (N | null | undefined)[] = []; + const map: Map = new Map(); + + for (const idOrNode of idsOrNodes) map.set(idOrNode, (map.get(idOrNode) ?? 0) + 1); + + for (let i = 0; i < idsOrNodes.length; i++) { + const idOrNode = idsOrNodes[i]; + if (map.has(idOrNode)) { - /** - * The `add` function adds a new node to a binary search tree, maintaining the tree's properties and balancing if - * necessary. - * @param {BinaryTreeNodeId | N} idOrNode - The `idOrNode` parameter can be either a `BinaryTreeNodeId` or a `N` (which - * represents a `BinaryTreeNode`). - * @param [val] - The `val` parameter represents the value to be added to the binary tree node. - * @param {number} [count] - The `count` parameter is an optional parameter that specifies the number of times the - * value should be added to the binary tree. If the `count` parameter is not provided, it defaults to 1. - * @returns The method `add` returns either the inserted node (`N`), `null`, or `undefined`. - */ - override add(idOrNode: BinaryTreeNodeId | N | null, val?: N['val'], count?: number): N | null | undefined { - count = count ?? 1; - let inserted: N | null | undefined = undefined, newNode: N | null; if (idOrNode instanceof TreeMultisetNode) { - newNode = this.createNode(idOrNode.id, idOrNode.val, idOrNode.count); - } else if (idOrNode === null) { - newNode = null; - } else { - newNode = this.createNode(idOrNode, val, count); + inserted.push(this.add(idOrNode.id, idOrNode.val, idOrNode.count)); + continue; } - if (!this.root) { - this._setRoot(newNode); - this._setSize(this.size + 1); - newNode && this._setCount(this.count + newNode.count); - inserted = this.root; - } else { - let cur = this.root; - let traversing = true; - while (traversing) { - if (cur) { - if (newNode) { - if (this._compare(cur.id, newNode.id) === CP.eq) { - cur.val = newNode.val; - cur.count += newNode.count; - this._setCount(this.count + newNode.count); - traversing = false; - inserted = cur; - } else if (this._compare(cur.id, newNode.id) === CP.gt) { - // Traverse left of the node - if (cur.left === undefined) { - //Add to the left of the current node - cur.left = newNode; - this._setSize(this.size + 1); - this._setCount(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, newNode.id) === CP.lt) { - // Traverse right of the node - if (cur.right === undefined) { - //Add to the right of the current node - cur.right = newNode; - this._setSize(this.size + 1); - this._setCount(this.count + newNode.count); - - traversing = false; - inserted = (cur.right); - } else { - //Traverse the left of the current node - if (cur.right) cur = cur.right; - } - } - } else { - // TODO may need to support null inserted - } - } else { - traversing = false; - } - } + if (idOrNode === null) { + inserted.push(this.add(NaN, null, 0)); + continue; } - if (inserted) this.balancePath(inserted); - return inserted; + + const val = data?.[i], count = map.get(idOrNode); + + inserted.push(this.add(idOrNode, val, count)); + map.delete(idOrNode); + } + } + return inserted; + } - /** - * The function adds a new node to a binary tree if there is an available slot on the left or right side of the parent - * node. - * @param {N | null} newNode - The `newNode` parameter represents the node that needs to be added to the tree. It can - * be either a node object (`N`) or `null`. - * @param {N} parent - The `parent` parameter represents the parent node to which the new node will be added as a - * child. - * @returns The method returns either the `parent.left`, `parent.right`, or `undefined`. - */ - override _addTo(newNode: N | null, parent: N): N | null | undefined { - if (parent) { - if (parent.left === undefined) { - parent.left = newNode; - if (newNode !== null) { - this._setSize(this.size + 1); - this._setCount(this.count + newNode.count) - } + /** + * The `perfectlyBalance` function takes a binary tree, performs a depth-first search to sort the nodes, and then + * constructs a balanced binary search tree using either a recursive or iterative approach. + * @returns The function `perfectlyBalance()` returns a boolean value. + */ + override perfectlyBalance(): boolean { + const sorted = this.DFS('in', 'node'), n = sorted.length; + if (sorted.length < 1) return false; - return parent.left; - } else if (parent.right === undefined) { + this.clear(); - parent.right = newNode; - if (newNode !== null) { - this._setSize(this.size + 1); - this._setCount(this.count + newNode.count); - } - return parent.right; + 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.add(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.add(midNode.id, midNode.val, midNode.count); + stack.push([m + 1, r]); + stack.push([l, m - 1]); + } + } + } + return true; + } + } + + /** + * The `remove` function removes a node from a binary search tree and returns the deleted node along with the parent + * node that needs to be balanced. + * @param {N | BinaryTreeNodeId | null} nodeOrId - The `nodeOrId` parameter can be one of the following: + * @param {boolean} [ignoreCount] - The `ignoreCount` parameter is an optional boolean parameter that determines + * whether to ignore the count of the node being removed. If `ignoreCount` is set to `true`, the count of the node will + * not be taken into account when removing it. If `ignoreCount` is set to `false + * @returns The function `remove` returns an array of `BinaryTreeDeletedResult` objects. + */ + override remove(nodeOrId: N | BinaryTreeNodeId, ignoreCount?: boolean): BinaryTreeDeletedResult[] { + const bstDeletedResult: BinaryTreeDeletedResult[] = []; + if (!this.root) return bstDeletedResult; + + const curr: N | null = this.get(nodeOrId); + if (!curr) return bstDeletedResult; + + const parent: N | null = curr?.parent ? curr.parent : null; + let needBalanced: N | null = null, orgCurrent = curr; + + if (curr.count > 1 && !ignoreCount) { + curr.count--; + this._setCount(this.count - 1); + } else { + if (!curr.left) { + if (!parent) { + if (curr.right !== undefined) this._setRoot(curr.right); + } else { + const {familyPosition: fp} = curr; + if (fp === FamilyPosition.LEFT || fp === FamilyPosition.ROOT_LEFT) { + parent.left = curr.right; + } else if (fp === FamilyPosition.RIGHT || fp === FamilyPosition.ROOT_RIGHT) { + parent.right = curr.right; + } + needBalanced = parent; + } + } else { + const leftSubTreeRightMost = curr.left ? this.getRightMost(curr.left) : null; + if (leftSubTreeRightMost) { + const parentOfLeftSubTreeMax = leftSubTreeRightMost.parent; + orgCurrent = this.swapLocation(curr, leftSubTreeRightMost); + if (parentOfLeftSubTreeMax) { + if (parentOfLeftSubTreeMax.right === leftSubTreeRightMost) { + parentOfLeftSubTreeMax.right = leftSubTreeRightMost.left; } else { - return; + parentOfLeftSubTreeMax.left = leftSubTreeRightMost.left; } + needBalanced = parentOfLeftSubTreeMax; + } + } + } + this._setSize(this.size - 1); + // TODO How to handle when the count of target node is lesser than current node's count + this._setCount(this.count - orgCurrent.count); + } + + bstDeletedResult.push({deleted: orgCurrent, needBalanced}); + + if (needBalanced) { + this.balancePath(needBalanced); + } + + return bstDeletedResult; + } + + /** + * The function `getSubTreeCount` calculates the number of nodes and the sum of their counts in a subtree, using either + * recursive or iterative traversal. + * @param {N | null | undefined} subTreeRoot - The `subTreeRoot` parameter represents the root node of a subtree in a + * binary tree. + * @returns The function `getSubTreeCount` returns an array `[number, number]`. + */ + getSubTreeCount(subTreeRoot: N | null | undefined) { + const res: [number, number] = [0, 0]; + if (!subTreeRoot) return res; + + if (this.loopType === LoopType.RECURSIVE) { + const _traverse = (cur: N) => { + res[0]++; + res[1] += cur.count; + cur.left && _traverse(cur.left); + cur.right && _traverse(cur.right); + } + + _traverse(subTreeRoot); + return res; + } else { + const stack: N[] = [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; + } + } + + /** + * The function `subTreeSumCount` calculates the sum of the `count` property of each node in a subtree, either + * recursively or iteratively. + * @param {N | BinaryTreeNodeId | null} subTreeRoot - The `subTreeRoot` parameter represents the root node of a subtree + * in a binary tree. It can be either a `BinaryTreeNodeId` (a unique identifier for a node in the binary tree) or + * `null` if the subtree is empty. + * @returns the sum of the count values of all nodes in the subtree rooted at `subTreeRoot`. + */ + subTreeSumCount(subTreeRoot: N | BinaryTreeNodeId | null): number { + + if (typeof subTreeRoot === 'number') subTreeRoot = this.get(subTreeRoot, 'id'); + + if (!subTreeRoot) return 0; + + let sum = 0; + + if (this.loopType === LoopType.RECURSIVE) { + const _traverse = (cur: N): void => { + sum += cur.count; + cur.left && _traverse(cur.left); + cur.right && _traverse(cur.right); + } + + _traverse(subTreeRoot); + } else { + const stack: N[] = [subTreeRoot]; + + while (stack.length > 0) { + const cur = stack.pop()!; + sum += cur.count; + cur.right && stack.push(cur.right); + cur.left && stack.push(cur.left); + } + } + + return sum; + } + + /** + * The function `subTreeAddCount` recursively or iteratively traverses a binary tree and adds a given delta value to + * the `count` property of each node. + * @param {N | BinaryTreeNodeId | null} subTreeRoot - The `subTreeRoot` parameter represents the root node of a subtree + * in a binary tree. It can be either a `BinaryTreeNodeId` (a unique identifier for a node in the binary tree), a + * `BinaryTreeNode` object, or `null` if the subtree is empty. + * @param {number} delta - The delta parameter is a number that represents the amount by which the count of each node + * in the subtree should be increased or decreased. + * @returns a boolean value. + */ + subTreeAddCount(subTreeRoot: N | BinaryTreeNodeId | null, delta: number): boolean { + if (typeof subTreeRoot === 'number') subTreeRoot = this.get(subTreeRoot, 'id'); + + if (!subTreeRoot) return false; + + const _addByProperty = (cur: N) => { + cur.count += delta; + this._setCount(this.count + delta); + } + + if (this.loopType === LoopType.RECURSIVE) { + const _traverse = (cur: N) => { + _addByProperty(cur); + cur.left && _traverse(cur.left); + cur.right && _traverse(cur.right); + }; + + _traverse(subTreeRoot); + } else { + const stack: N[] = [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; + } + + /** + * The function `getNodesByCount` returns an array of nodes that have a specific count property, either recursively or + * using a queue. + * @param {BinaryTreeNodeId | N} nodeProperty - The `nodeProperty` parameter can be either a `BinaryTreeNodeId` or a + * `N`. It represents the property of the nodes that you want to search for. + * @param {boolean} [onlyOne] - The `onlyOne` parameter is an optional boolean parameter that determines whether to + * return only one node that matches the `nodeProperty` or all nodes that match the `nodeProperty`. If `onlyOne` is set + * to `true`, the function will return only one node. If `onlyOne` + * @returns an array of nodes that match the given nodeProperty. + */ + getNodesByCount(nodeProperty: BinaryTreeNodeId | N, onlyOne ?: boolean): N[] { + if (!this.root) return []; + const result: N[] = []; + + if (this.loopType === LoopType.RECURSIVE) { + const _traverse = (cur: N) => { + if (cur.count === nodeProperty) { + result.push(cur); + if (onlyOne) return; + } + + if (!cur.left && !cur.right) return; + cur.left && _traverse(cur.left); + cur.right && _traverse(cur.right); + } + + _traverse(this.root); + } else { + const queue: N[] = [this.root]; + while (queue.length > 0) { + const cur = queue.shift(); + if (cur) { + if (cur.count === nodeProperty) { + result.push(cur); + if (onlyOne) return result; + } + + cur.left && queue.push(cur.left); + cur.right && queue.push(cur.right); + + } + } + } + + return result; + } + + /** + * The BFSCount function returns an array of counts from a breadth-first search of nodes. + * @returns The BFSCount() function returns an array of numbers, specifically the count property of each node in the + * BFS traversal. + */ + BFSCount(): number[] { + const nodes = super.BFS('node'); + return nodes.map(node => node.count); + } + + /** + * The function "listLevelsCount" takes a node and returns an array of arrays, where each inner array contains the + * count property of each node at that level. + * @param {N | null} node - The parameter `node` is of type `N | null`. This means that it can either be an instance of + * the class `N` or `null`. + * @returns a 2D array of numbers. Each inner array represents a level in the binary tree, and each number in the inner + * array represents the count property of a node in that level. + */ + listLevelsCount(node: N | null): number[][] { + const levels = super.listLevels(node, 'node'); + return levels.map(level => level.map(node => node.count)); + } + + /** + * The `morrisCount` function returns an array of counts for each node in a binary tree, based on a specified traversal + * pattern. + * @param {'in' | 'pre' | 'post'} [pattern] - The `pattern` parameter is an optional parameter that specifies the + * traversal pattern for the Morris traversal algorithm. It can have one of three values: 'in', 'pre', or 'post'. + * @returns The function `morrisCount` returns an array of numbers. + */ + morrisCount(pattern?: 'in' | 'pre' | 'post'): number[] { + pattern = pattern || 'in'; + const nodes = super.morris(pattern, 'node'); + return nodes.map(node => node.count); + } + + /** + * The function DFSIterativeCount performs a depth-first search iteratively and returns an array of count values for + * each node. + * @param {'in' | 'pre' | 'post'} [pattern] - The "pattern" parameter is a string that specifies the traversal order + * for the Depth-First Search (DFS) algorithm. It can have one of three values: 'in', 'pre', or 'post'. + * @param {NodeOrPropertyName} [nodeOrPropertyName] - The `nodeOrPropertyName` parameter is an optional parameter that + * specifies whether to return the nodes or the property names during the depth-first search traversal. If it is set to + * `'node'`, the function will return the nodes. If it is set to `'property'`, the function will return the property + * @returns The DFSIterativeCount method returns an array of numbers. + */ + DFSIterativeCount(pattern ?: 'in' | 'pre' | 'post', nodeOrPropertyName ?: NodeOrPropertyName): number[] { + pattern = pattern ?? 'in'; + const nodes = super.DFSIterative(pattern, 'node'); + return nodes.map(node => node.count); + } + + /** + * The DFSCount function returns an array of counts for each node in a depth-first search traversal. + * @param {DFSOrderPattern} [pattern] - The `pattern` parameter is an optional parameter that specifies the order in + * which the Depth-First Search (DFS) algorithm should traverse the nodes. It can have one of the following values: + * @param [nodeOrPropertyName] - The parameter `nodeOrPropertyName` is used to specify whether you want to retrieve the + * nodes themselves or a specific property of the nodes. If you pass `'count'` as the value for `nodeOrPropertyName`, + * the function will return an array of the `count` property of each node. + * @returns The DFSCount method returns an array of numbers representing the count property of each node in the DFS + * traversal. + */ + DFSCount(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'count'): number[] { + pattern = pattern ?? 'in'; + const nodes = super.DFS(pattern, 'node'); + return nodes.map(node => node.count); + + } + + /** + * The `lesserSumCount` function calculates the sum of the counts of all nodes in a binary tree that have a lesser + * value than a given node. + * @param {N | BinaryTreeNodeId | null} beginNode - The `beginNode` parameter can be one of the following: + * @returns the sum of the counts of nodes in the binary tree that have a lesser value than the given beginNode. + */ + lesserSumCount(beginNode: N | BinaryTreeNodeId | null): number { + if (typeof beginNode === 'number') beginNode = this.get(beginNode, 'id'); + if (!beginNode) return 0; + if (!this.root) return 0; + const id = beginNode.id; + + let sum = 0; + + if (this.loopType === LoopType.RECURSIVE) { + const _traverse = (cur: N): void => { + const compared = this._compare(cur.id, id); + if (compared === CP.eq) { + if (cur.right) sum += this.subTreeSumCount(cur.right); + return; + } else if (compared === CP.lt) { + if (cur.left) sum += this.subTreeSumCount(cur.left); + sum += cur.count; + if (cur.right) _traverse(cur.right); + else return; } else { - return; + if (cur.left) _traverse(cur.left); + else return; } - } - - /** - * The `addMany` function adds multiple nodes to a binary tree and returns an array of the inserted nodes. - * @param {BinaryTreeNodeId[] | N[]} idsOrNodes - An array of BinaryTreeNodeId objects or N objects. These objects - * represent the IDs or nodes of the binary tree where the values will be added. - * @param {N['val'][]} [data] - Optional array of values to be associated with each node being added. If provided, the - * length of the `data` array should be equal to the length of the `idsOrNodes` array. - * @returns The function `addMany` returns an array of `N`, `null`, or `undefined` values. - */ - override addMany(idsOrNodes: (BinaryTreeNodeId | N)[], data?: N['val'][]): (N | null | undefined)[] { - // TODO not sure addMany not be run multi times - const inserted: (N | null | undefined)[] = []; - const map: Map = new Map(); - - for (const idOrNode of idsOrNodes) map.set(idOrNode, (map.get(idOrNode) ?? 0) + 1); - - for (let i = 0; i < idsOrNodes.length; i++) { - const idOrNode = idsOrNodes[i]; - if (map.has(idOrNode)) { - - if (idOrNode instanceof TreeMultisetNode) { - inserted.push(this.add(idOrNode.id, idOrNode.val, idOrNode.count)); - continue; - } - - if (idOrNode === null) { - inserted.push(this.add(NaN, null, 0)); - continue; - } - - const val = data?.[i], count = map.get(idOrNode); - - inserted.push(this.add(idOrNode, val, count)); - map.delete(idOrNode); - } + }; + _traverse(this.root); + } else { + const queue: N[] = [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.subTreeSumCount(cur.right); + return sum; + } else if (compared === CP.lt) { // todo maybe a bug + if (cur.left) sum += this.subTreeSumCount(cur.left); + sum += cur.count; + if (cur.right) queue.push(cur.right); + else return sum; + } else { + if (cur.left) queue.push(cur.left); + else return sum; + } } - return inserted; + } } - /** - * The `perfectlyBalance` function takes a binary tree, performs a depth-first search to sort the nodes, and then - * constructs a balanced binary search tree using either a recursive or iterative approach. - * @returns The function `perfectlyBalance()` returns a boolean value. - */ - override perfectlyBalance(): boolean { - const sorted = this.DFS('in', 'node'), n = sorted.length; - if (sorted.length < 1) return false; + return sum; + } - this.clear(); + /** + * The function `allGreaterNodesAddCount` updates the count property of all nodes in a binary tree that have an ID + * greater than a given ID by a specified delta value. + * @param {N | BinaryTreeNodeId | null} node - The `node` parameter can be one of the following: + * @param {number} delta - The `delta` parameter is a number that represents the amount by which the `count` property + * of each node should be increased. + * @returns a boolean value. + */ + allGreaterNodesAddCount(node: N | BinaryTreeNodeId | null, delta: number): boolean { + if (typeof node === 'number') node = this.get(node, 'id'); + if (!node) return false; + const id = node.id; + if (!this.root) 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.add(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.add(midNode.id, midNode.val, midNode.count); - stack.push([m + 1, r]); - stack.push([l, m - 1]); - } - } - } - return true; + if (this.loopType === LoopType.RECURSIVE) { + const _traverse = (cur: N) => { + const compared = this._compare(cur.id, id); + if (compared === CP.gt) cur.count += delta; + + if (!cur.left && !cur.right) return; + if (cur.left && this._compare(cur.left.id, id) === CP.gt) _traverse(cur.left); + if (cur.right && this._compare(cur.right.id, id) === CP.gt) _traverse(cur.right); + }; + + _traverse(this.root); + return true; + } else { + const queue: N[] = [this.root]; + while (queue.length > 0) { + const cur = queue.shift(); + if (cur) { + const compared = this._compare(cur.id, id); + if (compared === CP.gt) cur.count += delta + + if (cur.left && this._compare(cur.left.id, id) === CP.gt) queue.push(cur.left); + if (cur.right && this._compare(cur.right.id, id) === CP.gt) queue.push(cur.right); } + } + return true; } + } - /** - * The `remove` function removes a node from a binary search tree and returns the deleted node along with the parent - * node that needs to be balanced. - * @param {N | BinaryTreeNodeId | null} nodeOrId - The `nodeOrId` parameter can be one of the following: - * @param {boolean} [ignoreCount] - The `ignoreCount` parameter is an optional boolean parameter that determines - * whether to ignore the count of the node being removed. If `ignoreCount` is set to `true`, the count of the node will - * not be taken into account when removing it. If `ignoreCount` is set to `false - * @returns The function `remove` returns an array of `BinaryTreeDeletedResult` objects. - */ - override remove(nodeOrId: N | BinaryTreeNodeId, ignoreCount?: boolean): BinaryTreeDeletedResult[] { - const bstDeletedResult: BinaryTreeDeletedResult[] = []; - if (!this.root) return bstDeletedResult; + /** + * The clear() function clears the data and sets the count to 0. + */ + clear() { + super.clear(); + this._setCount(0); + } - const curr: N | null = this.get(nodeOrId); - if (!curr) return bstDeletedResult; + /** + * The function "_setCount" is used to set the value of the "_count" property. + * @param {number} v - number + */ + protected _setCount(v: number) { + this._count = v; + } - const parent: N | null = curr?.parent ? curr.parent : null; - let needBalanced: N | null = null, orgCurrent = curr; - - if (curr.count > 1 && !ignoreCount) { - curr.count--; - this._setCount(this.count - 1); - } else { - if (!curr.left) { - if (!parent) { - if (curr.right !== undefined) this._setRoot(curr.right); - } else { - const {familyPosition: fp} = curr; - if (fp === FamilyPosition.LEFT || fp === FamilyPosition.ROOT_LEFT) { - parent.left = curr.right; - } else if (fp === FamilyPosition.RIGHT || fp === FamilyPosition.ROOT_RIGHT) { - parent.right = curr.right; - } - needBalanced = parent; - } - } else { - const leftSubTreeRightMost = curr.left ? this.getRightMost(curr.left) : null; - if (leftSubTreeRightMost) { - const parentOfLeftSubTreeMax = leftSubTreeRightMost.parent; - orgCurrent = this.swapLocation(curr, leftSubTreeRightMost); - if (parentOfLeftSubTreeMax) { - if (parentOfLeftSubTreeMax.right === leftSubTreeRightMost) { - parentOfLeftSubTreeMax.right = leftSubTreeRightMost.left; - } else { - parentOfLeftSubTreeMax.left = leftSubTreeRightMost.left; - } - needBalanced = parentOfLeftSubTreeMax; - } - } - } - this._setSize(this.size - 1); - // TODO How to handle when the count of target node is lesser than current node's count - this._setCount(this.count - orgCurrent.count); - } - - bstDeletedResult.push({deleted: orgCurrent, needBalanced}); - - if (needBalanced) { - this.balancePath(needBalanced); - } - - return bstDeletedResult; - } - - /** - * The function `getSubTreeCount` calculates the number of nodes and the sum of their counts in a subtree, using either - * recursive or iterative traversal. - * @param {N | null | undefined} subTreeRoot - The `subTreeRoot` parameter represents the root node of a subtree in a - * binary tree. - * @returns The function `getSubTreeCount` returns an array `[number, number]`. - */ - getSubTreeCount(subTreeRoot: N | null | undefined) { - const res: [number, number] = [0, 0]; - if (!subTreeRoot) return res; - - if (this.loopType === LoopType.RECURSIVE) { - const _traverse = (cur: N) => { - res[0]++; - res[1] += cur.count; - cur.left && _traverse(cur.left); - cur.right && _traverse(cur.right); - } - - _traverse(subTreeRoot); - return res; - } else { - const stack: N[] = [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; - } - } - - /** - * The function `subTreeSumCount` calculates the sum of the `count` property of each node in a subtree, either - * recursively or iteratively. - * @param {N | BinaryTreeNodeId | null} subTreeRoot - The `subTreeRoot` parameter represents the root node of a subtree - * in a binary tree. It can be either a `BinaryTreeNodeId` (a unique identifier for a node in the binary tree) or - * `null` if the subtree is empty. - * @returns the sum of the count values of all nodes in the subtree rooted at `subTreeRoot`. - */ - subTreeSumCount(subTreeRoot: N | BinaryTreeNodeId | null): number { - - if (typeof subTreeRoot === 'number') subTreeRoot = this.get(subTreeRoot, 'id'); - - if (!subTreeRoot) return 0; - - let sum = 0; - - if (this.loopType === LoopType.RECURSIVE) { - const _traverse = (cur: N): void => { - sum += cur.count; - cur.left && _traverse(cur.left); - cur.right && _traverse(cur.right); - } - - _traverse(subTreeRoot); - } else { - const stack: N[] = [subTreeRoot]; - - while (stack.length > 0) { - const cur = stack.pop()!; - sum += cur.count; - cur.right && stack.push(cur.right); - cur.left && stack.push(cur.left); - } - } - - return sum; - } - - /** - * The function `subTreeAddCount` recursively or iteratively traverses a binary tree and adds a given delta value to - * the `count` property of each node. - * @param {N | BinaryTreeNodeId | null} subTreeRoot - The `subTreeRoot` parameter represents the root node of a subtree - * in a binary tree. It can be either a `BinaryTreeNodeId` (a unique identifier for a node in the binary tree), a - * `BinaryTreeNode` object, or `null` if the subtree is empty. - * @param {number} delta - The delta parameter is a number that represents the amount by which the count of each node - * in the subtree should be increased or decreased. - * @returns a boolean value. - */ - subTreeAddCount(subTreeRoot: N | BinaryTreeNodeId | null, delta: number): boolean { - if (typeof subTreeRoot === 'number') subTreeRoot = this.get(subTreeRoot, 'id'); - - if (!subTreeRoot) return false; - - const _addByProperty = (cur: N) => { - cur.count += delta; - this._setCount(this.count + delta); - } - - if (this.loopType === LoopType.RECURSIVE) { - const _traverse = (cur: N) => { - _addByProperty(cur); - cur.left && _traverse(cur.left); - cur.right && _traverse(cur.right); - }; - - _traverse(subTreeRoot); - } else { - const stack: N[] = [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; - } - - /** - * The function `getNodesByCount` returns an array of nodes that have a specific count property, either recursively or - * using a queue. - * @param {BinaryTreeNodeId | N} nodeProperty - The `nodeProperty` parameter can be either a `BinaryTreeNodeId` or a - * `N`. It represents the property of the nodes that you want to search for. - * @param {boolean} [onlyOne] - The `onlyOne` parameter is an optional boolean parameter that determines whether to - * return only one node that matches the `nodeProperty` or all nodes that match the `nodeProperty`. If `onlyOne` is set - * to `true`, the function will return only one node. If `onlyOne` - * @returns an array of nodes that match the given nodeProperty. - */ - getNodesByCount(nodeProperty: BinaryTreeNodeId | N, onlyOne ?: boolean): N[] { - if (!this.root) return []; - const result: N[] = []; - - if (this.loopType === LoopType.RECURSIVE) { - const _traverse = (cur: N) => { - if (cur.count === nodeProperty) { - result.push(cur); - if (onlyOne) return; - } - - if (!cur.left && !cur.right) return; - cur.left && _traverse(cur.left); - cur.right && _traverse(cur.right); - } - - _traverse(this.root); - } else { - const queue: N[] = [this.root]; - while (queue.length > 0) { - const cur = queue.shift(); - if (cur) { - if (cur.count === nodeProperty) { - result.push(cur); - if (onlyOne) return result; - } - - cur.left && queue.push(cur.left); - cur.right && queue.push(cur.right); - - } - } - } - - return result; - } - - /** - * The BFSCount function returns an array of counts from a breadth-first search of nodes. - * @returns The BFSCount() function returns an array of numbers, specifically the count property of each node in the - * BFS traversal. - */ - BFSCount(): number[] { - const nodes = super.BFS('node'); - return nodes.map(node => node.count); - } - - /** - * The function "listLevelsCount" takes a node and returns an array of arrays, where each inner array contains the - * count property of each node at that level. - * @param {N | null} node - The parameter `node` is of type `N | null`. This means that it can either be an instance of - * the class `N` or `null`. - * @returns a 2D array of numbers. Each inner array represents a level in the binary tree, and each number in the inner - * array represents the count property of a node in that level. - */ - listLevelsCount(node: N | null): number[][] { - const levels = super.listLevels(node, 'node'); - return levels.map(level => level.map(node => node.count)); - } - - /** - * The `morrisCount` function returns an array of counts for each node in a binary tree, based on a specified traversal - * pattern. - * @param {'in' | 'pre' | 'post'} [pattern] - The `pattern` parameter is an optional parameter that specifies the - * traversal pattern for the Morris traversal algorithm. It can have one of three values: 'in', 'pre', or 'post'. - * @returns The function `morrisCount` returns an array of numbers. - */ - morrisCount(pattern?: 'in' | 'pre' | 'post'): number[] { - pattern = pattern || 'in'; - const nodes = super.morris(pattern, 'node'); - return nodes.map(node => node.count); - } - - /** - * The function DFSIterativeCount performs a depth-first search iteratively and returns an array of count values for - * each node. - * @param {'in' | 'pre' | 'post'} [pattern] - The "pattern" parameter is a string that specifies the traversal order - * for the Depth-First Search (DFS) algorithm. It can have one of three values: 'in', 'pre', or 'post'. - * @param {NodeOrPropertyName} [nodeOrPropertyName] - The `nodeOrPropertyName` parameter is an optional parameter that - * specifies whether to return the nodes or the property names during the depth-first search traversal. If it is set to - * `'node'`, the function will return the nodes. If it is set to `'property'`, the function will return the property - * @returns The DFSIterativeCount method returns an array of numbers. - */ - DFSIterativeCount(pattern ?: 'in' | 'pre' | 'post', nodeOrPropertyName ?: NodeOrPropertyName): number[] { - pattern = pattern ?? 'in'; - const nodes = super.DFSIterative(pattern, 'node'); - return nodes.map(node => node.count); - } - - /** - * The DFSCount function returns an array of counts for each node in a depth-first search traversal. - * @param {DFSOrderPattern} [pattern] - The `pattern` parameter is an optional parameter that specifies the order in - * which the Depth-First Search (DFS) algorithm should traverse the nodes. It can have one of the following values: - * @param [nodeOrPropertyName] - The parameter `nodeOrPropertyName` is used to specify whether you want to retrieve the - * nodes themselves or a specific property of the nodes. If you pass `'count'` as the value for `nodeOrPropertyName`, - * the function will return an array of the `count` property of each node. - * @returns The DFSCount method returns an array of numbers representing the count property of each node in the DFS - * traversal. - */ - DFSCount(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'count'): number[] { - pattern = pattern ?? 'in'; - const nodes = super.DFS(pattern, 'node'); - return nodes.map(node => node.count); - - } - - /** - * The `lesserSumCount` function calculates the sum of the counts of all nodes in a binary tree that have a lesser - * value than a given node. - * @param {N | BinaryTreeNodeId | null} beginNode - The `beginNode` parameter can be one of the following: - * @returns the sum of the counts of nodes in the binary tree that have a lesser value than the given beginNode. - */ - lesserSumCount(beginNode: N | BinaryTreeNodeId | null): number { - if (typeof beginNode === 'number') beginNode = this.get(beginNode, 'id'); - if (!beginNode) return 0; - if (!this.root) return 0; - const id = beginNode.id; - - let sum = 0; - - if (this.loopType === LoopType.RECURSIVE) { - const _traverse = (cur: N): void => { - const compared = this._compare(cur.id, id); - if (compared === CP.eq) { - if (cur.right) sum += this.subTreeSumCount(cur.right); - return; - } else if (compared === CP.lt) { - if (cur.left) sum += this.subTreeSumCount(cur.left); - sum += cur.count; - if (cur.right) _traverse(cur.right); - else return; - } else { - if (cur.left) _traverse(cur.left); - else return; - } - }; - - _traverse(this.root); - } else { - const queue: N[] = [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.subTreeSumCount(cur.right); - return sum; - } else if (compared === CP.lt) { // todo maybe a bug - if (cur.left) sum += this.subTreeSumCount(cur.left); - sum += cur.count; - if (cur.right) queue.push(cur.right); - else return sum; - } else { - if (cur.left) queue.push(cur.left); - else return sum; - } - } - } - } - - return sum; - } - - /** - * The function `allGreaterNodesAddCount` updates the count property of all nodes in a binary tree that have an ID - * greater than a given ID by a specified delta value. - * @param {N | BinaryTreeNodeId | null} node - The `node` parameter can be one of the following: - * @param {number} delta - The `delta` parameter is a number that represents the amount by which the `count` property - * of each node should be increased. - * @returns a boolean value. - */ - allGreaterNodesAddCount(node: N | BinaryTreeNodeId | null, delta: number): boolean { - if (typeof node === 'number') node = this.get(node, 'id'); - if (!node) return false; - const id = node.id; - if (!this.root) return false; - - - if (this.loopType === LoopType.RECURSIVE) { - const _traverse = (cur: N) => { - const compared = this._compare(cur.id, id); - if (compared === CP.gt) cur.count += delta; - - if (!cur.left && !cur.right) return; - if (cur.left && this._compare(cur.left.id, id) === CP.gt) _traverse(cur.left); - if (cur.right && this._compare(cur.right.id, id) === CP.gt) _traverse(cur.right); - }; - - _traverse(this.root); - return true; - } else { - const queue: N[] = [this.root]; - while (queue.length > 0) { - const cur = queue.shift(); - if (cur) { - const compared = this._compare(cur.id, id); - if (compared === CP.gt) cur.count += delta - - if (cur.left && this._compare(cur.left.id, id) === CP.gt) queue.push(cur.left); - if (cur.right && this._compare(cur.right.id, id) === CP.gt) queue.push(cur.right); - } - } - return true; - } - } - - /** - * The clear() function clears the data and sets the count to 0. - */ - clear() { - super.clear(); - this._setCount(0); - } - - /** - * The function "_setCount" is used to set the value of the "_count" property. - * @param {number} v - number - */ - protected _setCount(v: number) { - this._count = v; - } - -} \ No newline at end of file +} diff --git a/src/data-structures/graph/abstract-graph.ts b/src/data-structures/graph/abstract-graph.ts index cc75bb8..b138834 100644 --- a/src/data-structures/graph/abstract-graph.ts +++ b/src/data-structures/graph/abstract-graph.ts @@ -12,1025 +12,1025 @@ import {IAbstractGraph} from '../../interfaces'; export abstract class AbstractVertex { - /** - * The function is a protected constructor that takes an id and an optional value as parameters. - * @param {VertexId} id - The `id` parameter is of type `VertexId` and represents the identifier of the vertex. It is - * used to uniquely identify the vertex object. - * @param {T} [val] - The parameter "val" is an optional parameter of type T. It is used to assign a value to the - * vertex. If no value is provided, it will be set to undefined. - */ - protected constructor(id: VertexId, val?: T) { - this._id = id; - this._val = val; - } + /** + * The function is a protected constructor that takes an id and an optional value as parameters. + * @param {VertexId} id - The `id` parameter is of type `VertexId` and represents the identifier of the vertex. It is + * used to uniquely identify the vertex object. + * @param {T} [val] - The parameter "val" is an optional parameter of type T. It is used to assign a value to the + * vertex. If no value is provided, it will be set to undefined. + */ + protected constructor(id: VertexId, val?: T) { + this._id = id; + this._val = val; + } - private _id: VertexId; + private _id: VertexId; - get id(): VertexId { - return this._id; - } + get id(): VertexId { + return this._id; + } - set id(v: VertexId) { - this._id = v; - } + set id(v: VertexId) { + this._id = v; + } - private _val: T | undefined; + private _val: T | undefined; - get val(): T | undefined { - return this._val; - } + get val(): T | undefined { + return this._val; + } - set val(value: T | undefined) { - this._val = value; - } + set val(value: T | undefined) { + this._val = value; + } } export abstract class AbstractEdge { - /** - * The above function is a protected constructor that initializes the weight, value, and hash code properties of an - * object. - * @param {number} [weight] - The `weight` parameter is an optional number that represents the weight of the object. If - * a value is provided, it will be assigned to the `_weight` property. If no value is provided, the default value of 1 - * will be assigned. - * @param {T} [val] - The `val` parameter is of type `T`, which means it can be any type. It is an optional parameter, - * meaning it can be omitted when creating an instance of the class. - */ - protected constructor(weight?: number, val?: T) { - this._weight = weight !== undefined ? weight : 1; - this._val = val; - this._hashCode = uuidV4(); - } + /** + * The above function is a protected constructor that initializes the weight, value, and hash code properties of an + * object. + * @param {number} [weight] - The `weight` parameter is an optional number that represents the weight of the object. If + * a value is provided, it will be assigned to the `_weight` property. If no value is provided, the default value of 1 + * will be assigned. + * @param {T} [val] - The `val` parameter is of type `T`, which means it can be any type. It is an optional parameter, + * meaning it can be omitted when creating an instance of the class. + */ + protected constructor(weight?: number, val?: T) { + this._weight = weight !== undefined ? weight : 1; + this._val = val; + this._hashCode = uuidV4(); + } - private _val: T | undefined; + private _val: T | undefined; - get val(): T | undefined { - return this._val; - } + get val(): T | undefined { + return this._val; + } - set val(value: T | undefined) { - this._val = value; - } + set val(value: T | undefined) { + this._val = value; + } - private _weight: number; + private _weight: number; - get weight(): number { - return this._weight; - } + get weight(): number { + return this._weight; + } - set weight(v: number) { - this._weight = v; - } + set weight(v: number) { + this._weight = v; + } - protected _hashCode: string; + protected _hashCode: string; - get hashCode(): string { - return this._hashCode; - } + get hashCode(): string { + return this._hashCode; + } - /** - * In TypeScript, a subclass inherits the interface implementation of its parent class, without needing to implement the same interface again in the subclass. This behavior differs from Java's approach. In Java, if a parent class implements an interface, the subclass needs to explicitly implement the same interface, even if the parent class has already implemented it. - * This means that using abstract methods in the parent class cannot constrain the grandchild classes. Defining methods within an interface also cannot constrain the descendant classes. When inheriting from this class, developers need to be aware that this method needs to be overridden. - */ + /** + * In TypeScript, a subclass inherits the interface implementation of its parent class, without needing to implement the same interface again in the subclass. This behavior differs from Java's approach. In Java, if a parent class implements an interface, the subclass needs to explicitly implement the same interface, even if the parent class has already implemented it. + * This means that using abstract methods in the parent class cannot constrain the grandchild classes. Defining methods within an interface also cannot constrain the descendant classes. When inheriting from this class, developers need to be aware that this method needs to be overridden. + */ - /** - * The function sets the value of the _hashCode property to the provided string. - * @param {string} v - The parameter "v" is of type string and represents the value that will be assigned to the - * "_hashCode" property. - */ - protected _setHashCode(v: string) { - this._hashCode = v; - } + /** + * The function sets the value of the _hashCode property to the provided string. + * @param {string} v - The parameter "v" is of type string and represents the value that will be assigned to the + * "_hashCode" property. + */ + protected _setHashCode(v: string) { + this._hashCode = v; + } } export abstract class AbstractGraph = AbstractVertex, E extends AbstractEdge = AbstractEdge> implements IAbstractGraph { - private _vertices: Map = new Map(); + private _vertices: Map = new Map(); - get vertices(): Map { - return this._vertices; + get vertices(): Map { + return this._vertices; + } + + /** + * In TypeScript, a subclass inherits the interface implementation of its parent class, without needing to implement the same interface again in the subclass. This behavior differs from Java's approach. In Java, if a parent class implements an interface, the subclass needs to explicitly implement the same interface, even if the parent class has already implemented it. + * This means that using abstract methods in the parent class cannot constrain the grandchild classes. Defining methods within an interface also cannot constrain the descendant classes. When inheriting from this class, developers need to be aware that this method needs to be overridden. + * @param id + * @param val + */ + abstract createVertex(id: VertexId, val?: V): V; + + /** + * In TypeScript, a subclass inherits the interface implementation of its parent class, without needing to implement the same interface again in the subclass. This behavior differs from Java's approach. In Java, if a parent class implements an interface, the subclass needs to explicitly implement the same interface, even if the parent class has already implemented it. + * This means that using abstract methods in the parent class cannot constrain the grandchild classes. Defining methods within an interface also cannot constrain the descendant classes. When inheriting from this class, developers need to be aware that this method needs to be overridden. + * @param srcOrV1 + * @param destOrV2 + * @param weight + * @param val + */ + abstract createEdge(srcOrV1: VertexId | string, destOrV2: VertexId | string, weight?: number, val?: E): E; + + abstract removeEdge(edge: E): E | null; + + abstract getEdge(srcOrId: V | VertexId, destOrId: V | VertexId): E | null; + + abstract degreeOf(vertexOrId: V | VertexId): number; + + abstract edgeSet(): E[]; + + abstract edgesOf(vertexOrId: V | VertexId): E[]; + + abstract getNeighbors(vertexOrId: V | VertexId): V[]; + + abstract getEndsOfEdge(edge: E): [V, V] | null; + + /** + * The function "getVertex" returns the vertex with the specified ID or null if it doesn't exist. + * @param {VertexId} vertexId - The `vertexId` parameter is the identifier of the vertex that you want to retrieve from + * the `_vertices` map. + * @returns The method `getVertex` returns the vertex with the specified `vertexId` if it exists in the `_vertices` + * map. If the vertex does not exist, it returns `null`. + */ + getVertex(vertexId: VertexId): V | null { + return this._vertices.get(vertexId) || null; + } + + /** + * The function checks if a vertex exists in a graph. + * @param {V | VertexId} vertexOrId - The parameter `vertexOrId` can be either a vertex object (`V`) or a vertex ID + * (`VertexId`). + * @returns a boolean value. + */ + hasVertex(vertexOrId: V | VertexId): boolean { + return this._vertices.has(this._getVertexId(vertexOrId)); + } + + addVertex(vertex: V): boolean + + addVertex(id: VertexId, val?: V['val']): boolean + + addVertex(idOrVertex: VertexId | V, val?: V['val']): boolean { + if (idOrVertex instanceof AbstractVertex) { + return this._addVertexOnly(idOrVertex); + + } else { + const newVertex = this.createVertex(idOrVertex, val); + return this._addVertexOnly(newVertex); + } + } + + /** + * The `removeVertex` function removes a vertex from a graph by its ID or by the vertex object itself. + * @param {V | VertexId} vertexOrId - The parameter `vertexOrId` can be either a vertex object (`V`) or a vertex ID + * (`VertexId`). + * @returns The method is returning a boolean value. + */ + removeVertex(vertexOrId: V | VertexId): boolean { + const vertexId = this._getVertexId(vertexOrId); + return this._vertices.delete(vertexId); + } + + /** + * The function removes all vertices from a graph and returns a boolean indicating if any vertices were removed. + * @param {V[] | VertexId[]} vertices - The `vertices` parameter can be either an array of vertices (`V[]`) or an array + * of vertex IDs (`VertexId[]`). + * @returns a boolean value. It returns true if at least one vertex was successfully removed, and false if no vertices + * were removed. + */ + removeAllVertices(vertices: V[] | VertexId[]): boolean { + const removed: boolean[] = []; + for (const v of vertices) { + removed.push(this.removeVertex(v)); + } + return removed.length > 0; + } + + /** + * The function checks if there is an edge between two vertices and returns a boolean value indicating the result. + * @param {VertexId | V} v1 - The parameter v1 can be either a VertexId or a V. A VertexId represents the unique + * identifier of a vertex in a graph, while V represents the type of the vertex object itself. + * @param {VertexId | V} v2 - The parameter `v2` represents the second vertex in the edge. It can be either a + * `VertexId` or a `V` type, which represents the type of the vertex. + * @returns A boolean value is being returned. + */ + hasEdge(v1: VertexId | V, v2: VertexId | V): boolean { + const edge = this.getEdge(v1, v2); + return !!edge; + } + + addEdge(edge: E): boolean + + addEdge(src: V | VertexId, dest: V | VertexId, weight?: number, val?: E['val']): boolean + + addEdge(srcOrEdge: V | VertexId | E, dest?: V | VertexId, weight?: number, val?: E['val']): boolean { + if (srcOrEdge instanceof AbstractEdge) { + return this._addEdgeOnly(srcOrEdge); + } else { + if (dest instanceof AbstractVertex || typeof dest === 'string' || typeof dest === 'number') { + if (!(this.hasVertex(srcOrEdge) && this.hasVertex(dest))) return false; + if (srcOrEdge instanceof AbstractVertex) srcOrEdge = srcOrEdge.id; + if (dest instanceof AbstractVertex) dest = dest.id; + const newEdge = this.createEdge(srcOrEdge, dest, weight, val); + return this._addEdgeOnly(newEdge); + } else { + throw new Error('dest must be a Vertex or vertex id while srcOrEdge is an Edge') + } + } + } + + /** + * The function sets the weight of an edge between two vertices in a graph. + * @param {VertexId | V} srcOrId - The `srcOrId` parameter can be either a `VertexId` or a `V` object. It represents + * the source vertex of the edge. + * @param {VertexId | V} destOrId - The `destOrId` parameter represents the destination vertex of the edge. It can be + * either a `VertexId` or a vertex object `V`. + * @param {number} weight - The weight parameter represents the weight of the edge between the source vertex (srcOrId) + * and the destination vertex (destOrId). + * @returns a boolean value. If the edge exists between the source and destination vertices, the function will update + * the weight of the edge and return true. If the edge does not exist, the function will return false. + */ + 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; + } + } + + /** + * The function `getAllPathsBetween` finds all paths between two vertices in a graph using depth-first search. + * @param {V | VertexId} v1 - The parameter `v1` represents either a vertex object (`V`) or a vertex ID (`VertexId`). + * It is the starting vertex for finding paths. + * @param {V | VertexId} v2 - The parameter `v2` represents either a vertex object (`V`) or a vertex ID (`VertexId`). + * @returns The function `getAllPathsBetween` returns an array of arrays of vertices (`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 []; } - /** - * In TypeScript, a subclass inherits the interface implementation of its parent class, without needing to implement the same interface again in the subclass. This behavior differs from Java's approach. In Java, if a parent class implements an interface, the subclass needs to explicitly implement the same interface, even if the parent class has already implemented it. - * This means that using abstract methods in the parent class cannot constrain the grandchild classes. Defining methods within an interface also cannot constrain the descendant classes. When inheriting from this class, developers need to be aware that this method needs to be overridden. - * @param id - * @param val - */ - abstract createVertex(id: VertexId, val?: V): V; + const dfs = (cur: V, dest: V, visiting: Map, path: V[]) => { + visiting.set(cur, true); - /** - * In TypeScript, a subclass inherits the interface implementation of its parent class, without needing to implement the same interface again in the subclass. This behavior differs from Java's approach. In Java, if a parent class implements an interface, the subclass needs to explicitly implement the same interface, even if the parent class has already implemented it. - * This means that using abstract methods in the parent class cannot constrain the grandchild classes. Defining methods within an interface also cannot constrain the descendant classes. When inheriting from this class, developers need to be aware that this method needs to be overridden. - * @param srcOrV1 - * @param destOrV2 - * @param weight - * @param val - */ - abstract createEdge(srcOrV1: VertexId | string, destOrV2: VertexId | string, weight?: number, val?: E): E; + if (cur === dest) { + paths.push([vertex1, ...path]); + } - abstract removeEdge(edge: E): E | null; - - abstract getEdge(srcOrId: V | VertexId, destOrId: V | VertexId): E | null; - - abstract degreeOf(vertexOrId: V | VertexId): number; - - abstract edgeSet(): E[]; - - abstract edgesOf(vertexOrId: V | VertexId): E[]; - - abstract getNeighbors(vertexOrId: V | VertexId): V[]; - - abstract getEndsOfEdge(edge: E): [V, V] | null; - - /** - * The function "getVertex" returns the vertex with the specified ID or null if it doesn't exist. - * @param {VertexId} vertexId - The `vertexId` parameter is the identifier of the vertex that you want to retrieve from - * the `_vertices` map. - * @returns The method `getVertex` returns the vertex with the specified `vertexId` if it exists in the `_vertices` - * map. If the vertex does not exist, it returns `null`. - */ - getVertex(vertexId: VertexId): V | null { - return this._vertices.get(vertexId) || null; - } - - /** - * The function checks if a vertex exists in a graph. - * @param {V | VertexId} vertexOrId - The parameter `vertexOrId` can be either a vertex object (`V`) or a vertex ID - * (`VertexId`). - * @returns a boolean value. - */ - hasVertex(vertexOrId: V | VertexId): boolean { - return this._vertices.has(this._getVertexId(vertexOrId)); - } - - addVertex(vertex: V): boolean - - addVertex(id: VertexId, val?: V['val']): boolean - - addVertex(idOrVertex: VertexId | V, val?: V['val']): boolean { - if (idOrVertex instanceof AbstractVertex) { - return this._addVertexOnly(idOrVertex); - - } else { - const newVertex = this.createVertex(idOrVertex, val); - return this._addVertexOnly(newVertex); + 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: V) => vertex === neighbor); } + } + + visiting.set(cur, false); + }; + + dfs(vertex1, vertex2, new Map(), []); + return paths; + } + + /** + * The function calculates the sum of weights along a given path. + * @param {V[]} path - An array of vertices (V) representing a path in a graph. + * @returns The function `getPathSumWeight` returns the sum of the weights of the edges in the given path. + */ + 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; + } - /** - * The `removeVertex` function removes a vertex from a graph by its ID or by the vertex object itself. - * @param {V | VertexId} vertexOrId - The parameter `vertexOrId` can be either a vertex object (`V`) or a vertex ID - * (`VertexId`). - * @returns The method is returning a boolean value. - */ - removeVertex(vertexOrId: V | VertexId): boolean { - const vertexId = this._getVertexId(vertexOrId); - return this._vertices.delete(vertexId); - } + /** + * The function `getMinCostBetween` calculates the minimum cost between two vertices in a graph, either based on edge + * weights or using a breadth-first search algorithm. + * @param {V | VertexId} v1 - The parameter `v1` represents the starting vertex or its ID. + * @param {V | VertexId} v2 - The parameter `v2` represents the destination vertex or its ID. It is the vertex to which + * you want to find the minimum cost or weight from the source vertex `v1`. + * @param {boolean} [isWeight] - isWeight is an optional parameter that indicates whether the graph edges have weights. + * If isWeight is set to true, the function will calculate the minimum cost between v1 and v2 based on the weights of + * the edges. If isWeight is set to false or not provided, the function will calculate the + * @returns The function `getMinCostBetween` returns a number representing the minimum cost between two vertices (`v1` + * and `v2`). If the `isWeight` parameter is `true`, it calculates the minimum weight among all paths between the + * vertices. If `isWeight` is `false` or not provided, it uses a breadth-first search (BFS) algorithm to calculate the + * minimum number of + */ + getMinCostBetween(v1: V | VertexId, v2: V | VertexId, isWeight?: boolean): number | null { + if (isWeight === undefined) isWeight = false; - /** - * The function removes all vertices from a graph and returns a boolean indicating if any vertices were removed. - * @param {V[] | VertexId[]} vertices - The `vertices` parameter can be either an array of vertices (`V[]`) or an array - * of vertex IDs (`VertexId[]`). - * @returns a boolean value. It returns true if at least one vertex was successfully removed, and false if no vertices - * were removed. - */ - removeAllVertices(vertices: V[] | VertexId[]): boolean { - const removed: boolean[] = []; - for (const v of vertices) { - removed.push(this.removeVertex(v)); - } - return removed.length > 0; - } - - /** - * The function checks if there is an edge between two vertices and returns a boolean value indicating the result. - * @param {VertexId | V} v1 - The parameter v1 can be either a VertexId or a V. A VertexId represents the unique - * identifier of a vertex in a graph, while V represents the type of the vertex object itself. - * @param {VertexId | V} v2 - The parameter `v2` represents the second vertex in the edge. It can be either a - * `VertexId` or a `V` type, which represents the type of the vertex. - * @returns A boolean value is being returned. - */ - hasEdge(v1: VertexId | V, v2: VertexId | V): boolean { - const edge = this.getEdge(v1, v2); - return !!edge; - } - - addEdge(edge: E): boolean - - addEdge(src: V | VertexId, dest: V | VertexId, weight?: number, val?: E['val']): boolean - - addEdge(srcOrEdge: V | VertexId | E, dest?: V | VertexId, weight?: number, val?: E['val']): boolean { - if (srcOrEdge instanceof AbstractEdge) { - return this._addEdgeOnly(srcOrEdge); - } else { - if (dest instanceof AbstractVertex || typeof dest === 'string' || typeof dest === 'number') { - if (!(this.hasVertex(srcOrEdge) && this.hasVertex(dest))) return false; - if (srcOrEdge instanceof AbstractVertex) srcOrEdge = srcOrEdge.id; - if (dest instanceof AbstractVertex) dest = dest.id; - const newEdge = this.createEdge(srcOrEdge, dest, weight, val); - return this._addEdgeOnly(newEdge); - } else { - throw new Error('dest must be a Vertex or vertex id while srcOrEdge is an Edge') - } - } - } - - /** - * The function sets the weight of an edge between two vertices in a graph. - * @param {VertexId | V} srcOrId - The `srcOrId` parameter can be either a `VertexId` or a `V` object. It represents - * the source vertex of the edge. - * @param {VertexId | V} destOrId - The `destOrId` parameter represents the destination vertex of the edge. It can be - * either a `VertexId` or a vertex object `V`. - * @param {number} weight - The weight parameter represents the weight of the edge between the source vertex (srcOrId) - * and the destination vertex (destOrId). - * @returns a boolean value. If the edge exists between the source and destination vertices, the function will update - * the weight of the edge and return true. If the edge does not exist, the function will return false. - */ - 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; - } - } - - /** - * The function `getAllPathsBetween` finds all paths between two vertices in a graph using depth-first search. - * @param {V | VertexId} v1 - The parameter `v1` represents either a vertex object (`V`) or a vertex ID (`VertexId`). - * It is the starting vertex for finding paths. - * @param {V | VertexId} v2 - The parameter `v2` represents either a vertex object (`V`) or a vertex ID (`VertexId`). - * @returns The function `getAllPathsBetween` returns an array of arrays of vertices (`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]); - } + 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 (!visiting.get(neighbor)) { - path.push(neighbor); - dfs(neighbor, dest, visiting, path); - arrayRemove(path, (vertex: V) => vertex === neighbor); - } + if (!visited.has(neighbor)) { + visited.set(neighbor, true); + queue.push(neighbor); + } } - - visiting.set(cur, false); - }; - - dfs(vertex1, vertex2, new Map(), []); - return paths; - } - - /** - * The function calculates the sum of weights along a given path. - * @param {V[]} path - An array of vertices (V) representing a path in a graph. - * @returns The function `getPathSumWeight` returns the sum of the weights of the edges in the given path. - */ - 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; + cost++; + } + return null; + } + } + + /** + * The function `getMinPathBetween` returns the minimum path between two vertices in a graph, either based on weight or + * using a breadth-first search algorithm. + * @param {V | VertexId} v1 - The parameter `v1` represents the starting vertex of the path. It can be either a vertex + * object (`V`) or a vertex ID (`VertexId`). + * @param {V | VertexId} v2 - V | VertexId - The second vertex or vertex ID between which we want to find the minimum + * path. + * @param {boolean} [isWeight] - A boolean flag indicating whether to consider the weight of edges in finding the + * minimum path. If set to true, the function will use Dijkstra's algorithm to find the minimum weighted path. If set + * to false, the function will use breadth-first search (BFS) to find the minimum path. + * @returns The function `getMinPathBetween` returns an array of vertices (`V[]`) representing the minimum path between + * two vertices (`v1` and `v2`). If there is no path between the vertices, it returns `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: V) => vertex === neighbor); + } + } + + visiting.set(cur, false); + }; + + dfs(vertex1, vertex2, new Map(), []); + return minPath; + } + } + + /** + * Dijkstra algorithm time: O(VE) space: O(V + E) + * / + + /** + * Dijkstra algorithm time: O(VE) space: O(V + E) + * The function `dijkstraWithoutHeap` implements Dijkstra's algorithm to find the shortest path between two vertices in + * a graph without using a heap data structure. + * @param {V | VertexId} src - The source vertex from which to start the Dijkstra's algorithm. It can be either a + * vertex object or a vertex ID. + * @param {V | VertexId | null} [dest] - The `dest` parameter in the `dijkstraWithoutHeap` function is an optional + * parameter that specifies the destination vertex for the Dijkstra algorithm. It can be either a vertex object or its + * identifier. If no destination is provided, the value is set to `null`. + * @param {boolean} [getMinDist] - The `getMinDist` parameter is a boolean flag that determines whether the minimum + * distance from the source vertex to the destination vertex should be calculated and returned in the result. If + * `getMinDist` is set to `true`, the `minDist` property in the result will contain the minimum distance + * @param {boolean} [genPaths] - The `genPaths` parameter is a boolean flag that determines whether or not to generate + * paths in the Dijkstra algorithm. If `genPaths` is set to `true`, the algorithm will calculate and return the + * shortest paths from the source vertex to all other vertices in the graph. If `genPaths + * @returns The function `dijkstraWithoutHeap` returns an object of type `DijkstraResult`. + */ + 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; } - /** - * The function `getMinCostBetween` calculates the minimum cost between two vertices in a graph, either based on edge - * weights or using a breadth-first search algorithm. - * @param {V | VertexId} v1 - The parameter `v1` represents the starting vertex or its ID. - * @param {V | VertexId} v2 - The parameter `v2` represents the destination vertex or its ID. It is the vertex to which - * you want to find the minimum cost or weight from the source vertex `v1`. - * @param {boolean} [isWeight] - isWeight is an optional parameter that indicates whether the graph edges have weights. - * If isWeight is set to true, the function will calculate the minimum cost between v1 and v2 based on the weights of - * the edges. If isWeight is set to false or not provided, the function will calculate the - * @returns The function `getMinCostBetween` returns a number representing the minimum cost between two vertices (`v1` - * and `v2`). If the `isWeight` parameter is `true`, it calculates the minimum weight among all paths between the - * vertices. If `isWeight` is `false` or not provided, it uses a breadth-first search (BFS) algorithm to calculate the - * minimum number of - */ - getMinCostBetween(v1: V | VertexId, v2: V | VertexId, isWeight?: boolean): number | null { - if (isWeight === undefined) isWeight = false; + for (const vertex of vertices) { + const vertexOrId = vertex[1]; + if (vertexOrId instanceof AbstractVertex) distMap.set(vertexOrId, 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 vertexOrId = vertex[1]; + + if (vertexOrId instanceof AbstractVertex) { + const path: V[] = [vertexOrId]; + let parent = preMap.get(vertexOrId); + 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); + } + } - 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; + } + } + } + } + + 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) + * + * Dijkstra's algorithm only solves the single-source shortest path problem, while the Bellman-Ford algorithm and Floyd-Warshall algorithm can address shortest paths between all pairs of nodes. + * Dijkstra's algorithm is suitable for graphs with non-negative edge weights, whereas the Bellman-Ford algorithm and Floyd-Warshall algorithm can handle negative-weight edges. + * The time complexity of Dijkstra's algorithm and the Bellman-Ford algorithm depends on the size of the graph, while the time complexity of the Floyd-Warshall algorithm is O(V^3), where V is the number of nodes. For dense graphs, Floyd-Warshall might become slower. + * + * / + + /** + * Dijkstra's algorithm is used to find the shortest paths from a source node to all other nodes in a graph. Its basic idea is to repeatedly choose the node closest to the source node and update the distances of other nodes using this node as an intermediary. Dijkstra's algorithm requires that the edge weights in the graph are non-negative. + * The `dijkstra` function implements Dijkstra's algorithm to find the shortest path between a source vertex and an + * optional destination vertex, and optionally returns the minimum distance, the paths, and other information. + * @param {V | VertexId} src - The `src` parameter represents the source vertex from which the Dijkstra algorithm will + * start. It can be either a vertex object or a vertex ID. + * @param {V | VertexId | null} [dest] - The `dest` parameter is the destination vertex or vertex ID. It specifies the + * vertex to which the shortest path is calculated from the source vertex. If no destination is provided, the algorithm + * will calculate the shortest paths to all other vertices from the source vertex. + * @param {boolean} [getMinDist] - The `getMinDist` parameter is a boolean flag that determines whether the minimum + * distance from the source vertex to the destination vertex should be calculated and returned in the result. If + * `getMinDist` is set to `true`, the `minDist` property in the result will contain the minimum distance + * @param {boolean} [genPaths] - The `genPaths` parameter is a boolean flag that determines whether or not to generate + * paths in the Dijkstra algorithm. If `genPaths` is set to `true`, the algorithm will calculate and return the + * shortest paths from the source vertex to all other vertices in the graph. If `genPaths + * @returns The function `dijkstra` returns an object of type `DijkstraResult`. + */ + 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) { + const vertexOrId = vertex[1]; + if (vertexOrId instanceof AbstractVertex) distMap.set(vertexOrId, Infinity); + } + + const heap = new PriorityQueue<{ id: number, val: V }>({comparator: (a, b) => a.id - b.id}); + heap.add({id: 0, val: srcVertex}); + + distMap.set(srcVertex, 0); + preMap.set(srcVertex, null); + + /** + * The function `getPaths` retrieves all paths from vertices to a specified minimum vertex. + * @param {V | null} minV - The parameter `minV` is of type `V | null`. It represents the minimum vertex value or + * null. + */ + const getPaths = (minV: V | null) => { + for (const vertex of vertices) { + const vertexOrId = vertex[1]; + if (vertexOrId instanceof AbstractVertex) { + const path: V[] = [vertexOrId]; + let parent = preMap.get(vertexOrId); + 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.add({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}; + } + + /** + * BellmanFord time:O(VE) space:O(V) + * one to rest pairs + * / + + /** + * BellmanFord time:O(VE) space:O(V) + * one to rest pairs + * The Bellman-Ford algorithm is also used to find the shortest paths from a source node to all other nodes in a graph. Unlike Dijkstra's algorithm, it can handle edge weights that are negative. Its basic idea involves iterative relaxation of all edges for several rounds to gradually approximate the shortest paths. Due to its ability to handle negative-weight edges, the Bellman-Ford algorithm is more flexible in some scenarios. + * The `bellmanFord` function implements the Bellman-Ford algorithm to find the shortest path from a source vertex to + * all other vertices in a graph, and optionally detects negative cycles and generates the minimum path. + * @param {V | VertexId} src - The `src` parameter is the source vertex from which the Bellman-Ford algorithm will + * start calculating the shortest paths. It can be either a vertex object or a vertex ID. + * @param {boolean} [scanNegativeCycle] - A boolean flag indicating whether to scan for negative cycles in the graph. + * @param {boolean} [getMin] - The `getMin` parameter is a boolean flag that determines whether the algorithm should + * calculate the minimum distance from the source vertex to all other vertices in the graph. If `getMin` is set to + * `true`, the algorithm will find the minimum distance and update the `min` variable with the minimum + * @param {boolean} [genPath] - A boolean flag indicating whether to generate paths for all vertices from the source + * vertex. + * @returns The function `bellmanFord` returns an object with the following properties: + */ + 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; + 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 vertexOrId = vertex[1]; + if (vertexOrId instanceof AbstractVertex) { + const path: V[] = [vertexOrId]; + let parent = preMap.get(vertexOrId); + 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}; + } + + /** + * Dijkstra algorithm time: O(logVE) space: O(V + E) + * / + + /** + * Dijkstra algorithm time: O(logVE) space: O(V + E) + * Dijkstra's algorithm is used to find the shortest paths from a source node to all other nodes in a graph. Its basic idea is to repeatedly choose the node closest to the source node and update the distances of other nodes using this node as an intermediary. Dijkstra's algorithm requires that the edge weights in the graph are non-negative. + */ + + + /** + * BellmanFord time:O(VE) space:O(V) + * one to rest pairs + * The Bellman-Ford algorithm is also used to find the shortest paths from a source node to all other nodes in a graph. Unlike Dijkstra's algorithm, it can handle edge weights that are negative. Its basic idea involves iterative relaxation of all edges for several rounds to gradually approximate the shortest paths. Due to its ability to handle negative-weight edges, the Bellman-Ford algorithm is more flexible in some scenarios. + * The `bellmanFord` function implements the Bellman-Ford algorithm to find the shortest path from a source vertex to + */ + + /** + * Floyd algorithm time: O(V^3) space: O(V^2), not support graph with negative weight cycle + * all pairs + * The Floyd-Warshall algorithm is used to find the shortest paths between all pairs of nodes in a graph. It employs dynamic programming to compute the shortest paths from any node to any other node. The Floyd-Warshall algorithm's advantage lies in its ability to handle graphs with negative-weight edges, and it can simultaneously compute shortest paths between any two nodes. + */ + + /** + * Floyd algorithm time: O(V^3) space: O(V^2), not support graph with negative weight cycle + * all pairs + * / + + /** + * Floyd algorithm time: O(V^3) space: O(V^2), not support graph with negative weight cycle + * all pairs + * The Floyd-Warshall algorithm is used to find the shortest paths between all pairs of nodes in a graph. It employs dynamic programming to compute the shortest paths from any node to any other node. The Floyd-Warshall algorithm's advantage lies in its ability to handle graphs with negative-weight edges, and it can simultaneously compute shortest paths between any two nodes. + * The function implements the Floyd-Warshall algorithm to find the shortest path between all pairs of vertices in a + * graph. + * @returns The function `floyd()` returns an object with two properties: `costs` and `predecessor`. The `costs` + * property is a 2D array of numbers representing the shortest path costs between vertices in a graph. The + * `predecessor` property is a 2D array of vertices (or `null`) representing the predecessor vertices in the shortest + * path between vertices in the + */ + 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}; + + } + + /** + * 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 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. + * The `tarjan` function is used to perform various graph analysis tasks such as finding articulation points, bridges, + * strongly connected components (SCCs), and cycles in a graph. + * @param {boolean} [needArticulationPoints] - A boolean value indicating whether or not to calculate and return the + * articulation points in the graph. Articulation points are the vertices in a graph whose removal would increase the + * number of connected components in the graph. + * @param {boolean} [needBridges] - A boolean flag indicating whether the algorithm should find and return the bridges + * (edges whose removal would increase the number of connected components in the graph). + * @param {boolean} [needSCCs] - A boolean value indicating whether the Strongly Connected Components (SCCs) of the + * graph are needed. If set to true, the function will calculate and return the SCCs of the graph. If set to false, the + * SCCs will not be calculated or returned. + * @param {boolean} [needCycles] - A boolean flag indicating whether the algorithm should find cycles in the graph. If + * set to true, the algorithm will return a map of cycles, where the keys are the low values of the SCCs and the values + * are arrays of vertices that form cycles within the SCCs. + * @returns The function `tarjan` returns an object with the following properties: + */ + 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 { - // 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; + SCCs.get(low)?.push(vertex); } + }); + return SCCs; + }; + + if (needSCCs) { + SCCs = getSCCs(); } - /** - * The function `getMinPathBetween` returns the minimum path between two vertices in a graph, either based on weight or - * using a breadth-first search algorithm. - * @param {V | VertexId} v1 - The parameter `v1` represents the starting vertex of the path. It can be either a vertex - * object (`V`) or a vertex ID (`VertexId`). - * @param {V | VertexId} v2 - V | VertexId - The second vertex or vertex ID between which we want to find the minimum - * path. - * @param {boolean} [isWeight] - A boolean flag indicating whether to consider the weight of edges in finding the - * minimum path. If set to true, the function will use Dijkstra's algorithm to find the minimum weighted path. If set - * to false, the function will use breadth-first search (BFS) to find the minimum path. - * @returns The function `getMinPathBetween` returns an array of vertices (`V[]`) representing the minimum path between - * two vertices (`v1` and `v2`). If there is no path between the vertices, it returns `null`. - */ - getMinPathBetween(v1: V | VertexId, v2: V | VertexId, isWeight?: boolean): V[] | null { - if (isWeight === undefined) isWeight = false; + const cycles: Map = new Map(); + if (needCycles) { + let SCCs: Map = new Map(); + if (SCCs.size < 1) { + SCCs = getSCCs(); + } - 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: V) => vertex === neighbor); - } - } - - visiting.set(cur, false); - }; - - dfs(vertex1, vertex2, new Map(), []); - return minPath; + SCCs.forEach((SCC, low) => { + if (SCC.length > 1) { + cycles.set(low, SCC); } + }); } - /** - * Dijkstra algorithm time: O(VE) space: O(V + E) - * / + return {dfnMap, lowMap, bridges, articulationPoints, SCCs, cycles}; + } - /** - * Dijkstra algorithm time: O(VE) space: O(V + E) - * The function `dijkstraWithoutHeap` implements Dijkstra's algorithm to find the shortest path between two vertices in - * a graph without using a heap data structure. - * @param {V | VertexId} src - The source vertex from which to start the Dijkstra's algorithm. It can be either a - * vertex object or a vertex ID. - * @param {V | VertexId | null} [dest] - The `dest` parameter in the `dijkstraWithoutHeap` function is an optional - * parameter that specifies the destination vertex for the Dijkstra algorithm. It can be either a vertex object or its - * identifier. If no destination is provided, the value is set to `null`. - * @param {boolean} [getMinDist] - The `getMinDist` parameter is a boolean flag that determines whether the minimum - * distance from the source vertex to the destination vertex should be calculated and returned in the result. If - * `getMinDist` is set to `true`, the `minDist` property in the result will contain the minimum distance - * @param {boolean} [genPaths] - The `genPaths` parameter is a boolean flag that determines whether or not to generate - * paths in the Dijkstra algorithm. If `genPaths` is set to `true`, the algorithm will calculate and return the - * shortest paths from the source vertex to all other vertices in the graph. If `genPaths - * @returns The function `dijkstraWithoutHeap` returns an object of type `DijkstraResult`. - */ - dijkstraWithoutHeap(src: V | VertexId, dest?: V | VertexId | null, getMinDist?: boolean, genPaths?: boolean): DijkstraResult { - if (getMinDist === undefined) getMinDist = false; - if (genPaths === undefined) genPaths = false; + protected abstract _addEdgeOnly(edge: E): boolean; - 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) { - const vertexOrId = vertex[1]; - if (vertexOrId instanceof AbstractVertex) distMap.set(vertexOrId, 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 vertexOrId = vertex[1]; - - if (vertexOrId instanceof AbstractVertex) { - const path: V[] = [vertexOrId]; - let parent = preMap.get(vertexOrId); - 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}; + protected _addVertexOnly(newVertex: V): boolean { + if (this.hasVertex(newVertex)) { + return false; + // throw (new Error('Duplicated vertex id is not allowed')); } + this._vertices.set(newVertex.id, newVertex); + return true; + } - /** - * Dijkstra algorithm time: O(logVE) space: O(V + E) - * - * Dijkstra's algorithm only solves the single-source shortest path problem, while the Bellman-Ford algorithm and Floyd-Warshall algorithm can address shortest paths between all pairs of nodes. - * Dijkstra's algorithm is suitable for graphs with non-negative edge weights, whereas the Bellman-Ford algorithm and Floyd-Warshall algorithm can handle negative-weight edges. - * The time complexity of Dijkstra's algorithm and the Bellman-Ford algorithm depends on the size of the graph, while the time complexity of the Floyd-Warshall algorithm is O(V^3), where V is the number of nodes. For dense graphs, Floyd-Warshall might become slower. - * - * / + protected _getVertex(vertexOrId: VertexId | V): V | null { + const vertexId = this._getVertexId(vertexOrId); + return this._vertices.get(vertexId) || null; + } - /** - * Dijkstra's algorithm is used to find the shortest paths from a source node to all other nodes in a graph. Its basic idea is to repeatedly choose the node closest to the source node and update the distances of other nodes using this node as an intermediary. Dijkstra's algorithm requires that the edge weights in the graph are non-negative. - * The `dijkstra` function implements Dijkstra's algorithm to find the shortest path between a source vertex and an - * optional destination vertex, and optionally returns the minimum distance, the paths, and other information. - * @param {V | VertexId} src - The `src` parameter represents the source vertex from which the Dijkstra algorithm will - * start. It can be either a vertex object or a vertex ID. - * @param {V | VertexId | null} [dest] - The `dest` parameter is the destination vertex or vertex ID. It specifies the - * vertex to which the shortest path is calculated from the source vertex. If no destination is provided, the algorithm - * will calculate the shortest paths to all other vertices from the source vertex. - * @param {boolean} [getMinDist] - The `getMinDist` parameter is a boolean flag that determines whether the minimum - * distance from the source vertex to the destination vertex should be calculated and returned in the result. If - * `getMinDist` is set to `true`, the `minDist` property in the result will contain the minimum distance - * @param {boolean} [genPaths] - The `genPaths` parameter is a boolean flag that determines whether or not to generate - * paths in the Dijkstra algorithm. If `genPaths` is set to `true`, the algorithm will calculate and return the - * shortest paths from the source vertex to all other vertices in the graph. If `genPaths - * @returns The function `dijkstra` returns an object of type `DijkstraResult`. - */ - dijkstra(src: V | VertexId, dest?: V | VertexId | null, getMinDist?: boolean, genPaths?: boolean): DijkstraResult { - if (getMinDist === undefined) getMinDist = false; - if (genPaths === undefined) genPaths = false; + protected _getVertexId(vertexOrId: V | VertexId): VertexId { + return vertexOrId instanceof AbstractVertex ? vertexOrId.id : vertexOrId; + } - 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) { - const vertexOrId = vertex[1]; - if (vertexOrId instanceof AbstractVertex) distMap.set(vertexOrId, Infinity); - } - - const heap = new PriorityQueue<{ id: number, val: V }>({comparator: (a, b) => a.id - b.id}); - heap.add({id: 0, val: srcVertex}); - - distMap.set(srcVertex, 0); - preMap.set(srcVertex, null); - - /** - * The function `getPaths` retrieves all paths from vertices to a specified minimum vertex. - * @param {V | null} minV - The parameter `minV` is of type `V | null`. It represents the minimum vertex value or - * null. - */ - const getPaths = (minV: V | null) => { - for (const vertex of vertices) { - const vertexOrId = vertex[1]; - if (vertexOrId instanceof AbstractVertex) { - const path: V[] = [vertexOrId]; - let parent = preMap.get(vertexOrId); - 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.add({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}; - } - - /** - * BellmanFord time:O(VE) space:O(V) - * one to rest pairs - * / - - /** - * BellmanFord time:O(VE) space:O(V) - * one to rest pairs - * The Bellman-Ford algorithm is also used to find the shortest paths from a source node to all other nodes in a graph. Unlike Dijkstra's algorithm, it can handle edge weights that are negative. Its basic idea involves iterative relaxation of all edges for several rounds to gradually approximate the shortest paths. Due to its ability to handle negative-weight edges, the Bellman-Ford algorithm is more flexible in some scenarios. - * The `bellmanFord` function implements the Bellman-Ford algorithm to find the shortest path from a source vertex to - * all other vertices in a graph, and optionally detects negative cycles and generates the minimum path. - * @param {V | VertexId} src - The `src` parameter is the source vertex from which the Bellman-Ford algorithm will - * start calculating the shortest paths. It can be either a vertex object or a vertex ID. - * @param {boolean} [scanNegativeCycle] - A boolean flag indicating whether to scan for negative cycles in the graph. - * @param {boolean} [getMin] - The `getMin` parameter is a boolean flag that determines whether the algorithm should - * calculate the minimum distance from the source vertex to all other vertices in the graph. If `getMin` is set to - * `true`, the algorithm will find the minimum distance and update the `min` variable with the minimum - * @param {boolean} [genPath] - A boolean flag indicating whether to generate paths for all vertices from the source - * vertex. - * @returns The function `bellmanFord` returns an object with the following properties: - */ - 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; - 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 vertexOrId = vertex[1]; - if (vertexOrId instanceof AbstractVertex) { - const path: V[] = [vertexOrId]; - let parent = preMap.get(vertexOrId); - 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}; - } - - /** - * Dijkstra algorithm time: O(logVE) space: O(V + E) - * / - - /** - * Dijkstra algorithm time: O(logVE) space: O(V + E) - * Dijkstra's algorithm is used to find the shortest paths from a source node to all other nodes in a graph. Its basic idea is to repeatedly choose the node closest to the source node and update the distances of other nodes using this node as an intermediary. Dijkstra's algorithm requires that the edge weights in the graph are non-negative. - */ - - - /** - * BellmanFord time:O(VE) space:O(V) - * one to rest pairs - * The Bellman-Ford algorithm is also used to find the shortest paths from a source node to all other nodes in a graph. Unlike Dijkstra's algorithm, it can handle edge weights that are negative. Its basic idea involves iterative relaxation of all edges for several rounds to gradually approximate the shortest paths. Due to its ability to handle negative-weight edges, the Bellman-Ford algorithm is more flexible in some scenarios. - * The `bellmanFord` function implements the Bellman-Ford algorithm to find the shortest path from a source vertex to - */ - - /** - * Floyd algorithm time: O(V^3) space: O(V^2), not support graph with negative weight cycle - * all pairs - * The Floyd-Warshall algorithm is used to find the shortest paths between all pairs of nodes in a graph. It employs dynamic programming to compute the shortest paths from any node to any other node. The Floyd-Warshall algorithm's advantage lies in its ability to handle graphs with negative-weight edges, and it can simultaneously compute shortest paths between any two nodes. - */ - - /** - * Floyd algorithm time: O(V^3) space: O(V^2), not support graph with negative weight cycle - * all pairs - * / - - /** - * Floyd algorithm time: O(V^3) space: O(V^2), not support graph with negative weight cycle - * all pairs - * The Floyd-Warshall algorithm is used to find the shortest paths between all pairs of nodes in a graph. It employs dynamic programming to compute the shortest paths from any node to any other node. The Floyd-Warshall algorithm's advantage lies in its ability to handle graphs with negative-weight edges, and it can simultaneously compute shortest paths between any two nodes. - * The function implements the Floyd-Warshall algorithm to find the shortest path between all pairs of vertices in a - * graph. - * @returns The function `floyd()` returns an object with two properties: `costs` and `predecessor`. The `costs` - * property is a 2D array of numbers representing the shortest path costs between vertices in a graph. The - * `predecessor` property is a 2D array of vertices (or `null`) representing the predecessor vertices in the shortest - * path between vertices in the - */ - 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}; - - } - - /** - * 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 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. - * The `tarjan` function is used to perform various graph analysis tasks such as finding articulation points, bridges, - * strongly connected components (SCCs), and cycles in a graph. - * @param {boolean} [needArticulationPoints] - A boolean value indicating whether or not to calculate and return the - * articulation points in the graph. Articulation points are the vertices in a graph whose removal would increase the - * number of connected components in the graph. - * @param {boolean} [needBridges] - A boolean flag indicating whether the algorithm should find and return the bridges - * (edges whose removal would increase the number of connected components in the graph). - * @param {boolean} [needSCCs] - A boolean value indicating whether the Strongly Connected Components (SCCs) of the - * graph are needed. If set to true, the function will calculate and return the SCCs of the graph. If set to false, the - * SCCs will not be calculated or returned. - * @param {boolean} [needCycles] - A boolean flag indicating whether the algorithm should find cycles in the graph. If - * set to true, the algorithm will return a map of cycles, where the keys are the low values of the SCCs and the values - * are arrays of vertices that form cycles within the SCCs. - * @returns The function `tarjan` returns an object with the following properties: - */ - 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}; - } - - protected abstract _addEdgeOnly(edge: E): boolean; - - protected _addVertexOnly(newVertex: V): boolean { - if (this.hasVertex(newVertex)) { - return false; - // throw (new Error('Duplicated vertex id is not allowed')); - } - this._vertices.set(newVertex.id, newVertex); - return true; - } - - protected _getVertex(vertexOrId: VertexId | V): V | null { - const vertexId = this._getVertexId(vertexOrId); - return this._vertices.get(vertexId) || null; - } - - protected _getVertexId(vertexOrId: V | VertexId): VertexId { - return vertexOrId instanceof AbstractVertex ? vertexOrId.id : vertexOrId; - } - - protected _setVertices(value: Map) { - this._vertices = value; - } + protected _setVertices(value: Map) { + this._vertices = value; + } } diff --git a/src/data-structures/graph/directed-graph.ts b/src/data-structures/graph/directed-graph.ts index 834347d..d261bb5 100644 --- a/src/data-structures/graph/directed-graph.ts +++ b/src/data-structures/graph/directed-graph.ts @@ -11,462 +11,462 @@ import type {TopologicalStatus, VertexId} from '../../types'; import {IDirectedGraph} from '../../interfaces'; export class DirectedVertex extends AbstractVertex { - /** - * The constructor function initializes a vertex with an optional value. - * @param {VertexId} id - The `id` parameter is of type `VertexId` and represents the identifier of the vertex. It is - * used to uniquely identify the vertex within a graph or data structure. - * @param {T} [val] - The "val" parameter is an optional parameter of type T. It is used to initialize the value of the - * vertex. If no value is provided, the vertex will be initialized with a default value. - */ - constructor(id: VertexId, val?: T) { - super(id, val); - } + /** + * The constructor function initializes a vertex with an optional value. + * @param {VertexId} id - The `id` parameter is of type `VertexId` and represents the identifier of the vertex. It is + * used to uniquely identify the vertex within a graph or data structure. + * @param {T} [val] - The "val" parameter is an optional parameter of type T. It is used to initialize the value of the + * vertex. If no value is provided, the vertex will be initialized with a default value. + */ + constructor(id: VertexId, val?: T) { + super(id, val); + } } export class DirectedEdge extends AbstractEdge { - /** - * The constructor function initializes the source and destination vertices of an edge, along with an optional weight - * and value. - * @param {VertexId} src - The `src` parameter is the source vertex ID. It represents the starting point of an edge in - * a graph. - * @param {VertexId} dest - The `dest` parameter represents the destination vertex of an edge. It is of type - * `VertexId`, which is likely a unique identifier for a vertex in a graph. - * @param {number} [weight] - The weight parameter is an optional number that represents the weight of the edge. - * @param {T} [val] - The `val` parameter is an optional parameter of type `T`. It represents the value associated with - * the edge. - */ - constructor(src: VertexId, dest: VertexId, weight?: number, val?: T) { - super(weight, val); - this._src = src; - this._dest = dest; - } + /** + * The constructor function initializes the source and destination vertices of an edge, along with an optional weight + * and value. + * @param {VertexId} src - The `src` parameter is the source vertex ID. It represents the starting point of an edge in + * a graph. + * @param {VertexId} dest - The `dest` parameter represents the destination vertex of an edge. It is of type + * `VertexId`, which is likely a unique identifier for a vertex in a graph. + * @param {number} [weight] - The weight parameter is an optional number that represents the weight of the edge. + * @param {T} [val] - The `val` parameter is an optional parameter of type `T`. It represents the value associated with + * the edge. + */ + constructor(src: VertexId, dest: VertexId, weight?: number, val?: T) { + super(weight, val); + this._src = src; + this._dest = dest; + } - private _src: VertexId; + private _src: VertexId; - get src(): VertexId { - return this._src; - } + get src(): VertexId { + return this._src; + } - set src(v: VertexId) { - this._src = v; - } + set src(v: VertexId) { + this._src = v; + } - private _dest: VertexId; + private _dest: VertexId; - get dest(): VertexId { - return this._dest; - } + get dest(): VertexId { + return this._dest; + } - set dest(v: VertexId) { - this._dest = v; - } + set dest(v: VertexId) { + this._dest = v; + } } export class DirectedGraph = DirectedVertex, E extends DirectedEdge = DirectedEdge> extends AbstractGraph implements IDirectedGraph { - /** - * The constructor function initializes an instance of a class. - */ - constructor() { - super(); - } + /** + * The constructor function initializes an instance of a class. + */ + constructor() { + super(); + } - private _outEdgeMap: Map = new Map(); + private _outEdgeMap: Map = new Map(); - get outEdgeMap(): Map { - return this._outEdgeMap; - } + get outEdgeMap(): Map { + return this._outEdgeMap; + } - private _inEdgeMap: Map = new Map(); + private _inEdgeMap: Map = new Map(); - get inEdgeMap(): Map { - return this._inEdgeMap; - } + get inEdgeMap(): Map { + return this._inEdgeMap; + } - /** - * In TypeScript, a subclass inherits the interface implementation of its parent class, without needing to implement the same interface again in the subclass. This behavior differs from Java's approach. In Java, if a parent class implements an interface, the subclass needs to explicitly implement the same interface, even if the parent class has already implemented it. - * This means that using abstract methods in the parent class cannot constrain the grandchild classes. Defining methods within an interface also cannot constrain the descendant classes. When inheriting from this class, developers need to be aware that this method needs to be overridden. - */ + /** + * In TypeScript, a subclass inherits the interface implementation of its parent class, without needing to implement the same interface again in the subclass. This behavior differs from Java's approach. In Java, if a parent class implements an interface, the subclass needs to explicitly implement the same interface, even if the parent class has already implemented it. + * This means that using abstract methods in the parent class cannot constrain the grandchild classes. Defining methods within an interface also cannot constrain the descendant classes. When inheriting from this class, developers need to be aware that this method needs to be overridden. + */ - /** - * The function creates a new vertex with an optional value and returns it. - * @param {VertexId} id - The `id` parameter is the unique identifier for the vertex. It is of type `VertexId`, which - * could be a number or a string depending on how you want to identify your vertices. - * @param [val] - The 'val' parameter is an optional value that can be assigned to the vertex. If a value is provided, - * it will be assigned to the 'val' property of the vertex. If no value is provided, the 'val' property will be - * assigned the same value as the 'id' parameter - * @returns a new instance of a DirectedVertex object, casted as type V. - */ - createVertex(id: VertexId, val?: V['val']): V { - return new DirectedVertex(id, val ?? id) as V; - } + /** + * The function creates a new vertex with an optional value and returns it. + * @param {VertexId} id - The `id` parameter is the unique identifier for the vertex. It is of type `VertexId`, which + * could be a number or a string depending on how you want to identify your vertices. + * @param [val] - The 'val' parameter is an optional value that can be assigned to the vertex. If a value is provided, + * it will be assigned to the 'val' property of the vertex. If no value is provided, the 'val' property will be + * assigned the same value as the 'id' parameter + * @returns a new instance of a DirectedVertex object, casted as type V. + */ + createVertex(id: VertexId, val?: V['val']): V { + return new DirectedVertex(id, val ?? id) as V; + } - /** - * In TypeScript, a subclass inherits the interface implementation of its parent class, without needing to implement the same interface again in the subclass. This behavior differs from Java's approach. In Java, if a parent class implements an interface, the subclass needs to explicitly implement the same interface, even if the parent class has already implemented it. - * This means that using abstract methods in the parent class cannot constrain the grandchild classes. Defining methods within an interface also cannot constrain the descendant classes. When inheriting from this class, developers need to be aware that this method needs to be overridden. - */ + /** + * In TypeScript, a subclass inherits the interface implementation of its parent class, without needing to implement the same interface again in the subclass. This behavior differs from Java's approach. In Java, if a parent class implements an interface, the subclass needs to explicitly implement the same interface, even if the parent class has already implemented it. + * This means that using abstract methods in the parent class cannot constrain the grandchild classes. Defining methods within an interface also cannot constrain the descendant classes. When inheriting from this class, developers need to be aware that this method needs to be overridden. + */ - /** - * The function creates a directed edge between two vertices with an optional weight and value. - * @param {VertexId} src - The source vertex ID of the edge. It represents the starting point of the edge. - * @param {VertexId} dest - The `dest` parameter is the identifier of the destination vertex for the edge. - * @param {number} [weight] - The weight parameter is an optional number that represents the weight of the edge. If no - * weight is provided, it defaults to 1. - * @param [val] - The 'val' parameter is an optional value that can be assigned to the edge. It can be of any type and - * is used to store additional information or data associated with the edge. - * @returns a new instance of a DirectedEdge object, casted as type E. - */ - createEdge(src: VertexId, dest: VertexId, weight?: number, val?: E['val']): E { - return new DirectedEdge(src, dest, weight ?? 1, val) as E; - } + /** + * The function creates a directed edge between two vertices with an optional weight and value. + * @param {VertexId} src - The source vertex ID of the edge. It represents the starting point of the edge. + * @param {VertexId} dest - The `dest` parameter is the identifier of the destination vertex for the edge. + * @param {number} [weight] - The weight parameter is an optional number that represents the weight of the edge. If no + * weight is provided, it defaults to 1. + * @param [val] - The 'val' parameter is an optional value that can be assigned to the edge. It can be of any type and + * is used to store additional information or data associated with the edge. + * @returns a new instance of a DirectedEdge object, casted as type E. + */ + createEdge(src: VertexId, dest: VertexId, weight?: number, val?: E['val']): E { + return new DirectedEdge(src, dest, weight ?? 1, val) as E; + } - /** - * The `getEdge` function retrieves an edge between two vertices based on their source and destination IDs. - * @param {V | null | VertexId} srcOrId - The source vertex or its ID. It can be either a vertex object or a vertex ID. - * @param {V | null | VertexId} destOrId - The `destOrId` parameter in the `getEdge` function represents the - * destination vertex of the edge. It can be either a vertex object (`V`), a vertex ID (`VertexId`), or `null` if the - * destination is not specified. - * @returns the first edge found between the source and destination vertices, or null if no such edge is found. - */ - getEdge(srcOrId: V | null | VertexId, destOrId: V | null | VertexId): E | null { - let edges: E[] = []; + /** + * The `getEdge` function retrieves an edge between two vertices based on their source and destination IDs. + * @param {V | null | VertexId} srcOrId - The source vertex or its ID. It can be either a vertex object or a vertex ID. + * @param {V | null | VertexId} destOrId - The `destOrId` parameter in the `getEdge` function represents the + * destination vertex of the edge. It can be either a vertex object (`V`), a vertex ID (`VertexId`), or `null` if the + * destination is not specified. + * @returns the first edge found between the source and destination vertices, or null if no such edge is found. + */ + 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; - } - - /** - * The function removes an edge between two vertices in a graph and returns the removed edge. - * @param {V | VertexId} srcOrId - The source vertex or its ID. - * @param {V | VertexId} destOrId - The `destOrId` parameter represents the destination vertex or its ID. - * @returns the removed edge (E) if it exists, or null if either the source or destination vertex does not exist. - */ - removeEdgeSrcToDest(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; - } + 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) { - arrayRemove(srcOutEdges, (edge: E) => edge.dest === dest.id); + edges = srcOutEdges.filter(edge => edge.dest === dest.id); } + } + } - const destInEdges = this._inEdgeMap.get(dest); - if (destInEdges) { - removed = arrayRemove(destInEdges, (edge: E) => edge.src === src.id)[0] || null; + return edges[0] || null; + } + + /** + * The function removes an edge between two vertices in a graph and returns the removed edge. + * @param {V | VertexId} srcOrId - The source vertex or its ID. + * @param {V | VertexId} destOrId - The `destOrId` parameter represents the destination vertex or its ID. + * @returns the removed edge (E) if it exists, or null if either the source or destination vertex does not exist. + */ + removeEdgeSrcToDest(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: E) => edge.dest === dest.id); + } + + const destInEdges = this._inEdgeMap.get(dest); + if (destInEdges) { + removed = arrayRemove(destInEdges, (edge: E) => edge.src === src.id)[0] || null; + } + return removed; + } + + /** + * The function removes an edge from a graph and returns the removed edge, or null if the edge was not found. + * @param {E} edge - The `edge` parameter is an object that represents an edge in a graph. It has two properties: `src` + * and `dest`, which represent the source and destination vertices of the edge, respectively. + * @returns The method `removeEdge` returns the removed edge (`E`) if it exists, or `null` if the edge does not exist. + */ + 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: E) => edge.src === src.id); + } + + const destInEdges = this._inEdgeMap.get(dest); + if (destInEdges && destInEdges.length > 0) { + removed = arrayRemove(destInEdges, (edge: E) => edge.dest === dest.id)[0]; + } + + } + + return removed; + } + + /** + * The function removes edges between two vertices and returns the removed edges. + * @param {VertexId | V} v1 - The parameter `v1` can be either a `VertexId` or a `V`. A `VertexId` represents the + * unique identifier of a vertex in a graph, while `V` represents the actual vertex object. + * @param {VertexId | V} v2 - The parameter `v2` represents either a `VertexId` or a `V` object. It is used to specify + * the second vertex in the edge that needs to be removed. + * @returns an array of removed edges (E[]). + */ + removeEdgesBetween(v1: VertexId | V, v2: VertexId | V): E[] { + const removed: E[] = []; + + if (v1 && v2) { + const v1ToV2 = this.removeEdgeSrcToDest(v1, v2); + const v2ToV1 = this.removeEdgeSrcToDest(v2, v1); + + v1ToV2 && removed.push(v1ToV2); + v2ToV1 && removed.push(v2ToV1); + } + + return removed; + } + + /** + * The function `incomingEdgesOf` returns an array of incoming edges for a given vertex or vertex ID. + * @param {V | VertexId} vertexOrId - The parameter `vertexOrId` can be either a vertex object (`V`) or a vertex ID + * (`VertexId`). + * @returns The method `incomingEdgesOf` returns an array of edges (`E[]`). + */ + incomingEdgesOf(vertexOrId: V | VertexId): E[] { + const target = this._getVertex(vertexOrId); + if (target) { + return this.inEdgeMap.get(target) || [] + } + return []; + } + + /** + * The function `outgoingEdgesOf` returns an array of outgoing edges from a given vertex or vertex ID. + * @param {V | VertexId} vertexOrId - The parameter `vertexOrId` can accept either a vertex object (`V`) or a vertex ID + * (`VertexId`). + * @returns The method `outgoingEdgesOf` returns an array of edges (`E[]`). + */ + outgoingEdgesOf(vertexOrId: V | VertexId): E[] { + const target = this._getVertex(vertexOrId); + if (target) { + return this._outEdgeMap.get(target) || []; + } + return []; + } + + /** + * The function "degreeOf" returns the total degree of a vertex, which is the sum of its out-degree and in-degree. + * @param {VertexId | V} vertexOrId - The parameter `vertexOrId` can be either a `VertexId` or a `V`. + * @returns The sum of the out-degree and in-degree of the specified vertex or vertex ID. + */ + degreeOf(vertexOrId: VertexId | V): number { + return this.outDegreeOf(vertexOrId) + this.inDegreeOf(vertexOrId); + } + + /** + * The function "inDegreeOf" returns the number of incoming edges for a given vertex. + * @param {VertexId | V} vertexOrId - The parameter `vertexOrId` can be either a `VertexId` or a `V`. + * @returns The number of incoming edges of the specified vertex or vertex ID. + */ + inDegreeOf(vertexOrId: VertexId | V): number { + return this.incomingEdgesOf(vertexOrId).length; + } + + /** + * The function `outDegreeOf` returns the number of outgoing edges from a given vertex. + * @param {VertexId | V} vertexOrId - The parameter `vertexOrId` can be either a `VertexId` or a `V`. + * @returns The number of outgoing edges from the specified vertex or vertex ID. + */ + outDegreeOf(vertexOrId: VertexId | V): number { + return this.outgoingEdgesOf(vertexOrId).length; + } + + /** + * The function "edgesOf" returns an array of both outgoing and incoming edges of a given vertex or vertex ID. + * @param {VertexId | V} vertexOrId - The parameter `vertexOrId` can be either a `VertexId` or a `V`. + * @returns The function `edgesOf` returns an array of edges. + */ + edgesOf(vertexOrId: VertexId | V): E[] { + return [...this.outgoingEdgesOf(vertexOrId), ...this.incomingEdgesOf(vertexOrId)]; + } + + /** + * The function "getEdgeSrc" returns the source vertex of an edge, or null if the edge does not exist. + * @param {E} e - The parameter "e" is of type E, which represents an edge in a graph. + * @returns either a vertex object (V) or null. + */ + getEdgeSrc(e: E): V | null { + return this._getVertex(e.src); + } + + /** + * The function "getEdgeDest" returns the destination vertex of an edge. + * @param {E} e - The parameter "e" is of type "E", which represents an edge in a graph. + * @returns either a vertex object of type V or null. + */ + getEdgeDest(e: E): V | null { + return this._getVertex(e.dest); + } + + /** + * The function `getDestinations` returns an array of destination vertices connected to a given vertex. + * @param {V | VertexId | null} vertex - The `vertex` parameter represents the starting vertex from which we want to + * find the destinations. It can be either a `V` object, a `VertexId` value, or `null`. + * @returns an array of vertices (V[]). + */ + getDestinations(vertex: V | VertexId | 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; + } + + /** + * The `topologicalSort` function performs a topological sort on a graph and returns an array of vertices or vertex IDs + * in the sorted order, or null if the graph contains a cycle. + * @param {'vertex' | 'id'} [propertyName] - The `propertyName` parameter is an optional parameter that specifies the + * property to use for sorting the vertices. It can have two possible values: 'vertex' or 'id'. If 'vertex' is + * specified, the vertices themselves will be used for sorting. If 'id' is specified, the ids of + * @returns an array of vertices or vertex IDs in topological order. If there is a cycle in the graph, it returns null. + */ + topologicalSort(propertyName?: 'vertex' | 'id'): Array | null { + propertyName = propertyName ?? 'id'; + // 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); + } + + let sorted: (V | VertexId)[] = []; + let hasCycle = false; + const dfs = (cur: V | VertexId) => { + 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; } - return removed; + } + statusMap.set(cur, 2); + sorted.push(cur); + }; + + for (const entry of this.vertices) { + if (statusMap.get(entry[1]) === 0) { + dfs(entry[1]); + } } - /** - * The function removes an edge from a graph and returns the removed edge, or null if the edge was not found. - * @param {E} edge - The `edge` parameter is an object that represents an edge in a graph. It has two properties: `src` - * and `dest`, which represent the source and destination vertices of the edge, respectively. - * @returns The method `removeEdge` returns the removed edge (`E`) if it exists, or `null` if the edge does not exist. - */ - 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: E) => edge.src === src.id); - } + if (hasCycle) return null; - const destInEdges = this._inEdgeMap.get(dest); - if (destInEdges && destInEdges.length > 0) { - removed = arrayRemove(destInEdges, (edge: E) => edge.dest === dest.id)[0]; - } + if (propertyName === 'id') sorted = sorted.map(vertex => vertex instanceof DirectedVertex ? vertex.id : vertex); + return sorted.reverse(); + } - } - - return removed; - } - - /** - * The function removes edges between two vertices and returns the removed edges. - * @param {VertexId | V} v1 - The parameter `v1` can be either a `VertexId` or a `V`. A `VertexId` represents the - * unique identifier of a vertex in a graph, while `V` represents the actual vertex object. - * @param {VertexId | V} v2 - The parameter `v2` represents either a `VertexId` or a `V` object. It is used to specify - * the second vertex in the edge that needs to be removed. - * @returns an array of removed edges (E[]). - */ - removeEdgesBetween(v1: VertexId | V, v2: VertexId | V): E[] { - const removed: E[] = []; - - if (v1 && v2) { - const v1ToV2 = this.removeEdgeSrcToDest(v1, v2); - const v2ToV1 = this.removeEdgeSrcToDest(v2, v1); - - v1ToV2 && removed.push(v1ToV2); - v2ToV1 && removed.push(v2ToV1); - } - - return removed; - } - - /** - * The function `incomingEdgesOf` returns an array of incoming edges for a given vertex or vertex ID. - * @param {V | VertexId} vertexOrId - The parameter `vertexOrId` can be either a vertex object (`V`) or a vertex ID - * (`VertexId`). - * @returns The method `incomingEdgesOf` returns an array of edges (`E[]`). - */ - incomingEdgesOf(vertexOrId: V | VertexId): E[] { - const target = this._getVertex(vertexOrId); - if (target) { - return this.inEdgeMap.get(target) || [] - } - return []; - } - - /** - * The function `outgoingEdgesOf` returns an array of outgoing edges from a given vertex or vertex ID. - * @param {V | VertexId} vertexOrId - The parameter `vertexOrId` can accept either a vertex object (`V`) or a vertex ID - * (`VertexId`). - * @returns The method `outgoingEdgesOf` returns an array of edges (`E[]`). - */ - outgoingEdgesOf(vertexOrId: V | VertexId): E[] { - const target = this._getVertex(vertexOrId); - if (target) { - return this._outEdgeMap.get(target) || []; - } - return []; - } - - /** - * The function "degreeOf" returns the total degree of a vertex, which is the sum of its out-degree and in-degree. - * @param {VertexId | V} vertexOrId - The parameter `vertexOrId` can be either a `VertexId` or a `V`. - * @returns The sum of the out-degree and in-degree of the specified vertex or vertex ID. - */ - degreeOf(vertexOrId: VertexId | V): number { - return this.outDegreeOf(vertexOrId) + this.inDegreeOf(vertexOrId); - } - - /** - * The function "inDegreeOf" returns the number of incoming edges for a given vertex. - * @param {VertexId | V} vertexOrId - The parameter `vertexOrId` can be either a `VertexId` or a `V`. - * @returns The number of incoming edges of the specified vertex or vertex ID. - */ - inDegreeOf(vertexOrId: VertexId | V): number { - return this.incomingEdgesOf(vertexOrId).length; - } - - /** - * The function `outDegreeOf` returns the number of outgoing edges from a given vertex. - * @param {VertexId | V} vertexOrId - The parameter `vertexOrId` can be either a `VertexId` or a `V`. - * @returns The number of outgoing edges from the specified vertex or vertex ID. - */ - outDegreeOf(vertexOrId: VertexId | V): number { - return this.outgoingEdgesOf(vertexOrId).length; - } - - /** - * The function "edgesOf" returns an array of both outgoing and incoming edges of a given vertex or vertex ID. - * @param {VertexId | V} vertexOrId - The parameter `vertexOrId` can be either a `VertexId` or a `V`. - * @returns The function `edgesOf` returns an array of edges. - */ - edgesOf(vertexOrId: VertexId | V): E[] { - return [...this.outgoingEdgesOf(vertexOrId), ...this.incomingEdgesOf(vertexOrId)]; - } - - /** - * The function "getEdgeSrc" returns the source vertex of an edge, or null if the edge does not exist. - * @param {E} e - The parameter "e" is of type E, which represents an edge in a graph. - * @returns either a vertex object (V) or null. - */ - getEdgeSrc(e: E): V | null { - return this._getVertex(e.src); - } - - /** - * The function "getEdgeDest" returns the destination vertex of an edge. - * @param {E} e - The parameter "e" is of type "E", which represents an edge in a graph. - * @returns either a vertex object of type V or null. - */ - getEdgeDest(e: E): V | null { - return this._getVertex(e.dest); - } - - /** - * The function `getDestinations` returns an array of destination vertices connected to a given vertex. - * @param {V | VertexId | null} vertex - The `vertex` parameter represents the starting vertex from which we want to - * find the destinations. It can be either a `V` object, a `VertexId` value, or `null`. - * @returns an array of vertices (V[]). - */ - getDestinations(vertex: V | VertexId | 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; - } - - /** - * The `topologicalSort` function performs a topological sort on a graph and returns an array of vertices or vertex IDs - * in the sorted order, or null if the graph contains a cycle. - * @param {'vertex' | 'id'} [propertyName] - The `propertyName` parameter is an optional parameter that specifies the - * property to use for sorting the vertices. It can have two possible values: 'vertex' or 'id'. If 'vertex' is - * specified, the vertices themselves will be used for sorting. If 'id' is specified, the ids of - * @returns an array of vertices or vertex IDs in topological order. If there is a cycle in the graph, it returns null. - */ - topologicalSort(propertyName?: 'vertex' | 'id'): Array | null { - propertyName = propertyName ?? 'id'; - // 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); - } - - let sorted: (V | VertexId)[] = []; - let hasCycle = false; - const dfs = (cur: V | VertexId) => { - 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; - - if (propertyName === 'id') sorted = sorted.map(vertex => vertex instanceof DirectedVertex ? vertex.id : vertex); - return sorted.reverse(); - } - - /** - * The `edgeSet` function returns an array of all the edges in the graph. - * @returns The `edgeSet()` method returns an array of edges (`E[]`). - */ - edgeSet(): E[] { - let edges: E[] = []; - this._outEdgeMap.forEach(outEdges => { - edges = [...edges, ...outEdges]; - }); - return edges; - } - - /** - * The function `getNeighbors` returns an array of neighboring vertices of a given vertex or vertex ID in a graph. - * @param {V | VertexId} vertexOrId - The parameter `vertexOrId` can be either a vertex object (`V`) or a vertex ID - * (`VertexId`). - * @returns an array of vertices (V[]). - */ - 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; - } - - /** - * The function "getEndsOfEdge" returns the source and destination vertices of an edge if it exists in the graph, - * otherwise it returns null. - * @param {E} edge - The parameter `edge` is of type `E`, which represents an edge in a graph. - * @returns The function `getEndsOfEdge` returns an array containing two vertices `[V, V]` if the edge exists in the - * graph. If the edge does not exist, it returns `null`. - */ - getEndsOfEdge(edge: E): [V, V] | null { - if (!this.hasEdge(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; - } - } - - /** - * The function `_addEdgeOnly` adds an edge to a graph if the source and destination vertices exist. - * @param {E} edge - The parameter `edge` is of type `E`, which represents an edge in a graph. It is the edge that - * needs to be added to the graph. - * @returns a boolean value. It returns true if the edge was successfully added to the graph, and false if either the - * source or destination vertex does not exist in the graph. - */ - protected _addEdgeOnly(edge: E): boolean { - if (!(this.hasVertex(edge.src) && this.hasVertex(edge.dest))) { - return false; - } - - const srcVertex = this._getVertex(edge.src); - const destVertex = this._getVertex(edge.dest); + /** + * The `edgeSet` function returns an array of all the edges in the graph. + * @returns The `edgeSet()` method returns an array of edges (`E[]`). + */ + edgeSet(): E[] { + let edges: E[] = []; + this._outEdgeMap.forEach(outEdges => { + edges = [...edges, ...outEdges]; + }); + return edges; + } + /** + * The function `getNeighbors` returns an array of neighboring vertices of a given vertex or vertex ID in a graph. + * @param {V | VertexId} vertexOrId - The parameter `vertexOrId` can be either a vertex object (`V`) or a vertex ID + * (`VertexId`). + * @returns an array of vertices (V[]). + */ + 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 (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; + if (neighbor) { + neighbors.push(neighbor); } + } + } + return neighbors; + } + + /** + * The function "getEndsOfEdge" returns the source and destination vertices of an edge if it exists in the graph, + * otherwise it returns null. + * @param {E} edge - The parameter `edge` is of type `E`, which represents an edge in a graph. + * @returns The function `getEndsOfEdge` returns an array containing two vertices `[V, V]` if the edge exists in the + * graph. If the edge does not exist, it returns `null`. + */ + getEndsOfEdge(edge: E): [V, V] | null { + if (!this.hasEdge(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; + } + } + + /** + * The function `_addEdgeOnly` adds an edge to a graph if the source and destination vertices exist. + * @param {E} edge - The parameter `edge` is of type `E`, which represents an edge in a graph. It is the edge that + * needs to be added to the graph. + * @returns a boolean value. It returns true if the edge was successfully added to the graph, and false if either the + * source or destination vertex does not exist in the graph. + */ + protected _addEdgeOnly(edge: E): boolean { + if (!(this.hasVertex(edge.src) && this.hasVertex(edge.dest))) { + return false; } - protected _setOutEdgeMap(value: Map) { - this._outEdgeMap = value; - } + const srcVertex = this._getVertex(edge.src); + const destVertex = this._getVertex(edge.dest); - protected _setInEdgeMap(value: Map) { - this._inEdgeMap = value; + // 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; } + } + + protected _setOutEdgeMap(value: Map) { + this._outEdgeMap = value; + } + + protected _setInEdgeMap(value: Map) { + this._inEdgeMap = value; + } } diff --git a/src/data-structures/graph/map-graph.ts b/src/data-structures/graph/map-graph.ts index 530409d..be8136f 100644 --- a/src/data-structures/graph/map-graph.ts +++ b/src/data-structures/graph/map-graph.ts @@ -2,127 +2,127 @@ import {MapGraphCoordinate, VertexId} from '../../types'; import {DirectedEdge, DirectedGraph, DirectedVertex} from './directed-graph'; export class MapVertex extends DirectedVertex { - /** - * The constructor function initializes an object with an id, latitude, longitude, and an optional value. - * @param {VertexId} id - The `id` parameter is of type `VertexId` and represents the identifier of the vertex. - * @param {number} lat - The "lat" parameter represents the latitude of a vertex. Latitude is a geographic coordinate - * that specifies the north-south position of a point on the Earth's surface. It is measured in degrees, with positive - * values representing points north of the equator and negative values representing points south of the equator. - * @param {number} long - The "long" parameter represents the longitude of a location. Longitude is a geographic - * coordinate that specifies the east-west position of a point on the Earth's surface. It is measured in degrees, with - * values ranging from -180 to 180. - * @param {T} [val] - The "val" parameter is an optional value of type T. It is not required to be provided when - * creating an instance of the class. - */ - constructor(id: VertexId, lat: number, long: number, val?: T) { - super(id, val); - this._lat = lat; - this._long = long; - } + /** + * The constructor function initializes an object with an id, latitude, longitude, and an optional value. + * @param {VertexId} id - The `id` parameter is of type `VertexId` and represents the identifier of the vertex. + * @param {number} lat - The "lat" parameter represents the latitude of a vertex. Latitude is a geographic coordinate + * that specifies the north-south position of a point on the Earth's surface. It is measured in degrees, with positive + * values representing points north of the equator and negative values representing points south of the equator. + * @param {number} long - The "long" parameter represents the longitude of a location. Longitude is a geographic + * coordinate that specifies the east-west position of a point on the Earth's surface. It is measured in degrees, with + * values ranging from -180 to 180. + * @param {T} [val] - The "val" parameter is an optional value of type T. It is not required to be provided when + * creating an instance of the class. + */ + constructor(id: VertexId, lat: number, long: number, val?: T) { + super(id, val); + this._lat = lat; + this._long = long; + } - private _lat: number; + private _lat: number; - get lat(): number { - return this._lat; - } + get lat(): number { + return this._lat; + } - set lat(value: number) { - this._lat = value; - } + set lat(value: number) { + this._lat = value; + } - private _long: number; + private _long: number; - get long(): number { - return this._long; - } + get long(): number { + return this._long; + } - set long(value: number) { - this._long = value; - } + set long(value: number) { + this._long = value; + } } export class MapEdge extends DirectedEdge { - /** - * The constructor function initializes a new instance of a class with the given source, destination, weight, and - * value. - * @param {VertexId} src - The `src` parameter is the source vertex ID. It represents the starting point of an edge in - * a graph. - * @param {VertexId} dest - The `dest` parameter is the identifier of the destination vertex for an edge. - * @param {number} [weight] - The weight parameter is an optional number that represents the weight of the edge. - * @param {T} [val] - The "val" parameter is an optional parameter of type T. It is used to store additional - * information or data associated with the edge. - */ - constructor(src: VertexId, dest: VertexId, weight?: number, val?: T) { - super(src, dest, weight, val); - } + /** + * The constructor function initializes a new instance of a class with the given source, destination, weight, and + * value. + * @param {VertexId} src - The `src` parameter is the source vertex ID. It represents the starting point of an edge in + * a graph. + * @param {VertexId} dest - The `dest` parameter is the identifier of the destination vertex for an edge. + * @param {number} [weight] - The weight parameter is an optional number that represents the weight of the edge. + * @param {T} [val] - The "val" parameter is an optional parameter of type T. It is used to store additional + * information or data associated with the edge. + */ + constructor(src: VertexId, dest: VertexId, weight?: number, val?: T) { + super(src, dest, weight, val); + } } export class MapGraph = MapVertex, E extends MapEdge = MapEdge> extends DirectedGraph { - /** - * The constructor function initializes the origin and bottomRight properties of a MapGraphCoordinate object. - * @param {MapGraphCoordinate} origin - The `origin` parameter is a `MapGraphCoordinate` object that represents the - * starting point or reference point of the map graph. It defines the coordinates of the top-left corner of the map - * graph. - * @param {MapGraphCoordinate} [bottomRight] - The `bottomRight` parameter is an optional parameter of type - * `MapGraphCoordinate`. It represents the bottom right coordinate of a map graph. If this parameter is not provided, - * it will default to `undefined`. - */ - constructor(origin: MapGraphCoordinate, bottomRight?: MapGraphCoordinate) { - super(); - this._origin = origin; - this._bottomRight = bottomRight; - } + /** + * The constructor function initializes the origin and bottomRight properties of a MapGraphCoordinate object. + * @param {MapGraphCoordinate} origin - The `origin` parameter is a `MapGraphCoordinate` object that represents the + * starting point or reference point of the map graph. It defines the coordinates of the top-left corner of the map + * graph. + * @param {MapGraphCoordinate} [bottomRight] - The `bottomRight` parameter is an optional parameter of type + * `MapGraphCoordinate`. It represents the bottom right coordinate of a map graph. If this parameter is not provided, + * it will default to `undefined`. + */ + constructor(origin: MapGraphCoordinate, bottomRight?: MapGraphCoordinate) { + super(); + this._origin = origin; + this._bottomRight = bottomRight; + } - private _origin: MapGraphCoordinate = [0, 0]; + private _origin: MapGraphCoordinate = [0, 0]; - get origin(): MapGraphCoordinate { - return this._origin; - } + get origin(): MapGraphCoordinate { + return this._origin; + } - set origin(value: MapGraphCoordinate) { - this._origin = value; - } + set origin(value: MapGraphCoordinate) { + this._origin = value; + } - private _bottomRight: MapGraphCoordinate | undefined; + private _bottomRight: MapGraphCoordinate | undefined; - get bottomRight(): MapGraphCoordinate | undefined { - return this._bottomRight; - } + get bottomRight(): MapGraphCoordinate | undefined { + return this._bottomRight; + } - set bottomRight(value: MapGraphCoordinate | undefined) { - this._bottomRight = value; - } + set bottomRight(value: MapGraphCoordinate | undefined) { + this._bottomRight = value; + } - /** - * The function creates a new vertex with the given id, value, latitude, and longitude. - * @param {VertexId} id - The id parameter is the unique identifier for the vertex. It is of type VertexId, which could - * be a string or a number depending on how you define it in your code. - * @param [val] - The `val` parameter is an optional value that can be assigned to the `val` property of the vertex. It - * is of type `V['val']`, which means it should be of the same type as the `val` property of the vertex class `V`. - * @param {number} lat - The `lat` parameter represents the latitude of the vertex. It is a number that specifies the - * position of the vertex on the Earth's surface in the north-south direction. - * @param {number} long - The `long` parameter represents the longitude coordinate of the vertex. - * @returns The method is returning a new instance of the `MapVertex` class, casted as type `V`. - */ - override createVertex(id: VertexId, val?: V['val'], lat: number = this.origin[0], long: number = this.origin[1]): V { - return new MapVertex(id, lat, long, val) as V; - } + /** + * The function creates a new vertex with the given id, value, latitude, and longitude. + * @param {VertexId} id - The id parameter is the unique identifier for the vertex. It is of type VertexId, which could + * be a string or a number depending on how you define it in your code. + * @param [val] - The `val` parameter is an optional value that can be assigned to the `val` property of the vertex. It + * is of type `V['val']`, which means it should be of the same type as the `val` property of the vertex class `V`. + * @param {number} lat - The `lat` parameter represents the latitude of the vertex. It is a number that specifies the + * position of the vertex on the Earth's surface in the north-south direction. + * @param {number} long - The `long` parameter represents the longitude coordinate of the vertex. + * @returns The method is returning a new instance of the `MapVertex` class, casted as type `V`. + */ + override createVertex(id: VertexId, val?: V['val'], lat: number = this.origin[0], long: number = this.origin[1]): V { + return new MapVertex(id, lat, long, val) as V; + } - /** - * The function creates a new instance of a MapEdge with the given source, destination, weight, and value. - * @param {VertexId} src - The source vertex ID of the edge. It represents the starting point of the edge. - * @param {VertexId} dest - The `dest` parameter is the identifier of the destination vertex for the edge being - * created. - * @param {number} [weight] - The `weight` parameter is an optional number that represents the weight of the edge. It - * is used to assign a numerical value to the edge, which can be used in algorithms such as shortest path algorithms. - * If the weight is not provided, it can be set to a default value or left undefined. - * @param [val] - The `val` parameter is an optional value that can be assigned to the edge. It can be of any type, - * depending on the specific implementation of the `MapEdge` class. - * @returns a new instance of the `MapEdge` class, casted as type `E`. - */ - override createEdge(src: VertexId, dest: VertexId, weight?: number, val?: E['val']): E { - return new MapEdge(src, dest, weight, val) as E; - } -} \ No newline at end of file + /** + * The function creates a new instance of a MapEdge with the given source, destination, weight, and value. + * @param {VertexId} src - The source vertex ID of the edge. It represents the starting point of the edge. + * @param {VertexId} dest - The `dest` parameter is the identifier of the destination vertex for the edge being + * created. + * @param {number} [weight] - The `weight` parameter is an optional number that represents the weight of the edge. It + * is used to assign a numerical value to the edge, which can be used in algorithms such as shortest path algorithms. + * If the weight is not provided, it can be set to a default value or left undefined. + * @param [val] - The `val` parameter is an optional value that can be assigned to the edge. It can be of any type, + * depending on the specific implementation of the `MapEdge` class. + * @returns a new instance of the `MapEdge` class, casted as type `E`. + */ + override createEdge(src: VertexId, dest: VertexId, weight?: number, val?: E['val']): E { + return new MapEdge(src, dest, weight, val) as E; + } +} diff --git a/src/data-structures/graph/undirected-graph.ts b/src/data-structures/graph/undirected-graph.ts index 8c106e3..36b6448 100644 --- a/src/data-structures/graph/undirected-graph.ts +++ b/src/data-structures/graph/undirected-graph.ts @@ -11,260 +11,260 @@ import type {VertexId} from '../../types'; import {IUNDirectedGraph} from '../../interfaces'; export class UndirectedVertex extends AbstractVertex { - /** - * The constructor function initializes a vertex with an optional value. - * @param {VertexId} id - The `id` parameter is of type `VertexId` and represents the identifier of the vertex. It is - * used to uniquely identify the vertex within a graph or network. - * @param {T} [val] - The "val" parameter is an optional parameter of type T. It is used to initialize the value of the - * vertex. If no value is provided, the vertex will be initialized with a default value. - */ - constructor(id: VertexId, val?: T) { - super(id, val); - } + /** + * The constructor function initializes a vertex with an optional value. + * @param {VertexId} id - The `id` parameter is of type `VertexId` and represents the identifier of the vertex. It is + * used to uniquely identify the vertex within a graph or network. + * @param {T} [val] - The "val" parameter is an optional parameter of type T. It is used to initialize the value of the + * vertex. If no value is provided, the vertex will be initialized with a default value. + */ + constructor(id: VertexId, val?: T) { + super(id, val); + } } export class UndirectedEdge extends AbstractEdge { - /** - * The constructor function creates an instance of a class with two vertex IDs, an optional weight, and an optional - * value. - * @param {VertexId} v1 - The first vertex ID of the edge. - * @param {VertexId} v2 - The parameter `v2` is a `VertexId`, which represents the identifier of the second vertex in a - * graph edge. - * @param {number} [weight] - The weight parameter is an optional number that represents the weight of the edge. - * @param {T} [val] - The "val" parameter is an optional parameter of type T. It is used to store a value associated - * with the edge. - */ - constructor(v1: VertexId, v2: VertexId, weight?: number, val?: T) { - super(weight, val); - this._vertices = [v1, v2]; - } + /** + * The constructor function creates an instance of a class with two vertex IDs, an optional weight, and an optional + * value. + * @param {VertexId} v1 - The first vertex ID of the edge. + * @param {VertexId} v2 - The parameter `v2` is a `VertexId`, which represents the identifier of the second vertex in a + * graph edge. + * @param {number} [weight] - The weight parameter is an optional number that represents the weight of the edge. + * @param {T} [val] - The "val" parameter is an optional parameter of type T. It is used to store a value associated + * with the edge. + */ + constructor(v1: VertexId, v2: VertexId, weight?: number, val?: T) { + super(weight, val); + this._vertices = [v1, v2]; + } - private _vertices: [VertexId, VertexId]; + private _vertices: [VertexId, VertexId]; - get vertices() { - return this._vertices; - } + get vertices() { + return this._vertices; + } - set vertices(v: [VertexId, VertexId]) { - this._vertices = v; - } + set vertices(v: [VertexId, VertexId]) { + this._vertices = v; + } } export class UndirectedGraph = UndirectedVertex, E extends UndirectedEdge = UndirectedEdge> extends AbstractGraph implements IUNDirectedGraph { - /** - * The constructor initializes a new Map object to store edges. - */ - constructor() { - super(); - this._edges = new Map(); + /** + * The constructor initializes a new Map object to store edges. + */ + constructor() { + super(); + this._edges = new Map(); + } + + protected _edges: Map; + + get edges(): Map { + return this._edges; + } + + /** + * The function creates a new vertex with an optional value and returns it. + * @param {VertexId} id - The `id` parameter is the unique identifier for the vertex. It is used to distinguish one + * vertex from another in the graph. + * @param [val] - The `val` parameter is an optional value that can be assigned to the vertex. If a value is provided, + * it will be used as the value of the vertex. If no value is provided, the `id` parameter will be used as the value of + * the vertex. + * @returns The method is returning a new instance of the `UndirectedVertex` class, casted as type `V`. + */ + override createVertex(id: VertexId, val?: V['val']): V { + return new UndirectedVertex(id, val ?? id) as V; + } + + /** + * The function creates an undirected edge between two vertices with an optional weight and value. + * @param {VertexId} v1 - The parameter `v1` represents the first vertex of the edge. + * @param {VertexId} v2 - The parameter `v2` represents the second vertex of the edge. + * @param {number} [weight] - The `weight` parameter is an optional number that represents the weight of the edge. If + * no weight is provided, it defaults to 1. + * @param [val] - The `val` parameter is an optional value that can be assigned to the edge. It can be of any type and + * is used to store additional information or data associated with the edge. + * @returns a new instance of the `UndirectedEdge` class, which is casted as type `E`. + */ + override createEdge(v1: VertexId, v2: VertexId, weight?: number, val?: E['val']): E { + return new UndirectedEdge(v1, v2, weight ?? 1, val) as E; + } + + /** + * The function `getEdge` returns the first edge that connects two vertices, or null if no such edge exists. + * @param {V | null | VertexId} v1 - The parameter `v1` represents a vertex or vertex ID. It can be of type `V` (vertex + * object), `null`, or `VertexId` (a string or number representing the ID of a vertex). + * @param {V | null | VertexId} v2 - The parameter `v2` represents a vertex or vertex ID. It can be of type `V` (vertex + * object), `null`, or `VertexId` (vertex ID). + * @returns an edge (E) or null. + */ + 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)); + } } - protected _edges: Map; + return edges ? edges[0] || null : null; + } - get edges(): Map { - return this._edges; + /** + * The function removes an edge between two vertices in a graph and returns the removed edge. + * @param {V | VertexId} v1 - The parameter `v1` represents either a vertex object (`V`) or a vertex ID (`VertexId`). + * @param {V | VertexId} v2 - V | VertexId - This parameter can be either a vertex object (V) or a vertex ID + * (VertexId). It represents the second vertex of the edge that needs to be removed. + * @returns the removed edge (E) if it exists, or null if either of the vertices (V) does not exist. + */ + 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; } - /** - * The function creates a new vertex with an optional value and returns it. - * @param {VertexId} id - The `id` parameter is the unique identifier for the vertex. It is used to distinguish one - * vertex from another in the graph. - * @param [val] - The `val` parameter is an optional value that can be assigned to the vertex. If a value is provided, - * it will be used as the value of the vertex. If no value is provided, the `id` parameter will be used as the value of - * the vertex. - * @returns The method is returning a new instance of the `UndirectedVertex` class, casted as type `V`. - */ - override createVertex(id: VertexId, val?: V['val']): V { - return new UndirectedVertex(id, val ?? id) as V; + const v1Edges = this._edges.get(vertex1); + let removed: E | null = null; + if (v1Edges) { + removed = arrayRemove(v1Edges, (e: E) => e.vertices.includes(vertex2.id))[0] || null; } - - /** - * The function creates an undirected edge between two vertices with an optional weight and value. - * @param {VertexId} v1 - The parameter `v1` represents the first vertex of the edge. - * @param {VertexId} v2 - The parameter `v2` represents the second vertex of the edge. - * @param {number} [weight] - The `weight` parameter is an optional number that represents the weight of the edge. If - * no weight is provided, it defaults to 1. - * @param [val] - The `val` parameter is an optional value that can be assigned to the edge. It can be of any type and - * is used to store additional information or data associated with the edge. - * @returns a new instance of the `UndirectedEdge` class, which is casted as type `E`. - */ - override createEdge(v1: VertexId, v2: VertexId, weight?: number, val?: E['val']): E { - return new UndirectedEdge(v1, v2, weight ?? 1, val) as E; + const v2Edges = this._edges.get(vertex2); + if (v2Edges) { + arrayRemove(v2Edges, (e: E) => e.vertices.includes(vertex1.id)); } + return removed; + } - /** - * The function `getEdge` returns the first edge that connects two vertices, or null if no such edge exists. - * @param {V | null | VertexId} v1 - The parameter `v1` represents a vertex or vertex ID. It can be of type `V` (vertex - * object), `null`, or `VertexId` (a string or number representing the ID of a vertex). - * @param {V | null | VertexId} v2 - The parameter `v2` represents a vertex or vertex ID. It can be of type `V` (vertex - * object), `null`, or `VertexId` (vertex ID). - * @returns an edge (E) or null. - */ - getEdge(v1: V | null | VertexId, v2: V | null | VertexId): E | null { - let edges: E[] | undefined = []; + /** + * The removeEdge function removes an edge between two vertices in a graph. + * @param {E} edge - The parameter "edge" is of type E, which represents an edge in a graph. + * @returns The method is returning either the removed edge (of type E) or null if the edge was not found. + */ + removeEdge(edge: E): E | null { + return this.removeEdgeBetween(edge.vertices[0], edge.vertices[1]); + } - if (v1 !== null && v2 !== null) { - const vertex1: V | null = this._getVertex(v1); - const vertex2: V | null = this._getVertex(v2); + /** + * The function `degreeOf` returns the degree of a vertex in a graph, which is the number of edges connected to that + * vertex. + * @param {VertexId | V} vertexOrId - The parameter `vertexOrId` can be either a `VertexId` or a `V`. + * @returns The function `degreeOf` returns the degree of a vertex in a graph. The degree of a vertex is the number of + * edges connected to that vertex. + */ + degreeOf(vertexOrId: VertexId | V): number { + const vertex = this._getVertex(vertexOrId); + if (vertex) { + return this._edges.get(vertex)?.length || 0; + } else { + return 0; + } + } - if (vertex1 && vertex2) { - edges = this._edges.get(vertex1)?.filter(e => e.vertices.includes(vertex2.id)); - } + /** + * The function returns the edges of a given vertex or vertex ID. + * @param {VertexId | V} vertexOrId - The parameter `vertexOrId` can be either a `VertexId` or a `V`. A `VertexId` is a + * unique identifier for a vertex in a graph, while `V` represents the type of the vertex. + * @returns an array of edges. + */ + edgesOf(vertexOrId: VertexId | V): E[] { + const vertex = this._getVertex(vertexOrId); + if (vertex) { + return this._edges.get(vertex) || []; + } else { + return []; + } + } + + /** + * The function "edgeSet" returns an array of unique edges from a set of edges. + * @returns The method `edgeSet()` returns an array of type `E[]`. + */ + edgeSet(): E[] { + const edgeSet: Set = new Set(); + this._edges.forEach(edges => { + edges.forEach(edge => { + edgeSet.add(edge); + }); + }); + return [...edgeSet]; + } + + /** + * The function "getNeighbors" returns an array of neighboring vertices for a given vertex or vertex ID. + * @param {V | VertexId} vertexOrId - The parameter `vertexOrId` can be either a vertex object (`V`) or a vertex ID + * (`VertexId`). + * @returns an array of vertices (V[]). + */ + getNeighbors(vertexOrId: V | VertexId): V[] { + const neighbors: V[] = []; + const vertex = this._getVertex(vertexOrId); + if (vertex) { + const neighborEdges = this.edgesOf(vertex); + for (const edge of neighborEdges) { + const neighbor = this._getVertex(edge.vertices.filter(e => e !== vertex.id)[0]); + if (neighbor) { + neighbors.push(neighbor); } - - return edges ? edges[0] || null : null; + } } + return neighbors; + } - /** - * The function removes an edge between two vertices in a graph and returns the removed edge. - * @param {V | VertexId} v1 - The parameter `v1` represents either a vertex object (`V`) or a vertex ID (`VertexId`). - * @param {V | VertexId} v2 - V | VertexId - This parameter can be either a vertex object (V) or a vertex ID - * (VertexId). It represents the second vertex of the edge that needs to be removed. - * @returns the removed edge (E) if it exists, or null if either of the vertices (V) does not exist. - */ - 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) => e.vertices.includes(vertex2.id))[0] || null; - } - const v2Edges = this._edges.get(vertex2); - if (v2Edges) { - arrayRemove(v2Edges, (e: E) => e.vertices.includes(vertex1.id)); - } - return removed; + /** + * The function "getEndsOfEdge" returns the vertices at the ends of an edge if the edge exists in the graph, otherwise + * it returns null. + * @param {E} edge - The parameter "edge" is of type E, which represents an edge in a graph. + * @returns The function `getEndsOfEdge` returns an array containing two vertices `[V, V]` if the edge exists in the + * graph. If the edge does not exist, it returns `null`. + */ + getEndsOfEdge(edge: E): [V, V] | null { + if (!this.hasEdge(edge.vertices[0], edge.vertices[1])) { + return null; } - - /** - * The removeEdge function removes an edge between two vertices in a graph. - * @param {E} edge - The parameter "edge" is of type E, which represents an edge in a graph. - * @returns The method is returning either the removed edge (of type E) or null if the edge was not found. - */ - removeEdge(edge: E): E | null { - return this.removeEdgeBetween(edge.vertices[0], edge.vertices[1]); + const v1 = this._getVertex(edge.vertices[0]); + const v2 = this._getVertex(edge.vertices[1]); + if (v1 && v2) { + return [v1, v2]; + } else { + return null; } + } - /** - * The function `degreeOf` returns the degree of a vertex in a graph, which is the number of edges connected to that - * vertex. - * @param {VertexId | V} vertexOrId - The parameter `vertexOrId` can be either a `VertexId` or a `V`. - * @returns The function `degreeOf` returns the degree of a vertex in a graph. The degree of a vertex is the number of - * edges connected to that vertex. - */ - degreeOf(vertexOrId: VertexId | V): number { - const vertex = this._getVertex(vertexOrId); - if (vertex) { - return this._edges.get(vertex)?.length || 0; + /** + * The function adds an edge to the graph by updating the adjacency list with the vertices of the edge. + * @param {E} edge - The parameter "edge" is of type E, which represents an edge in a graph. + * @returns a boolean value. + */ + protected _addEdgeOnly(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 { - return 0; + this._edges.set(endVertex, [edge]); } + } } + return true; + } - /** - * The function returns the edges of a given vertex or vertex ID. - * @param {VertexId | V} vertexOrId - The parameter `vertexOrId` can be either a `VertexId` or a `V`. A `VertexId` is a - * unique identifier for a vertex in a graph, while `V` represents the type of the vertex. - * @returns an array of edges. - */ - edgesOf(vertexOrId: VertexId | V): E[] { - const vertex = this._getVertex(vertexOrId); - if (vertex) { - return this._edges.get(vertex) || []; - } else { - return []; - } - } - - /** - * The function "edgeSet" returns an array of unique edges from a set of edges. - * @returns The method `edgeSet()` returns an array of type `E[]`. - */ - edgeSet(): E[] { - const edgeSet: Set = new Set(); - this._edges.forEach(edges => { - edges.forEach(edge => { - edgeSet.add(edge); - }); - }); - return [...edgeSet]; - } - - /** - * The function "getNeighbors" returns an array of neighboring vertices for a given vertex or vertex ID. - * @param {V | VertexId} vertexOrId - The parameter `vertexOrId` can be either a vertex object (`V`) or a vertex ID - * (`VertexId`). - * @returns an array of vertices (V[]). - */ - getNeighbors(vertexOrId: V | VertexId): V[] { - const neighbors: V[] = []; - const vertex = this._getVertex(vertexOrId); - if (vertex) { - const neighborEdges = this.edgesOf(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; - } - - /** - * The function "getEndsOfEdge" returns the vertices at the ends of an edge if the edge exists in the graph, otherwise - * it returns null. - * @param {E} edge - The parameter "edge" is of type E, which represents an edge in a graph. - * @returns The function `getEndsOfEdge` returns an array containing two vertices `[V, V]` if the edge exists in the - * graph. If the edge does not exist, it returns `null`. - */ - getEndsOfEdge(edge: E): [V, V] | null { - if (!this.hasEdge(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; - } - } - - /** - * The function adds an edge to the graph by updating the adjacency list with the vertices of the edge. - * @param {E} edge - The parameter "edge" is of type E, which represents an edge in a graph. - * @returns a boolean value. - */ - protected _addEdgeOnly(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; - } - - /** - * The function sets the edges of a graph. - * @param v - A map where the keys are of type V and the values are arrays of type E. - */ - protected _setEdges(v: Map) { - this._edges = v; - } + /** + * The function sets the edges of a graph. + * @param v - A map where the keys are of type V and the values are arrays of type E. + */ + protected _setEdges(v: Map) { + this._edges = v; + } } diff --git a/src/data-structures/hash/coordinate-map.ts b/src/data-structures/hash/coordinate-map.ts index 4463845..8cb1b11 100644 --- a/src/data-structures/hash/coordinate-map.ts +++ b/src/data-structures/hash/coordinate-map.ts @@ -6,62 +6,62 @@ * @license MIT License */ export class CoordinateMap extends Map { - constructor(joint?: string) { - super(); - if (joint !== undefined) this._joint = joint; - } + constructor(joint?: string) { + super(); + if (joint !== undefined) this._joint = joint; + } - protected _joint: string = '_'; + protected _joint: string = '_'; - get joint(): string { - return this._joint; - } + get joint(): string { + return this._joint; + } - /** - * The "has" function overrides the base class's "has" function and checks if a key exists in the map by joining the - * key array with a specified delimiter. - * @param {number[]} key - The parameter "key" is an array of numbers. - * @returns The `has` method is being overridden to return the result of calling the `has` method of the superclass - * (`super.has`) with the `key` array joined together using the `_joint` property. - */ - override has(key: number[]) { - return super.has(key.join(this._joint)); - } + /** + * The "has" function overrides the base class's "has" function and checks if a key exists in the map by joining the + * key array with a specified delimiter. + * @param {number[]} key - The parameter "key" is an array of numbers. + * @returns The `has` method is being overridden to return the result of calling the `has` method of the superclass + * (`super.has`) with the `key` array joined together using the `_joint` property. + */ + override has(key: number[]) { + return super.has(key.join(this._joint)); + } - /** - * The function overrides the set method of a Map object to convert the key from an array to a string using a specified - * delimiter before calling the original set method. - * @param {number[]} key - The key parameter is an array of numbers. - * @param {V} value - The value parameter is the value that you want to associate with the specified key. - * @returns The `set` method is returning the result of calling the `set` method of the superclass - * (`super.set(key.join(this._joint), value)`). - */ - override set(key: number[], value: V) { - return super.set(key.join(this._joint), value); - } + /** + * The function overrides the set method of a Map object to convert the key from an array to a string using a specified + * delimiter before calling the original set method. + * @param {number[]} key - The key parameter is an array of numbers. + * @param {V} value - The value parameter is the value that you want to associate with the specified key. + * @returns The `set` method is returning the result of calling the `set` method of the superclass + * (`super.set(key.join(this._joint), value)`). + */ + override set(key: number[], value: V) { + return super.set(key.join(this._joint), value); + } - /** - * The function overrides the get method to join the key array with a specified joint and then calls the super get - * method. - * @param {number[]} key - An array of numbers - * @returns The code is returning the value associated with the specified key in the map. - */ - override get(key: number[]) { - return super.get(key.join(this._joint)); - } + /** + * The function overrides the get method to join the key array with a specified joint and then calls the super get + * method. + * @param {number[]} key - An array of numbers + * @returns The code is returning the value associated with the specified key in the map. + */ + override get(key: number[]) { + return super.get(key.join(this._joint)); + } - /** - * The function overrides the delete method and joins the key array using a specified joint character before calling - * the super delete method. - * @param {number[]} key - An array of numbers that represents the key to be deleted. - * @returns The `delete` method is returning the result of calling the `delete` method on the superclass, with the - * `key` array joined together using the `_joint` property. - */ - override delete(key: number[]) { - return super.delete(key.join(this._joint)); - } + /** + * The function overrides the delete method and joins the key array using a specified joint character before calling + * the super delete method. + * @param {number[]} key - An array of numbers that represents the key to be deleted. + * @returns The `delete` method is returning the result of calling the `delete` method on the superclass, with the + * `key` array joined together using the `_joint` property. + */ + override delete(key: number[]) { + return super.delete(key.join(this._joint)); + } - protected _setJoint(v: string) { - this._joint = v; - } -} \ No newline at end of file + protected _setJoint(v: string) { + this._joint = v; + } +} diff --git a/src/data-structures/hash/coordinate-set.ts b/src/data-structures/hash/coordinate-set.ts index 7d65fd1..4e9884a 100644 --- a/src/data-structures/hash/coordinate-set.ts +++ b/src/data-structures/hash/coordinate-set.ts @@ -6,51 +6,51 @@ * @license MIT License */ export class CoordinateSet extends Set { - constructor(joint?: string) { - super(); - if (joint !== undefined) this._joint = joint; - } + constructor(joint?: string) { + super(); + if (joint !== undefined) this._joint = joint; + } - protected _joint: string = '_'; + protected _joint: string = '_'; - get joint(): string { - return this._joint; - } + get joint(): string { + return this._joint; + } - /** - * The "has" function overrides the "has" method of the superclass and checks if a value exists in an array after - * joining its elements with a specified separator. - * @param {number[]} value - The parameter "value" is an array of numbers. - * @returns The overridden `has` method is returning the result of calling the `has` method of the superclass, passing - * in the joined value as an argument. - */ - override has(value: number[]) { - return super.has(value.join(this._joint)); - } + /** + * The "has" function overrides the "has" method of the superclass and checks if a value exists in an array after + * joining its elements with a specified separator. + * @param {number[]} value - The parameter "value" is an array of numbers. + * @returns The overridden `has` method is returning the result of calling the `has` method of the superclass, passing + * in the joined value as an argument. + */ + override has(value: number[]) { + return super.has(value.join(this._joint)); + } - /** - * The "add" function overrides the parent class's "add" function by joining the elements of the input array with a - * specified delimiter before calling the parent class's "add" function. - * @param {number[]} value - An array of numbers - * @returns The overridden `add` method is returning the result of calling the `add` method of the superclass - * (`super.add`) with the joined string representation of the `value` array (`value.join(this._joint)`). - */ - override add(value: number[]) { - return super.add(value.join(this._joint)); - } + /** + * The "add" function overrides the parent class's "add" function by joining the elements of the input array with a + * specified delimiter before calling the parent class's "add" function. + * @param {number[]} value - An array of numbers + * @returns The overridden `add` method is returning the result of calling the `add` method of the superclass + * (`super.add`) with the joined string representation of the `value` array (`value.join(this._joint)`). + */ + override add(value: number[]) { + return super.add(value.join(this._joint)); + } - /** - * The function overrides the delete method and deletes an element from a Set by joining the elements of the input - * array with a specified joint and then calling the delete method of the parent class. - * @param {number[]} value - An array of numbers - * @returns The `delete` method is returning the result of calling the `delete` method of the superclass, with the - * `value` array joined together using the `_joint` property. - */ - override delete(value: number[]) { - return super.delete(value.join(this._joint)); - } + /** + * The function overrides the delete method and deletes an element from a Set by joining the elements of the input + * array with a specified joint and then calling the delete method of the parent class. + * @param {number[]} value - An array of numbers + * @returns The `delete` method is returning the result of calling the `delete` method of the superclass, with the + * `value` array joined together using the `_joint` property. + */ + override delete(value: number[]) { + return super.delete(value.join(this._joint)); + } - protected _setJoint(v: string) { - this._joint = v; - } -} \ No newline at end of file + protected _setJoint(v: string) { + this._joint = v; + } +} diff --git a/src/data-structures/heap/heap.ts b/src/data-structures/heap/heap.ts index a049231..7e85446 100644 --- a/src/data-structures/heap/heap.ts +++ b/src/data-structures/heap/heap.ts @@ -10,191 +10,202 @@ import type {HeapOptions} from '../../types'; export class HeapItem { - /** - * The constructor function initializes an instance of a class with a priority and a value. - * @param {number} priority - The `priority` parameter is a number that represents the priority of the value. It is - * optional and has a default value of `NaN`. - * @param {T | null} [val=null] - The `val` parameter is of type `T | null`, which means it can accept a value of type - * `T` or `null`. - */ - constructor(priority: number = NaN, val: T | null = null) { - this._val = val; - this._priority = priority; - } + /** + * The constructor function initializes an instance of a class with a priority and a value. + * @param {number} priority - The `priority` parameter is a number that represents the priority of the value. It is + * optional and has a default value of `NaN`. + * @param {T | null} [val=null] - The `val` parameter is of type `T | null`, which means it can accept a value of type + * `T` or `null`. + */ + constructor(priority: number = Number.MAX_SAFE_INTEGER, val: T | null = null) { + this._val = val; + this._priority = priority; + } - private _priority: number; + private _priority: number; - get priority(): number { - return this._priority; - } + get priority(): number { + return this._priority; + } - set priority(value: number) { - this._priority = value; - } + set priority(value: number) { + this._priority = value; + } - private _val: T | null; + private _val: T | null; - get val(): T | null { - return this._val; - } + get val(): T | null { + return this._val; + } - set val(value: T | null) { - this._val = value; - } + set val(value: T | null) { + this._val = value; + } } export abstract class Heap { - /** - * The function is a constructor for a class that initializes a priority callback function based on the - * options provided. - * @param [options] - An optional object that contains configuration options for the Heap. - */ - protected constructor(options?: HeapOptions) { - if (options) { - const {priorityExtractor} = options; - if (priorityExtractor !== undefined && typeof priorityExtractor !== 'function') { - throw new Error('.constructor expects a valid priority function'); - } - this._priorityExtractor = priorityExtractor || ((el) => +el); - } else { - this._priorityExtractor = (el) => +el; - } + /** + * The function is a constructor for a class that initializes a priority callback function based on the + * options provided. + * @param [options] - An optional object that contains configuration options for the Heap. + */ + protected constructor(options?: HeapOptions) { + if (options) { + const {priorityExtractor} = options; + if (priorityExtractor !== undefined && typeof priorityExtractor !== 'function') { + throw new Error('.constructor expects a valid priority function'); + } + this._priorityExtractor = priorityExtractor || ((el) => +el); + } else { + this._priorityExtractor = (el) => +el; + } + } + + protected abstract _pq: PriorityQueue>; + + get pq() { + return this._pq; + } + + protected _priorityExtractor: (val: T) => number; + get priorityExtractor() { + return this._priorityExtractor; + } + + /** + * The function returns the size of a priority queue. + * @returns The size of the priority queue. + */ + get size(): number { + return this._pq.size; + } + + /** + * The function checks if a priority queue is empty. + * @returns {boolean} A boolean value indicating whether the size of the priority queue is less than 1. + */ + isEmpty(): boolean { + return this._pq.size < 1; + } + + peek(isItem?: undefined): T | undefined; + peek(isItem: false): T | undefined; + peek(isItem: true): HeapItem | null; + + /** + * The `peek` function returns the top item in the priority queue without removing it. + * @returns The `peek()` method is returning either a `HeapItem` object or `null`.Returns an val with the highest priority in the queue + */ + peek(isItem?: boolean): HeapItem | null | T | undefined { + isItem = isItem ?? false; + const peeked = this._pq.peek(); + + return isItem ? peeked : peeked?.val; + } + + peekLast(isItem?: undefined): T | undefined; + peekLast(isItem: false): T | undefined; + peekLast(isItem: true): HeapItem | null; + + /** + * The `peekLast` function returns the last item in the heap. + * @returns The method `peekLast()` returns either a `HeapItem` object or `null`.Returns an val with the lowest priority in the queue + */ + peekLast(isItem?: boolean): HeapItem | null | T | undefined { + isItem = isItem ?? false; + const leafItem = this._pq.leaf(); + + return isItem ? leafItem : leafItem?.val; + } + + /** + * The `add` function adds an val to a priority queue with an optional priority value. + * @param {T} val - The `val` parameter represents the value that you want to add to the heap. It can be of any + * type. + * @param {number} [priority] - The `priority` parameter is an optional number that represents the priority of the + * val being added to the heap. If the `val` parameter is a number, then the `priority` parameter is set to + * the value of `val`. If the `val` parameter is not a number, then the + * @returns The `add` method returns the instance of the `Heap` class. + * @throws {Error} if priority is not a valid number + */ + add(priority: number, val?: T,): Heap { + val = (val === undefined) ? priority as unknown as T : val; + this._pq.add(new HeapItem(priority, val)); + + return this; + } + + poll(isItem?: undefined): T | undefined; + poll(isItem: false): T | undefined; + poll(isItem: true): HeapItem | null; + + /** + * The `poll` function returns the top item from a priority queue or null if the queue is empty.Removes and returns an val with the highest priority in the queue + * @returns either a HeapItem object or null. + */ + poll(isItem?: boolean): HeapItem | null | T | undefined { + isItem = isItem ?? false; + const top = this._pq.poll(); + if (!top) { + return null; } - protected abstract _pq: PriorityQueue>; + return isItem ? top : top.val; + } - get pq() { - return this._pq; + /** + * The function checks if a given node or value exists in the priority queue. + * @param {T | HeapItem} node - The parameter `node` can be of type `T` or `HeapItem`. + * @returns a boolean value. + */ + has(node: T | HeapItem): boolean { + if (node instanceof HeapItem) { + return this.pq.getNodes().includes(node); + } else { + return this.pq.getNodes().findIndex(item => { + return item.val === node; + }) !== -1; } + } - protected _priorityExtractor: (val: T) => number; - get priorityExtractor() { - return this._priorityExtractor; - } + toArray(isItem?: undefined): (T | undefined)[]; + toArray(isItem: false): (T | undefined)[]; + toArray(isItem: true): (HeapItem | null)[]; - /** - * The function returns the size of a priority queue. - * @returns The size of the priority queue. - */ - get size(): number { - return this._pq.size; - } + /** + * The `toArray` function returns an array of `HeapItem` objects. + * @returns An array of HeapItem objects.Returns a sorted list of vals + */ + toArray(isItem?: boolean): (HeapItem | null | T | undefined)[] { + isItem = isItem ?? false; + const itemArray = this._pq.toArray(); - /** - * The function checks if a priority queue is empty. - * @returns {boolean} A boolean value indicating whether the size of the priority queue is less than 1. - */ - isEmpty(): boolean { - return this._pq.size < 1; - } + return isItem ? itemArray : itemArray.map(item => item.val); + } - peek(isItem?: undefined): T | undefined; - peek(isItem: false): T | undefined; - peek(isItem: true): HeapItem | null; - /** - * The `peek` function returns the top item in the priority queue without removing it. - * @returns The `peek()` method is returning either a `HeapItem` object or `null`.Returns an val with the highest priority in the queue - */ - peek(isItem?: boolean): HeapItem | null | T | undefined { - isItem = isItem ?? false; - const peeked = this._pq.peek(); - return isItem ? peeked : peeked?.val; - } + sort(isItem?: undefined): (T | undefined)[]; + sort(isItem: false): (T | undefined)[]; + sort(isItem: true): (HeapItem | null)[]; - peekLast(isItem?: undefined): T | undefined; - peekLast(isItem: false): T | undefined; - peekLast(isItem: true): HeapItem | null; - /** - * The `peekLast` function returns the last item in the heap. - * @returns The method `peekLast()` returns either a `HeapItem` object or `null`.Returns an val with the lowest priority in the queue - */ - peekLast(isItem?: boolean): HeapItem | null | T | undefined { - isItem = isItem ?? false; - const leafItem = this._pq.leaf(); - return isItem ? leafItem : leafItem?.val; - } + /** + * The function sorts the elements in the priority queue and returns either the sorted items or their values depending + * on the value of the isItem parameter. + * @param {boolean} [isItem] - The `isItem` parameter is a boolean flag that indicates whether the sorted result should + * be an array of `HeapItem` objects or an array of the values (`T`) of those objects. If `isItem` is `true`, the + * sorted result will be an array of `HeapItem + * @returns an array of either `HeapItem`, `null`, `T`, or `undefined` values. + */ + sort(isItem?: boolean): (HeapItem | null | T | undefined)[] { + isItem = isItem ?? false; + const sorted = this._pq.sort(); - /** - * The `add` function adds an val to a priority queue with an optional priority value. - * @param {T} val - The `val` parameter represents the value that you want to add to the heap. It can be of any - * type. - * @param {number} [priority] - The `priority` parameter is an optional number that represents the priority of the - * val being added to the heap. If the `val` parameter is a number, then the `priority` parameter is set to - * the value of `val`. If the `val` parameter is not a number, then the - * @returns The `add` method returns the instance of the `Heap` class. - * @throws {Error} if priority is not a valid number - */ - add(priority: number, val?: T,): Heap { - val = (val === undefined) ? priority as T : val; - this._pq.add(new HeapItem(priority, val)); - return this; - } + return isItem ? sorted : sorted.map(item => item.val); + } - poll(isItem?: undefined): T | undefined; - poll(isItem: false): T | undefined; - poll(isItem: true): HeapItem | null; - /** - * The `poll` function returns the top item from a priority queue or null if the queue is empty.Removes and returns an val with the highest priority in the queue - * @returns either a HeapItem object or null. - */ - poll(isItem?: boolean): HeapItem | null | T | undefined { - isItem = isItem ?? false; - const top = this._pq.poll(); - if (!top) { - return null; - } - return isItem ? top : top.val; - } - - /** - * The function checks if a given node or value exists in the priority queue. - * @param {T | HeapItem} node - The parameter `node` can be of type `T` or `HeapItem`. - * @returns a boolean value. - */ - has(node: T | HeapItem): boolean { - if (node instanceof HeapItem) { - return this.pq.getNodes().includes(node); - } else { - return this.pq.getNodes().findIndex(item => { - return item.val === node; - }) !== -1; - } - } - - toArray(isItem?: undefined): (T | undefined)[]; - toArray(isItem: false): (T | undefined)[]; - toArray(isItem: true): (HeapItem | null)[]; - /** - * The `toArray` function returns an array of `HeapItem` objects. - * @returns An array of HeapItem objects.Returns a sorted list of vals - */ - toArray(isItem?: boolean): (HeapItem | null | T | undefined)[] { - isItem = isItem ?? false; - const itemArray = this._pq.toArray(); - return isItem ? itemArray : itemArray.map(item => item.val); - } - - sort(isItem?: undefined): (T | undefined)[]; - sort(isItem: false): (T | undefined)[]; - sort(isItem: true): (HeapItem | null)[]; - /** - * The function sorts the elements in the priority queue and returns either the sorted items or their values depending - * on the value of the isItem parameter. - * @param {boolean} [isItem] - The `isItem` parameter is a boolean flag that indicates whether the sorted result should - * be an array of `HeapItem` objects or an array of the values (`T`) of those objects. If `isItem` is `true`, the - * sorted result will be an array of `HeapItem - * @returns an array of either `HeapItem`, `null`, `T`, or `undefined` values. - */ - sort(isItem?: boolean): (HeapItem | null | T | undefined)[] { - isItem = isItem ?? false; - const sorted = this._pq.sort(); - return isItem ? sorted : sorted.map(item => item.val); - } - - /** - * The clear function clears the priority queue. - */ - clear(): void { - this._pq.clear(); - } + /** + * The clear function clears the priority queue. + */ + clear(): void { + this._pq.clear(); + } } diff --git a/src/data-structures/heap/max-heap.ts b/src/data-structures/heap/max-heap.ts index 8b747d1..48a72bd 100644 --- a/src/data-structures/heap/max-heap.ts +++ b/src/data-structures/heap/max-heap.ts @@ -15,17 +15,17 @@ import type {HeapOptions} from '../../types'; * @extends Heap */ export class MaxHeap extends Heap { - protected _pq: PriorityQueue>; + protected _pq: PriorityQueue>; - /** - * The constructor initializes a PriorityQueue with a custom comparator function. - * @param [options] - The `options` parameter is an optional object that can be passed to the constructor. It is of - * type `HeapOptions`, which is a generic type that represents the options for the heap. - */ - constructor(options?: HeapOptions) { - super(options); - this._pq = new PriorityQueue>({ - comparator: (a, b) => b.priority - a.priority - }); - } + /** + * The constructor initializes a PriorityQueue with a custom comparator function. + * @param [options] - The `options` parameter is an optional object that can be passed to the constructor. It is of + * type `HeapOptions`, which is a generic type that represents the options for the heap. + */ + 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 index e3b64d0..04c8cc6 100644 --- a/src/data-structures/heap/min-heap.ts +++ b/src/data-structures/heap/min-heap.ts @@ -15,20 +15,20 @@ import type {HeapOptions} from '../../types'; * @extends Heap */ export class MinHeap extends Heap { - protected _pq: PriorityQueue>; + protected _pq: PriorityQueue>; - /** - * The constructor initializes a PriorityQueue with a comparator function that compares the priority of two HeapItem - * objects. - * @param [options] - The `options` parameter is an optional object that can be passed to the constructor. It is of - * type `HeapOptions`, which is a generic type that represents the options for the heap. - */ - constructor(options?: HeapOptions) { - super(options); - this._pq = new PriorityQueue>({ - comparator: (a, b) => a.priority - b.priority - }); - } + /** + * The constructor initializes a PriorityQueue with a comparator function that compares the priority of two HeapItem + * objects. + * @param [options] - The `options` parameter is an optional object that can be passed to the constructor. It is of + * type `HeapOptions`, which is a generic type that represents the options for the heap. + */ + constructor(options?: HeapOptions) { + super(options); + this._pq = new PriorityQueue>({ + comparator: (a, b) => a.priority - b.priority + }); + } } diff --git a/src/data-structures/linked-list/doubly-linked-list.ts b/src/data-structures/linked-list/doubly-linked-list.ts index cc24188..661a4e4 100644 --- a/src/data-structures/linked-list/doubly-linked-list.ts +++ b/src/data-structures/linked-list/doubly-linked-list.ts @@ -1,573 +1,573 @@ -/** - * data-structure-typed - * - * @author Tyler Zeng - * @copyright Copyright (c) 2022 Tyler Zeng - * @license MIT License - */ -export class DoublyLinkedListNode { - - /** - * The constructor function initializes the value, next, and previous properties of an object. - * @param {T} val - The "val" parameter is the value that will be stored in the node. It can be of any data type, as it - * is defined as a generic type "T". - */ - constructor(val: T) { - this._val = val; - this._next = null; - this._prev = null; - } - - private _val: T; - - get val(): T { - return this._val; - } - - set val(value: T) { - this._val = value; - } - - private _next: DoublyLinkedListNode | null; - - get next(): DoublyLinkedListNode | null { - return this._next; - } - - set next(value: DoublyLinkedListNode | null) { - this._next = value; - } - - private _prev: DoublyLinkedListNode | null; - - get prev(): DoublyLinkedListNode | null { - return this._prev; - } - - set prev(value: DoublyLinkedListNode | null) { - this._prev = value; - } -} - -export class DoublyLinkedList { - - /** - * The constructor initializes the linked list with an empty head, tail, and length. - */ - constructor() { - this._head = null; - this._tail = null; - this._length = 0; - } - - private _head: DoublyLinkedListNode | null; - - get head(): DoublyLinkedListNode | null { - return this._head; - } - - set head(value: DoublyLinkedListNode | null) { - this._head = value; - } - - private _tail: DoublyLinkedListNode | null; - - get tail(): DoublyLinkedListNode | null { - return this._tail; - } - - set tail(value: DoublyLinkedListNode | null) { - this._tail = value; - } - - private _length: number; - - get length(): number { - return this._length; - } - - /** - * The `fromArray` function creates a new instance of a DoublyLinkedList and populates it with the elements from the - * given array. - * @param {T[]} data - The `data` parameter is an array of elements of type `T`. - * @returns The `fromArray` function returns a DoublyLinkedList object. - */ - static fromArray(data: T[]) { - const doublyLinkedList = new DoublyLinkedList(); - for (const item of data) { - doublyLinkedList.push(item); - } - return doublyLinkedList; - } - - /** - * The push function adds a new node with the given value to the end of the doubly linked list. - * @param {T} val - The value to be added to the linked list. - */ - push(val: T): void { - const newNode = new DoublyLinkedListNode(val); - if (!this.head) { - this.head = newNode; - this.tail = newNode; - } else { - newNode.prev = this.tail; - this.tail!.next = newNode; - this.tail = newNode; - } - this._length++; - } - - /** - * The `pop()` function removes and returns the value of the last node in a doubly linked list. - * @returns The method is returning the value of the removed node (removedNode.val) if the list is not empty. If the - * list is empty, it returns null. - */ - pop(): T | null { - if (!this.tail) return null; - const removedNode = this.tail; - if (this.head === this.tail) { - this.head = null; - this.tail = null; - } else { - this.tail = removedNode.prev; - this.tail!.next = null; - } - this._length--; - return removedNode.val; - } - - /** - * The `shift()` function removes and returns the value of the first node in a doubly linked list. - * @returns The method `shift()` returns the value of the node that is removed from the beginning of the doubly linked - * list. - */ - shift(): T | null { - if (!this.head) return null; - const removedNode = this.head; - if (this.head === this.tail) { - this.head = null; - this.tail = null; - } else { - this.head = removedNode.next; - this.head!.prev = null; - } - this._length--; - return removedNode.val; - } - - /** - * The unshift function adds a new node with the given value to the beginning of a doubly linked list. - * @param {T} val - The `val` parameter represents the value of the new node that will be added to the beginning of the - * doubly linked list. - */ - unshift(val: T): void { - const newNode = new DoublyLinkedListNode(val); - if (!this.head) { - this.head = newNode; - this.tail = newNode; - } else { - newNode.next = this.head; - this.head!.prev = newNode; - this.head = newNode; - } - this._length++; - } - - /** - * The `getAt` function returns the value at a specified index in a linked list, or null if the index is out of bounds. - * @param {number} index - The index parameter is a number that represents the position of the element we want to - * retrieve from the list. - * @returns The method is returning the value at the specified index in the linked list. If the index is out of bounds - * or the linked list is empty, it will return null. - */ - getAt(index: number): T | null { - if (index < 0 || index >= this.length) return null; - let current = this.head; - for (let i = 0; i < index; i++) { - current = current!.next; - } - return current!.val; - } - - /** - * The function `getNodeAt` returns the node at a given index in a doubly linked list, or null if the index is out of - * range. - * @param {number} index - The `index` parameter is a number that represents the position of the node we want to - * retrieve from the doubly linked list. It indicates the zero-based index of the node we want to access. - * @returns The method `getNodeAt(index: number)` returns a `DoublyLinkedListNode` object if the index is within the - * valid range of the linked list, otherwise it returns `null`. - */ - getNodeAt(index: number): DoublyLinkedListNode | null { - if (index < 0 || index >= this.length) return null; - let current = this.head; - for (let i = 0; i < index; i++) { - current = current!.next; - } - return current; - } - - /** - * The function `findNodeByValue` searches for a node with a specific value in a doubly linked list and returns the - * node if found, otherwise it returns null. - * @param {T} val - The `val` parameter is the value that we want to search for in the doubly linked list. - * @returns The function `findNodeByValue` returns a `DoublyLinkedListNode` if a node with the specified value `val` - * is found in the linked list. If no such node is found, it returns `null`. - */ - findNode(val: T): DoublyLinkedListNode | null { - let current = this.head; - - while (current) { - if (current.val === val) { - return current; - } - current = current.next; - } - - return null; - } - - /** - * The `insert` function inserts a value at a specified index in a doubly linked list. - * @param {number} index - The index parameter represents the position at which the new value should be inserted in the - * DoublyLinkedList. It is of type number. - * @param {T} val - The `val` parameter represents the value that you want to insert into the Doubly Linked List at the - * specified index. - * @returns The `insert` method returns a boolean value. It returns `true` if the insertion is successful, and `false` - * if the index is out of bounds. - */ - insertAt(index: number, val: T): boolean { - if (index < 0 || index > this.length) return false; - if (index === 0) { - this.unshift(val); - return true; - } - if (index === this.length) { - this.push(val); - return true; - } - - const newNode = new DoublyLinkedListNode(val); - const prevNode = this.getNodeAt(index - 1); - const nextNode = prevNode!.next; - newNode.prev = prevNode; - newNode.next = nextNode; - prevNode!.next = newNode; - nextNode!.prev = newNode; - this._length++; - return true; - } - - /** - * The `deleteAt` function removes an element at a specified index from a linked list and returns the removed element. - * @param {number} index - The index parameter represents the position of the element that needs to be deleted in the - * data structure. It is of type number. - * @returns The method `deleteAt` returns the value of the node that was deleted, or `null` if the index is out of - * bounds. - */ - deleteAt(index: number): T | null { - if (index < 0 || index >= this.length) return null; - if (index === 0) return this.shift(); - if (index === this.length - 1) return this.pop(); - - const removedNode = this.getNodeAt(index); - const prevNode = removedNode!.prev; - const nextNode = removedNode!.next; - prevNode!.next = nextNode; - nextNode!.prev = prevNode; - this._length--; - return removedNode!.val; - } - - delete(valOrNode: T): boolean; - delete(valOrNode: DoublyLinkedListNode): boolean; - /** - * The `delete` function removes a node from a doubly linked list based on either the node itself or its value. - * @param {T | DoublyLinkedListNode} valOrNode - The `valOrNode` parameter can accept either a value of type `T` or - * a `DoublyLinkedListNode` object. - * @returns The `delete` method returns a boolean value. It returns `true` if the value or node was successfully - * deleted from the doubly linked list, and `false` if the value or node was not found in the list. - */ - delete(valOrNode: T | DoublyLinkedListNode): boolean { - let node: DoublyLinkedListNode | null; - - if (valOrNode instanceof DoublyLinkedListNode) { - node = valOrNode; - } else { - node = this.findNode(valOrNode); - } - - if (node) { - if (node === this.head) { - this.shift(); - } else if (node === this.tail) { - this.pop(); - } else { - const prevNode = node.prev; - const nextNode = node.next; - prevNode!.next = nextNode; - nextNode!.prev = prevNode; - this._length--; - } - return true; - } - return false; - } - - /** - * The `toArray` function converts a linked list into an array. - * @returns The `toArray()` method is returning an array of type `T[]`. - */ - toArray(): T[] { - const array: T[] = []; - let current = this.head; - while (current) { - array.push(current.val); - current = current.next; - } - return array; - } - - /** - * The `clear` function resets the linked list by setting the head, tail, and length to null and 0 respectively. - */ - clear(): void { - this._head = null; - this._tail = null; - this._length = 0; - } - - /** - * The `find` function iterates through a linked list and returns the first element that satisfies a given condition. - * @param callback - A function that takes a value of type T as its parameter and returns a boolean value. This - * function is used to determine whether a particular value in the linked list satisfies a certain condition. - * @returns The method `find` returns the first element in the linked list that satisfies the condition specified by - * the callback function. If no element satisfies the condition, it returns `null`. - */ - find(callback: (val: T) => boolean): T | null { - let current = this.head; - while (current) { - if (callback(current.val)) { - return current.val; - } - current = current.next; - } - return null; - } - - /** - * The function returns the index of the first occurrence of a given value in a linked list. - * @param {T} val - The parameter `val` is of type `T`, which means it can be any data type. It represents the value - * that we are searching for in the linked list. - * @returns The method `indexOf` returns the index of the first occurrence of the specified value `val` in the linked - * list. If the value is not found, it returns -1. - */ - indexOf(val: T): number { - let index = 0; - let current = this.head; - while (current) { - if (current.val === val) { - return index; - } - index++; - current = current.next; - } - return -1; - } - - /** - * The `findLast` function iterates through a linked list from the last node to the first node and returns the last - * value that satisfies the given callback function, or null if no value satisfies the callback. - * @param callback - A function that takes a value of type T as its parameter and returns a boolean value. This - * function is used to determine whether a given value satisfies a certain condition. - * @returns The method `findLast` returns the last value in the linked list that satisfies the condition specified by - * the callback function. If no value satisfies the condition, it returns `null`. - */ - findLast(callback: (val: T) => boolean): T | null { - let current = this.tail; - while (current) { - if (callback(current.val)) { - return current.val; - } - current = current.prev; - } - return null; - } - - /** - * The `toArrayReverse` function converts a doubly linked list into an array in reverse order. - * @returns The `toArrayReverse()` function returns an array of type `T[]`. - */ - toArrayReverse(): T[] { - const array: T[] = []; - let current = this.tail; - while (current) { - array.push(current.val); - current = current.prev; - } - return array; - } - - /** - * The `reverse` function reverses the order of the elements in a doubly linked list. - */ - reverse(): void { - let current = this.head; - [this.head, this.tail] = [this.tail, this.head]; - while (current) { - const next = current.next; - [current.prev, current.next] = [current.next, current.prev]; - current = next; - } - } - - /** - * The `forEach` function iterates over each element in a linked list and applies a callback function to each element. - * @param callback - The callback parameter is a function that takes two arguments: val and index. The val argument - * represents the value of the current node in the linked list, and the index argument represents the index of the - * current node in the linked list. - */ - forEach(callback: (val: T, index: number) => void): void { - let current = this.head; - let index = 0; - while (current) { - callback(current.val, index); - current = current.next; - index++; - } - } - - /** - * The `map` function takes a callback function and applies it to each element in the DoublyLinkedList, returning a new - * DoublyLinkedList with the transformed values. - * @param callback - The callback parameter is a function that takes a value of type T (the type of values stored in - * the original DoublyLinkedList) and returns a value of type U (the type of values that will be stored in the mapped - * DoublyLinkedList). - * @returns The `map` function is returning a new instance of `DoublyLinkedList` that contains the mapped values. - */ - map(callback: (val: T) => U): DoublyLinkedList { - const mappedList = new DoublyLinkedList(); - let current = this.head; - while (current) { - mappedList.push(callback(current.val)); - current = current.next; - } - return mappedList; - } - - /** - * The `filter` function iterates through a DoublyLinkedList and returns a new DoublyLinkedList containing only the - * elements that satisfy the given callback function. - * @param callback - The `callback` parameter is a function that takes a value of type `T` and returns a boolean value. - * It is used to determine whether a value should be included in the filtered list or not. - * @returns The filtered list, which is an instance of the DoublyLinkedList class. - */ - filter(callback: (val: T) => boolean): DoublyLinkedList { - const filteredList = new DoublyLinkedList(); - let current = this.head; - while (current) { - if (callback(current.val)) { - filteredList.push(current.val); - } - current = current.next; - } - return filteredList; - } - - /** - * The `reduce` function iterates over a linked list and applies a callback function to each element, accumulating a - * single value. - * @param callback - The `callback` parameter is a function that takes two arguments: `accumulator` and `val`. It is - * used to perform a specific operation on each element of the linked list. - * @param {U} initialValue - The `initialValue` parameter is the initial value of the accumulator. It is the starting - * point for the reduction operation. - * @returns The `reduce` method is returning the final value of the accumulator after iterating through all the - * elements in the linked list. - */ - reduce(callback: (accumulator: U, val: T) => U, initialValue: U): U { - let accumulator = initialValue; - let current = this.head; - while (current) { - accumulator = callback(accumulator, current.val); - current = current.next; - } - return accumulator; - } - - insertAfter(existingValueOrNode: T, newValue: T): boolean; - insertAfter(existingValueOrNode: DoublyLinkedListNode, newValue: T): boolean; - /** - * The `insertAfter` function inserts a new node with a given value after an existing node in a doubly linked list. - * @param {T | DoublyLinkedListNode} existingValueOrNode - The existing value or node in the doubly linked list - * after which the new value will be inserted. It can be either the value of the existing node or the existing node - * itself. - * @param {T} newValue - The value that you want to insert into the doubly linked list. - * @returns The method returns a boolean value. It returns true if the insertion is successful, and false if the - * existing value or node is not found in the doubly linked list. - */ - insertAfter(existingValueOrNode: T | DoublyLinkedListNode, newValue: T): boolean { - let existingNode; - - if (existingValueOrNode instanceof DoublyLinkedListNode) { - existingNode = existingValueOrNode; - } else { - existingNode = this.findNode(existingValueOrNode); - } - - if (existingNode) { - const newNode = new DoublyLinkedListNode(newValue); - newNode.next = existingNode.next; - if (existingNode.next) { - existingNode.next.prev = newNode; - } - newNode.prev = existingNode; - existingNode.next = newNode; - if (existingNode === this.tail) { - this.tail = newNode; - } - this._length++; - return true; - } - - return false; - } - - insertBefore(existingValueOrNode: T, newValue: T): boolean; - insertBefore(existingValueOrNode: DoublyLinkedListNode, newValue: T): boolean; - /** - * The `insertBefore` function inserts a new value before an existing value or node in a doubly linked list. - * @param {T | DoublyLinkedListNode} existingValueOrNode - The existing value or node in the doubly linked list - * before which the new value will be inserted. It can be either the value of the existing node or the existing node - * itself. - * @param {T} newValue - The `newValue` parameter represents the value that you want to insert into the doubly linked - * list. - * @returns The method returns a boolean value. It returns `true` if the insertion is successful, and `false` if the - * insertion fails. - */ - insertBefore(existingValueOrNode: T | DoublyLinkedListNode, newValue: T): boolean { - let existingNode; - - if (existingValueOrNode instanceof DoublyLinkedListNode) { - existingNode = existingValueOrNode; - } else { - existingNode = this.findNode(existingValueOrNode); - } - - if (existingNode) { - const newNode = new DoublyLinkedListNode(newValue); - newNode.prev = existingNode.prev; - if (existingNode.prev) { - existingNode.prev.next = newNode; - } - newNode.next = existingNode; - existingNode.prev = newNode; - if (existingNode === this.head) { - this.head = newNode; - } - this._length++; - return true; - } - - return false; - } -} - +/** + * data-structure-typed + * + * @author Tyler Zeng + * @copyright Copyright (c) 2022 Tyler Zeng + * @license MIT License + */ +export class DoublyLinkedListNode { + + /** + * The constructor function initializes the value, next, and previous properties of an object. + * @param {T} val - The "val" parameter is the value that will be stored in the node. It can be of any data type, as it + * is defined as a generic type "T". + */ + constructor(val: T) { + this._val = val; + this._next = null; + this._prev = null; + } + + private _val: T; + + get val(): T { + return this._val; + } + + set val(value: T) { + this._val = value; + } + + private _next: DoublyLinkedListNode | null; + + get next(): DoublyLinkedListNode | null { + return this._next; + } + + set next(value: DoublyLinkedListNode | null) { + this._next = value; + } + + private _prev: DoublyLinkedListNode | null; + + get prev(): DoublyLinkedListNode | null { + return this._prev; + } + + set prev(value: DoublyLinkedListNode | null) { + this._prev = value; + } +} + +export class DoublyLinkedList { + + /** + * The constructor initializes the linked list with an empty head, tail, and length. + */ + constructor() { + this._head = null; + this._tail = null; + this._length = 0; + } + + private _head: DoublyLinkedListNode | null; + + get head(): DoublyLinkedListNode | null { + return this._head; + } + + set head(value: DoublyLinkedListNode | null) { + this._head = value; + } + + private _tail: DoublyLinkedListNode | null; + + get tail(): DoublyLinkedListNode | null { + return this._tail; + } + + set tail(value: DoublyLinkedListNode | null) { + this._tail = value; + } + + private _length: number; + + get length(): number { + return this._length; + } + + /** + * The `fromArray` function creates a new instance of a DoublyLinkedList and populates it with the elements from the + * given array. + * @param {T[]} data - The `data` parameter is an array of elements of type `T`. + * @returns The `fromArray` function returns a DoublyLinkedList object. + */ + static fromArray(data: T[]) { + const doublyLinkedList = new DoublyLinkedList(); + for (const item of data) { + doublyLinkedList.push(item); + } + return doublyLinkedList; + } + + /** + * The push function adds a new node with the given value to the end of the doubly linked list. + * @param {T} val - The value to be added to the linked list. + */ + push(val: T): void { + const newNode = new DoublyLinkedListNode(val); + if (!this.head) { + this.head = newNode; + this.tail = newNode; + } else { + newNode.prev = this.tail; + this.tail!.next = newNode; + this.tail = newNode; + } + this._length++; + } + + /** + * The `pop()` function removes and returns the value of the last node in a doubly linked list. + * @returns The method is returning the value of the removed node (removedNode.val) if the list is not empty. If the + * list is empty, it returns null. + */ + pop(): T | null { + if (!this.tail) return null; + const removedNode = this.tail; + if (this.head === this.tail) { + this.head = null; + this.tail = null; + } else { + this.tail = removedNode.prev; + this.tail!.next = null; + } + this._length--; + return removedNode.val; + } + + /** + * The `shift()` function removes and returns the value of the first node in a doubly linked list. + * @returns The method `shift()` returns the value of the node that is removed from the beginning of the doubly linked + * list. + */ + shift(): T | null { + if (!this.head) return null; + const removedNode = this.head; + if (this.head === this.tail) { + this.head = null; + this.tail = null; + } else { + this.head = removedNode.next; + this.head!.prev = null; + } + this._length--; + return removedNode.val; + } + + /** + * The unshift function adds a new node with the given value to the beginning of a doubly linked list. + * @param {T} val - The `val` parameter represents the value of the new node that will be added to the beginning of the + * doubly linked list. + */ + unshift(val: T): void { + const newNode = new DoublyLinkedListNode(val); + if (!this.head) { + this.head = newNode; + this.tail = newNode; + } else { + newNode.next = this.head; + this.head!.prev = newNode; + this.head = newNode; + } + this._length++; + } + + /** + * The `getAt` function returns the value at a specified index in a linked list, or null if the index is out of bounds. + * @param {number} index - The index parameter is a number that represents the position of the element we want to + * retrieve from the list. + * @returns The method is returning the value at the specified index in the linked list. If the index is out of bounds + * or the linked list is empty, it will return null. + */ + getAt(index: number): T | null { + if (index < 0 || index >= this.length) return null; + let current = this.head; + for (let i = 0; i < index; i++) { + current = current!.next; + } + return current!.val; + } + + /** + * The function `getNodeAt` returns the node at a given index in a doubly linked list, or null if the index is out of + * range. + * @param {number} index - The `index` parameter is a number that represents the position of the node we want to + * retrieve from the doubly linked list. It indicates the zero-based index of the node we want to access. + * @returns The method `getNodeAt(index: number)` returns a `DoublyLinkedListNode` object if the index is within the + * valid range of the linked list, otherwise it returns `null`. + */ + getNodeAt(index: number): DoublyLinkedListNode | null { + if (index < 0 || index >= this.length) return null; + let current = this.head; + for (let i = 0; i < index; i++) { + current = current!.next; + } + return current; + } + + /** + * The function `findNodeByValue` searches for a node with a specific value in a doubly linked list and returns the + * node if found, otherwise it returns null. + * @param {T} val - The `val` parameter is the value that we want to search for in the doubly linked list. + * @returns The function `findNodeByValue` returns a `DoublyLinkedListNode` if a node with the specified value `val` + * is found in the linked list. If no such node is found, it returns `null`. + */ + findNode(val: T): DoublyLinkedListNode | null { + let current = this.head; + + while (current) { + if (current.val === val) { + return current; + } + current = current.next; + } + + return null; + } + + /** + * The `insert` function inserts a value at a specified index in a doubly linked list. + * @param {number} index - The index parameter represents the position at which the new value should be inserted in the + * DoublyLinkedList. It is of type number. + * @param {T} val - The `val` parameter represents the value that you want to insert into the Doubly Linked List at the + * specified index. + * @returns The `insert` method returns a boolean value. It returns `true` if the insertion is successful, and `false` + * if the index is out of bounds. + */ + insertAt(index: number, val: T): boolean { + if (index < 0 || index > this.length) return false; + if (index === 0) { + this.unshift(val); + return true; + } + if (index === this.length) { + this.push(val); + return true; + } + + const newNode = new DoublyLinkedListNode(val); + const prevNode = this.getNodeAt(index - 1); + const nextNode = prevNode!.next; + newNode.prev = prevNode; + newNode.next = nextNode; + prevNode!.next = newNode; + nextNode!.prev = newNode; + this._length++; + return true; + } + + /** + * The `deleteAt` function removes an element at a specified index from a linked list and returns the removed element. + * @param {number} index - The index parameter represents the position of the element that needs to be deleted in the + * data structure. It is of type number. + * @returns The method `deleteAt` returns the value of the node that was deleted, or `null` if the index is out of + * bounds. + */ + deleteAt(index: number): T | null { + if (index < 0 || index >= this.length) return null; + if (index === 0) return this.shift(); + if (index === this.length - 1) return this.pop(); + + const removedNode = this.getNodeAt(index); + const prevNode = removedNode!.prev; + const nextNode = removedNode!.next; + prevNode!.next = nextNode; + nextNode!.prev = prevNode; + this._length--; + return removedNode!.val; + } + + delete(valOrNode: T): boolean; + delete(valOrNode: DoublyLinkedListNode): boolean; + /** + * The `delete` function removes a node from a doubly linked list based on either the node itself or its value. + * @param {T | DoublyLinkedListNode} valOrNode - The `valOrNode` parameter can accept either a value of type `T` or + * a `DoublyLinkedListNode` object. + * @returns The `delete` method returns a boolean value. It returns `true` if the value or node was successfully + * deleted from the doubly linked list, and `false` if the value or node was not found in the list. + */ + delete(valOrNode: T | DoublyLinkedListNode): boolean { + let node: DoublyLinkedListNode | null; + + if (valOrNode instanceof DoublyLinkedListNode) { + node = valOrNode; + } else { + node = this.findNode(valOrNode); + } + + if (node) { + if (node === this.head) { + this.shift(); + } else if (node === this.tail) { + this.pop(); + } else { + const prevNode = node.prev; + const nextNode = node.next; + prevNode!.next = nextNode; + nextNode!.prev = prevNode; + this._length--; + } + return true; + } + return false; + } + + /** + * The `toArray` function converts a linked list into an array. + * @returns The `toArray()` method is returning an array of type `T[]`. + */ + toArray(): T[] { + const array: T[] = []; + let current = this.head; + while (current) { + array.push(current.val); + current = current.next; + } + return array; + } + + /** + * The `clear` function resets the linked list by setting the head, tail, and length to null and 0 respectively. + */ + clear(): void { + this._head = null; + this._tail = null; + this._length = 0; + } + + /** + * The `find` function iterates through a linked list and returns the first element that satisfies a given condition. + * @param callback - A function that takes a value of type T as its parameter and returns a boolean value. This + * function is used to determine whether a particular value in the linked list satisfies a certain condition. + * @returns The method `find` returns the first element in the linked list that satisfies the condition specified by + * the callback function. If no element satisfies the condition, it returns `null`. + */ + find(callback: (val: T) => boolean): T | null { + let current = this.head; + while (current) { + if (callback(current.val)) { + return current.val; + } + current = current.next; + } + return null; + } + + /** + * The function returns the index of the first occurrence of a given value in a linked list. + * @param {T} val - The parameter `val` is of type `T`, which means it can be any data type. It represents the value + * that we are searching for in the linked list. + * @returns The method `indexOf` returns the index of the first occurrence of the specified value `val` in the linked + * list. If the value is not found, it returns -1. + */ + indexOf(val: T): number { + let index = 0; + let current = this.head; + while (current) { + if (current.val === val) { + return index; + } + index++; + current = current.next; + } + return -1; + } + + /** + * The `findLast` function iterates through a linked list from the last node to the first node and returns the last + * value that satisfies the given callback function, or null if no value satisfies the callback. + * @param callback - A function that takes a value of type T as its parameter and returns a boolean value. This + * function is used to determine whether a given value satisfies a certain condition. + * @returns The method `findLast` returns the last value in the linked list that satisfies the condition specified by + * the callback function. If no value satisfies the condition, it returns `null`. + */ + findLast(callback: (val: T) => boolean): T | null { + let current = this.tail; + while (current) { + if (callback(current.val)) { + return current.val; + } + current = current.prev; + } + return null; + } + + /** + * The `toArrayReverse` function converts a doubly linked list into an array in reverse order. + * @returns The `toArrayReverse()` function returns an array of type `T[]`. + */ + toArrayReverse(): T[] { + const array: T[] = []; + let current = this.tail; + while (current) { + array.push(current.val); + current = current.prev; + } + return array; + } + + /** + * The `reverse` function reverses the order of the elements in a doubly linked list. + */ + reverse(): void { + let current = this.head; + [this.head, this.tail] = [this.tail, this.head]; + while (current) { + const next = current.next; + [current.prev, current.next] = [current.next, current.prev]; + current = next; + } + } + + /** + * The `forEach` function iterates over each element in a linked list and applies a callback function to each element. + * @param callback - The callback parameter is a function that takes two arguments: val and index. The val argument + * represents the value of the current node in the linked list, and the index argument represents the index of the + * current node in the linked list. + */ + forEach(callback: (val: T, index: number) => void): void { + let current = this.head; + let index = 0; + while (current) { + callback(current.val, index); + current = current.next; + index++; + } + } + + /** + * The `map` function takes a callback function and applies it to each element in the DoublyLinkedList, returning a new + * DoublyLinkedList with the transformed values. + * @param callback - The callback parameter is a function that takes a value of type T (the type of values stored in + * the original DoublyLinkedList) and returns a value of type U (the type of values that will be stored in the mapped + * DoublyLinkedList). + * @returns The `map` function is returning a new instance of `DoublyLinkedList` that contains the mapped values. + */ + map(callback: (val: T) => U): DoublyLinkedList { + const mappedList = new DoublyLinkedList(); + let current = this.head; + while (current) { + mappedList.push(callback(current.val)); + current = current.next; + } + return mappedList; + } + + /** + * The `filter` function iterates through a DoublyLinkedList and returns a new DoublyLinkedList containing only the + * elements that satisfy the given callback function. + * @param callback - The `callback` parameter is a function that takes a value of type `T` and returns a boolean value. + * It is used to determine whether a value should be included in the filtered list or not. + * @returns The filtered list, which is an instance of the DoublyLinkedList class. + */ + filter(callback: (val: T) => boolean): DoublyLinkedList { + const filteredList = new DoublyLinkedList(); + let current = this.head; + while (current) { + if (callback(current.val)) { + filteredList.push(current.val); + } + current = current.next; + } + return filteredList; + } + + /** + * The `reduce` function iterates over a linked list and applies a callback function to each element, accumulating a + * single value. + * @param callback - The `callback` parameter is a function that takes two arguments: `accumulator` and `val`. It is + * used to perform a specific operation on each element of the linked list. + * @param {U} initialValue - The `initialValue` parameter is the initial value of the accumulator. It is the starting + * point for the reduction operation. + * @returns The `reduce` method is returning the final value of the accumulator after iterating through all the + * elements in the linked list. + */ + reduce(callback: (accumulator: U, val: T) => U, initialValue: U): U { + let accumulator = initialValue; + let current = this.head; + while (current) { + accumulator = callback(accumulator, current.val); + current = current.next; + } + return accumulator; + } + + insertAfter(existingValueOrNode: T, newValue: T): boolean; + insertAfter(existingValueOrNode: DoublyLinkedListNode, newValue: T): boolean; + /** + * The `insertAfter` function inserts a new node with a given value after an existing node in a doubly linked list. + * @param {T | DoublyLinkedListNode} existingValueOrNode - The existing value or node in the doubly linked list + * after which the new value will be inserted. It can be either the value of the existing node or the existing node + * itself. + * @param {T} newValue - The value that you want to insert into the doubly linked list. + * @returns The method returns a boolean value. It returns true if the insertion is successful, and false if the + * existing value or node is not found in the doubly linked list. + */ + insertAfter(existingValueOrNode: T | DoublyLinkedListNode, newValue: T): boolean { + let existingNode; + + if (existingValueOrNode instanceof DoublyLinkedListNode) { + existingNode = existingValueOrNode; + } else { + existingNode = this.findNode(existingValueOrNode); + } + + if (existingNode) { + const newNode = new DoublyLinkedListNode(newValue); + newNode.next = existingNode.next; + if (existingNode.next) { + existingNode.next.prev = newNode; + } + newNode.prev = existingNode; + existingNode.next = newNode; + if (existingNode === this.tail) { + this.tail = newNode; + } + this._length++; + return true; + } + + return false; + } + + insertBefore(existingValueOrNode: T, newValue: T): boolean; + insertBefore(existingValueOrNode: DoublyLinkedListNode, newValue: T): boolean; + /** + * The `insertBefore` function inserts a new value before an existing value or node in a doubly linked list. + * @param {T | DoublyLinkedListNode} existingValueOrNode - The existing value or node in the doubly linked list + * before which the new value will be inserted. It can be either the value of the existing node or the existing node + * itself. + * @param {T} newValue - The `newValue` parameter represents the value that you want to insert into the doubly linked + * list. + * @returns The method returns a boolean value. It returns `true` if the insertion is successful, and `false` if the + * insertion fails. + */ + insertBefore(existingValueOrNode: T | DoublyLinkedListNode, newValue: T): boolean { + let existingNode; + + if (existingValueOrNode instanceof DoublyLinkedListNode) { + existingNode = existingValueOrNode; + } else { + existingNode = this.findNode(existingValueOrNode); + } + + if (existingNode) { + const newNode = new DoublyLinkedListNode(newValue); + newNode.prev = existingNode.prev; + if (existingNode.prev) { + existingNode.prev.next = newNode; + } + newNode.next = existingNode; + existingNode.prev = newNode; + if (existingNode === this.head) { + this.head = newNode; + } + this._length++; + return true; + } + + return false; + } +} + diff --git a/src/data-structures/linked-list/singly-linked-list.ts b/src/data-structures/linked-list/singly-linked-list.ts index 3f1b5b0..1cbbcdd 100644 --- a/src/data-structures/linked-list/singly-linked-list.ts +++ b/src/data-structures/linked-list/singly-linked-list.ts @@ -1,490 +1,490 @@ -/** - * data-structure-typed - * - * @author Tyler Zeng - * @copyright Copyright (c) 2022 Tyler Zeng - * @license MIT License - */ -export class SinglyLinkedListNode { - - /** - * The constructor function initializes an instance of a class with a given value and sets the next property to null. - * @param {T} val - The "val" parameter is of type T, which means it can be any data type. It represents the value that - * will be stored in the node of a linked list. - */ - constructor(val: T) { - this._val = val; - this._next = null; - } - - private _val: T; - - get val(): T { - return this._val; - } - - set val(value: T) { - this._val = value; - } - - private _next: SinglyLinkedListNode | null; - - get next(): SinglyLinkedListNode | null { - return this._next; - } - - set next(value: SinglyLinkedListNode | null) { - this._next = value; - } -} - -export class SinglyLinkedList { - - /** - * The constructor initializes the linked list with an empty head, tail, and length. - */ - constructor() { - this._head = null; - this._tail = null; - this._length = 0; - } - - private _head: SinglyLinkedListNode | null; - - get head(): SinglyLinkedListNode | null { - return this._head; - } - - set head(value: SinglyLinkedListNode | null) { - this._head = value; - } - - private _tail: SinglyLinkedListNode | null; - - get tail(): SinglyLinkedListNode | null { - return this._tail; - } - - set tail(value: SinglyLinkedListNode | null) { - this._tail = value; - } - - private _length: number; - - get length(): number { - return this._length; - } - - /** - * The `fromArray` function creates a new SinglyLinkedList instance and populates it with the elements from the given - * array. - * @param {T[]} data - The `data` parameter is an array of elements of type `T`. - * @returns The `fromArray` function returns a `SinglyLinkedList` object. - */ - static fromArray(data: T[]) { - const singlyLinkedList = new SinglyLinkedList(); - for (const item of data) { - singlyLinkedList.push(item); - } - return singlyLinkedList; - } - - getLength(): number { - return this._length; - } - - /** - * The `push` function adds a new node with the given data to the end of a singly linked list. - * @param {T} data - The "data" parameter represents the value that you want to add to the linked list. It can be of - * any type (T) as specified in the generic type declaration of the class or function. - */ - push(data: T): void { - const newNode = new SinglyLinkedListNode(data); - if (!this.head) { - this.head = newNode; - this.tail = newNode; - } else { - this.tail!.next = newNode; - this.tail = newNode; - } - this._length++; - } - - /** - * The `pop()` function removes and returns the value of the last element in a linked list, updating the head and tail - * pointers accordingly. - * @returns The method `pop()` returns the value of the node that is being removed from the end of the linked list. If - * the linked list is empty, it returns `null`. - */ - pop(): T | null { - if (!this.head) return null; - if (this.head === this.tail) { - const val = this.head.val; - this.head = null; - this.tail = null; - this._length--; - return val; - } - - let current = this.head; - while (current.next !== this.tail) { - current = current.next!; - } - const val = this.tail!.val; - current.next = null; - this.tail = current; - this._length--; - return val; - } - - /** - * The `shift()` function removes and returns the value of the first node in a linked list. - * @returns The value of the node that is being removed from the beginning of the linked list. - */ - shift(): T | null { - if (!this.head) return null; - const removedNode = this.head; - this.head = this.head.next; - this._length--; - return removedNode.val; - } - - /** - * The unshift function adds a new node with the given value to the beginning of a singly linked list. - * @param {T} val - The parameter "val" represents the value of the new node that will be added to the beginning of the - * linked list. - */ - unshift(val: T): void { - const newNode = new SinglyLinkedListNode(val); - if (!this.head) { - this.head = newNode; - this.tail = newNode; - } else { - newNode.next = this.head; - this.head = newNode; - } - this._length++; - } - - /** - * The function `getAt` returns the value at a specified index in a linked list, or null if the index is out of range. - * @param {number} index - The index parameter is a number that represents the position of the element we want to - * retrieve from the list. - * @returns The method `getAt(index: number): T | null` returns the value at the specified index in the linked list, or - * `null` if the index is out of bounds. - */ - getAt(index: number): T | null { - if (index < 0 || index >= this.length) return null; - let current = this.head; - for (let i = 0; i < index; i++) { - current = current!.next; - } - return current!.val; - } - - /** - * The function `getNodeAt` returns the node at a given index in a singly linked list. - * @param {number} index - The `index` parameter is a number that represents the position of the node we want to - * retrieve from the linked list. It indicates the zero-based index of the node we want to access. - * @returns The method `getNodeAt(index: number)` returns a `SinglyLinkedListNode` object if the node at the - * specified index exists, or `null` if the index is out of bounds. - */ - getNodeAt(index: number): SinglyLinkedListNode | null { - let current = this.head; - for (let i = 0; i < index; i++) { - current = current!.next; - } - return current; - } - - /** - * The `deleteAt` function removes an element at a specified index from a linked list and returns the removed element. - * @param {number} index - The index parameter represents the position of the element that needs to be deleted in the - * data structure. It is of type number. - * @returns The method `deleteAt` returns the value of the node that was deleted, or `null` if the index is out of - * bounds. - */ - deleteAt(index: number): T | null { - if (index < 0 || index >= this.length) return null; - if (index === 0) return this.shift(); - if (index === this.length - 1) return this.pop(); - - const prevNode = this.getNodeAt(index - 1); - const removedNode = prevNode!.next; - prevNode!.next = removedNode!.next; - this._length--; - return removedNode!.val; - } - - delete(valueOrNode: T): boolean; - delete(valueOrNode: SinglyLinkedListNode): boolean; - /** - * The delete function removes a node with a specific value from a singly linked list. - * @param {T | SinglyLinkedListNode} valueOrNode - The `valueOrNode` parameter can accept either a value of type `T` - * or a `SinglyLinkedListNode` object. - * @returns The `delete` method returns a boolean value. It returns `true` if the value or node is found and - * successfully deleted from the linked list, and `false` if the value or node is not found in the linked list. - */ - delete(valueOrNode: T | SinglyLinkedListNode): boolean { - let value: T; - if (valueOrNode instanceof SinglyLinkedListNode) { - value = valueOrNode.val; - } else { - value = valueOrNode; - } - let current = this.head, prev = null; - - while (current) { - if (current.val === value) { - if (prev === null) { - this.head = current.next; - if (current === this.tail) { - this.tail = null; - } - } else { - prev.next = current.next; - if (current === this.tail) { - this.tail = prev; - } - } - this._length--; - return true; - } - prev = current; - current = current.next; - } - - return false; - } - - /** - * The `insertAt` function inserts a value at a specified index in a singly linked list. - * @param {number} index - The index parameter represents the position at which the new value should be inserted in the - * linked list. It is of type number. - * @param {T} val - The `val` parameter represents the value that you want to insert into the linked list at the - * specified index. - * @returns The `insert` method returns a boolean value. It returns `true` if the insertion is successful, and `false` - * if the index is out of bounds. - */ - insertAt(index: number, val: T): boolean { - if (index < 0 || index > this.length) return false; - if (index === 0) { - this.unshift(val); - return true; - } - if (index === this.length) { - this.push(val); - return true; - } - - const newNode = new SinglyLinkedListNode(val); - const prevNode = this.getNodeAt(index - 1); - newNode.next = prevNode!.next; - prevNode!.next = newNode; - this._length++; - return true; - } - - /** - * The function checks if the length of a data structure is equal to zero and returns a boolean value indicating - * whether it is empty or not. - * @returns A boolean value indicating whether the length of the object is equal to 0. - */ - isEmpty(): boolean { - return this.length === 0; - } - - /** - * The `clear` function resets the linked list by setting the head, tail, and length to null and 0 respectively. - */ - clear(): void { - this._head = null; - this._tail = null; - this._length = 0; - } - - /** - * The `toArray` function converts a linked list into an array. - * @returns The `toArray()` method is returning an array of type `T[]`. - */ - toArray(): T[] { - const array: T[] = []; - let current = this.head; - while (current) { - array.push(current.val); - current = current.next; - } - return array; - } - - /** - * The `reverse` function reverses the order of the nodes in a singly linked list. - * @returns The reverse() method does not return anything. It has a return type of void. - */ - reverse(): void { - if (!this.head || this.head === this.tail) return; - - let prev: SinglyLinkedListNode | null = null; - let current: SinglyLinkedListNode | null = this.head; - let next: SinglyLinkedListNode | null = null; - - while (current) { - next = current.next; - current.next = prev; - prev = current; - current = next; - } - - [this.head, this.tail] = [this.tail!, this.head!]; - } - - /** - * The `find` function iterates through a linked list and returns the first element that satisfies a given condition. - * @param callback - A function that takes a value of type T as its parameter and returns a boolean value. This - * function is used to determine whether a particular value in the linked list satisfies a certain condition. - * @returns The method `find` returns the first element in the linked list that satisfies the condition specified by - * the callback function. If no element satisfies the condition, it returns `null`. - */ - find(callback: (val: T) => boolean): T | null { - let current = this.head; - while (current) { - if (callback(current.val)) { - return current.val; - } - current = current.next; - } - return null; - } - - /** - * The `indexOf` function returns the index of the first occurrence of a given value in a linked list. - * @param {T} value - The value parameter is the value that you want to find the index of in the linked list. - * @returns The method is returning the index of the first occurrence of the specified value in the linked list. If the - * value is not found, it returns -1. - */ - indexOf(value: T): number { - let index = 0; - let current = this.head; - - while (current) { - if (current.val === value) { - return index; - } - index++; - current = current.next; - } - - return -1; - } - - /** - * The function finds a node in a singly linked list by its value and returns the node if found, otherwise returns - * null. - * @param {T} value - The value parameter is the value that we want to search for in the linked list. - * @returns a `SinglyLinkedListNode` if a node with the specified value is found in the linked list. If no node with - * the specified value is found, the function returns `null`. - */ - findNode(value: T): SinglyLinkedListNode | null { - let current = this.head; - - while (current) { - if (current.val === value) { - return current; - } - current = current.next; - } - - return null; - } - - insertBefore(existingValue: T, newValue: T): boolean - insertBefore(existingValue: SinglyLinkedListNode, newValue: T): boolean - /** - * The `insertBefore` function inserts a new value before an existing value in a singly linked list. - * @param {T | SinglyLinkedListNode} existingValueOrNode - The existing value or node that you want to insert the - * new value before. It can be either the value itself or a node containing the value in the linked list. - * @param {T} newValue - The `newValue` parameter represents the value that you want to insert into the linked list. - * @returns The method `insertBefore` returns a boolean value. It returns `true` if the new value was successfully - * inserted before the existing value, and `false` otherwise. - */ - insertBefore(existingValueOrNode: T | SinglyLinkedListNode, newValue: T): boolean { - if (!this.head) return false; - - let existingValue: T; - if (existingValueOrNode instanceof SinglyLinkedListNode) { - existingValue = existingValueOrNode.val; - } else { - existingValue = existingValueOrNode; - } - if (this.head.val === existingValue) { - this.unshift(newValue); - return true; - } - - let current = this.head; - while (current.next) { - if (current.next.val === existingValue) { - const newNode = new SinglyLinkedListNode(newValue); - newNode.next = current.next; - current.next = newNode; - this._length++; - return true; - } - current = current.next; - } - - return false; - } - - insertAfter(existingValueOrNode: T, newValue: T): boolean - insertAfter(existingValueOrNode: SinglyLinkedListNode, newValue: T): boolean - /** - * The `insertAfter` function inserts a new node with a given value after an existing node in a singly linked list. - * @param {T | SinglyLinkedListNode} existingValueOrNode - The existing value or node in the linked list after which - * the new value will be inserted. It can be either the value of the existing node or the existing node itself. - * @param {T} newValue - The value that you want to insert into the linked list after the existing value or node. - * @returns The method returns a boolean value. It returns true if the new value was successfully inserted after the - * existing value or node, and false if the existing value or node was not found in the linked list. - */ - insertAfter(existingValueOrNode: T | SinglyLinkedListNode, newValue: T): boolean { - let existingNode: T | SinglyLinkedListNode | null; - - if (existingValueOrNode instanceof SinglyLinkedListNode) { - existingNode = existingValueOrNode; - } else { - existingNode = this.findNode(existingValueOrNode); - } - - if (existingNode) { - const newNode = new SinglyLinkedListNode(newValue); - newNode.next = existingNode.next; - existingNode.next = newNode; - if (existingNode === this.tail) { - this.tail = newNode; - } - this._length++; - return true; - } - - return false; - } - - /** - * The function counts the number of occurrences of a given value in a linked list. - * @param {T} value - The value parameter is the value that you want to count the occurrences of in the linked list. - * @returns The count of occurrences of the given value in the linked list. - */ - countOccurrences(value: T): number { - let count = 0; - let current = this.head; - - while (current) { - if (current.val === value) { - count++; - } - current = current.next; - } - - return count; - } -} +/** + * data-structure-typed + * + * @author Tyler Zeng + * @copyright Copyright (c) 2022 Tyler Zeng + * @license MIT License + */ +export class SinglyLinkedListNode { + + /** + * The constructor function initializes an instance of a class with a given value and sets the next property to null. + * @param {T} val - The "val" parameter is of type T, which means it can be any data type. It represents the value that + * will be stored in the node of a linked list. + */ + constructor(val: T) { + this._val = val; + this._next = null; + } + + private _val: T; + + get val(): T { + return this._val; + } + + set val(value: T) { + this._val = value; + } + + private _next: SinglyLinkedListNode | null; + + get next(): SinglyLinkedListNode | null { + return this._next; + } + + set next(value: SinglyLinkedListNode | null) { + this._next = value; + } +} + +export class SinglyLinkedList { + + /** + * The constructor initializes the linked list with an empty head, tail, and length. + */ + constructor() { + this._head = null; + this._tail = null; + this._length = 0; + } + + private _head: SinglyLinkedListNode | null; + + get head(): SinglyLinkedListNode | null { + return this._head; + } + + set head(value: SinglyLinkedListNode | null) { + this._head = value; + } + + private _tail: SinglyLinkedListNode | null; + + get tail(): SinglyLinkedListNode | null { + return this._tail; + } + + set tail(value: SinglyLinkedListNode | null) { + this._tail = value; + } + + private _length: number; + + get length(): number { + return this._length; + } + + /** + * The `fromArray` function creates a new SinglyLinkedList instance and populates it with the elements from the given + * array. + * @param {T[]} data - The `data` parameter is an array of elements of type `T`. + * @returns The `fromArray` function returns a `SinglyLinkedList` object. + */ + static fromArray(data: T[]) { + const singlyLinkedList = new SinglyLinkedList(); + for (const item of data) { + singlyLinkedList.push(item); + } + return singlyLinkedList; + } + + getLength(): number { + return this._length; + } + + /** + * The `push` function adds a new node with the given data to the end of a singly linked list. + * @param {T} data - The "data" parameter represents the value that you want to add to the linked list. It can be of + * any type (T) as specified in the generic type declaration of the class or function. + */ + push(data: T): void { + const newNode = new SinglyLinkedListNode(data); + if (!this.head) { + this.head = newNode; + this.tail = newNode; + } else { + this.tail!.next = newNode; + this.tail = newNode; + } + this._length++; + } + + /** + * The `pop()` function removes and returns the value of the last element in a linked list, updating the head and tail + * pointers accordingly. + * @returns The method `pop()` returns the value of the node that is being removed from the end of the linked list. If + * the linked list is empty, it returns `null`. + */ + pop(): T | null { + if (!this.head) return null; + if (this.head === this.tail) { + const val = this.head.val; + this.head = null; + this.tail = null; + this._length--; + return val; + } + + let current = this.head; + while (current.next !== this.tail) { + current = current.next!; + } + const val = this.tail!.val; + current.next = null; + this.tail = current; + this._length--; + return val; + } + + /** + * The `shift()` function removes and returns the value of the first node in a linked list. + * @returns The value of the node that is being removed from the beginning of the linked list. + */ + shift(): T | null { + if (!this.head) return null; + const removedNode = this.head; + this.head = this.head.next; + this._length--; + return removedNode.val; + } + + /** + * The unshift function adds a new node with the given value to the beginning of a singly linked list. + * @param {T} val - The parameter "val" represents the value of the new node that will be added to the beginning of the + * linked list. + */ + unshift(val: T): void { + const newNode = new SinglyLinkedListNode(val); + if (!this.head) { + this.head = newNode; + this.tail = newNode; + } else { + newNode.next = this.head; + this.head = newNode; + } + this._length++; + } + + /** + * The function `getAt` returns the value at a specified index in a linked list, or null if the index is out of range. + * @param {number} index - The index parameter is a number that represents the position of the element we want to + * retrieve from the list. + * @returns The method `getAt(index: number): T | null` returns the value at the specified index in the linked list, or + * `null` if the index is out of bounds. + */ + getAt(index: number): T | null { + if (index < 0 || index >= this.length) return null; + let current = this.head; + for (let i = 0; i < index; i++) { + current = current!.next; + } + return current!.val; + } + + /** + * The function `getNodeAt` returns the node at a given index in a singly linked list. + * @param {number} index - The `index` parameter is a number that represents the position of the node we want to + * retrieve from the linked list. It indicates the zero-based index of the node we want to access. + * @returns The method `getNodeAt(index: number)` returns a `SinglyLinkedListNode` object if the node at the + * specified index exists, or `null` if the index is out of bounds. + */ + getNodeAt(index: number): SinglyLinkedListNode | null { + let current = this.head; + for (let i = 0; i < index; i++) { + current = current!.next; + } + return current; + } + + /** + * The `deleteAt` function removes an element at a specified index from a linked list and returns the removed element. + * @param {number} index - The index parameter represents the position of the element that needs to be deleted in the + * data structure. It is of type number. + * @returns The method `deleteAt` returns the value of the node that was deleted, or `null` if the index is out of + * bounds. + */ + deleteAt(index: number): T | null { + if (index < 0 || index >= this.length) return null; + if (index === 0) return this.shift(); + if (index === this.length - 1) return this.pop(); + + const prevNode = this.getNodeAt(index - 1); + const removedNode = prevNode!.next; + prevNode!.next = removedNode!.next; + this._length--; + return removedNode!.val; + } + + delete(valueOrNode: T): boolean; + delete(valueOrNode: SinglyLinkedListNode): boolean; + /** + * The delete function removes a node with a specific value from a singly linked list. + * @param {T | SinglyLinkedListNode} valueOrNode - The `valueOrNode` parameter can accept either a value of type `T` + * or a `SinglyLinkedListNode` object. + * @returns The `delete` method returns a boolean value. It returns `true` if the value or node is found and + * successfully deleted from the linked list, and `false` if the value or node is not found in the linked list. + */ + delete(valueOrNode: T | SinglyLinkedListNode): boolean { + let value: T; + if (valueOrNode instanceof SinglyLinkedListNode) { + value = valueOrNode.val; + } else { + value = valueOrNode; + } + let current = this.head, prev = null; + + while (current) { + if (current.val === value) { + if (prev === null) { + this.head = current.next; + if (current === this.tail) { + this.tail = null; + } + } else { + prev.next = current.next; + if (current === this.tail) { + this.tail = prev; + } + } + this._length--; + return true; + } + prev = current; + current = current.next; + } + + return false; + } + + /** + * The `insertAt` function inserts a value at a specified index in a singly linked list. + * @param {number} index - The index parameter represents the position at which the new value should be inserted in the + * linked list. It is of type number. + * @param {T} val - The `val` parameter represents the value that you want to insert into the linked list at the + * specified index. + * @returns The `insert` method returns a boolean value. It returns `true` if the insertion is successful, and `false` + * if the index is out of bounds. + */ + insertAt(index: number, val: T): boolean { + if (index < 0 || index > this.length) return false; + if (index === 0) { + this.unshift(val); + return true; + } + if (index === this.length) { + this.push(val); + return true; + } + + const newNode = new SinglyLinkedListNode(val); + const prevNode = this.getNodeAt(index - 1); + newNode.next = prevNode!.next; + prevNode!.next = newNode; + this._length++; + return true; + } + + /** + * The function checks if the length of a data structure is equal to zero and returns a boolean value indicating + * whether it is empty or not. + * @returns A boolean value indicating whether the length of the object is equal to 0. + */ + isEmpty(): boolean { + return this.length === 0; + } + + /** + * The `clear` function resets the linked list by setting the head, tail, and length to null and 0 respectively. + */ + clear(): void { + this._head = null; + this._tail = null; + this._length = 0; + } + + /** + * The `toArray` function converts a linked list into an array. + * @returns The `toArray()` method is returning an array of type `T[]`. + */ + toArray(): T[] { + const array: T[] = []; + let current = this.head; + while (current) { + array.push(current.val); + current = current.next; + } + return array; + } + + /** + * The `reverse` function reverses the order of the nodes in a singly linked list. + * @returns The reverse() method does not return anything. It has a return type of void. + */ + reverse(): void { + if (!this.head || this.head === this.tail) return; + + let prev: SinglyLinkedListNode | null = null; + let current: SinglyLinkedListNode | null = this.head; + let next: SinglyLinkedListNode | null = null; + + while (current) { + next = current.next; + current.next = prev; + prev = current; + current = next; + } + + [this.head, this.tail] = [this.tail!, this.head!]; + } + + /** + * The `find` function iterates through a linked list and returns the first element that satisfies a given condition. + * @param callback - A function that takes a value of type T as its parameter and returns a boolean value. This + * function is used to determine whether a particular value in the linked list satisfies a certain condition. + * @returns The method `find` returns the first element in the linked list that satisfies the condition specified by + * the callback function. If no element satisfies the condition, it returns `null`. + */ + find(callback: (val: T) => boolean): T | null { + let current = this.head; + while (current) { + if (callback(current.val)) { + return current.val; + } + current = current.next; + } + return null; + } + + /** + * The `indexOf` function returns the index of the first occurrence of a given value in a linked list. + * @param {T} value - The value parameter is the value that you want to find the index of in the linked list. + * @returns The method is returning the index of the first occurrence of the specified value in the linked list. If the + * value is not found, it returns -1. + */ + indexOf(value: T): number { + let index = 0; + let current = this.head; + + while (current) { + if (current.val === value) { + return index; + } + index++; + current = current.next; + } + + return -1; + } + + /** + * The function finds a node in a singly linked list by its value and returns the node if found, otherwise returns + * null. + * @param {T} value - The value parameter is the value that we want to search for in the linked list. + * @returns a `SinglyLinkedListNode` if a node with the specified value is found in the linked list. If no node with + * the specified value is found, the function returns `null`. + */ + findNode(value: T): SinglyLinkedListNode | null { + let current = this.head; + + while (current) { + if (current.val === value) { + return current; + } + current = current.next; + } + + return null; + } + + insertBefore(existingValue: T, newValue: T): boolean + insertBefore(existingValue: SinglyLinkedListNode, newValue: T): boolean + /** + * The `insertBefore` function inserts a new value before an existing value in a singly linked list. + * @param {T | SinglyLinkedListNode} existingValueOrNode - The existing value or node that you want to insert the + * new value before. It can be either the value itself or a node containing the value in the linked list. + * @param {T} newValue - The `newValue` parameter represents the value that you want to insert into the linked list. + * @returns The method `insertBefore` returns a boolean value. It returns `true` if the new value was successfully + * inserted before the existing value, and `false` otherwise. + */ + insertBefore(existingValueOrNode: T | SinglyLinkedListNode, newValue: T): boolean { + if (!this.head) return false; + + let existingValue: T; + if (existingValueOrNode instanceof SinglyLinkedListNode) { + existingValue = existingValueOrNode.val; + } else { + existingValue = existingValueOrNode; + } + if (this.head.val === existingValue) { + this.unshift(newValue); + return true; + } + + let current = this.head; + while (current.next) { + if (current.next.val === existingValue) { + const newNode = new SinglyLinkedListNode(newValue); + newNode.next = current.next; + current.next = newNode; + this._length++; + return true; + } + current = current.next; + } + + return false; + } + + insertAfter(existingValueOrNode: T, newValue: T): boolean + insertAfter(existingValueOrNode: SinglyLinkedListNode, newValue: T): boolean + /** + * The `insertAfter` function inserts a new node with a given value after an existing node in a singly linked list. + * @param {T | SinglyLinkedListNode} existingValueOrNode - The existing value or node in the linked list after which + * the new value will be inserted. It can be either the value of the existing node or the existing node itself. + * @param {T} newValue - The value that you want to insert into the linked list after the existing value or node. + * @returns The method returns a boolean value. It returns true if the new value was successfully inserted after the + * existing value or node, and false if the existing value or node was not found in the linked list. + */ + insertAfter(existingValueOrNode: T | SinglyLinkedListNode, newValue: T): boolean { + let existingNode: T | SinglyLinkedListNode | null; + + if (existingValueOrNode instanceof SinglyLinkedListNode) { + existingNode = existingValueOrNode; + } else { + existingNode = this.findNode(existingValueOrNode); + } + + if (existingNode) { + const newNode = new SinglyLinkedListNode(newValue); + newNode.next = existingNode.next; + existingNode.next = newNode; + if (existingNode === this.tail) { + this.tail = newNode; + } + this._length++; + return true; + } + + return false; + } + + /** + * The function counts the number of occurrences of a given value in a linked list. + * @param {T} value - The value parameter is the value that you want to count the occurrences of in the linked list. + * @returns The count of occurrences of the given value in the linked list. + */ + countOccurrences(value: T): number { + let count = 0; + let current = this.head; + + while (current) { + if (current.val === value) { + count++; + } + current = current.next; + } + + return count; + } +} diff --git a/src/data-structures/matrix/matrix.ts b/src/data-structures/matrix/matrix.ts index e61959e..e3d2e85 100644 --- a/src/data-structures/matrix/matrix.ts +++ b/src/data-structures/matrix/matrix.ts @@ -7,21 +7,21 @@ */ // todo need to be improved export class MatrixNTI2D { - private readonly _matrix: Array>; + private readonly _matrix: Array>; - /** - * The constructor creates a matrix with the specified number of rows and columns, and initializes all elements to a - * given initial value or 0 if not provided. - * @param options - An object containing the following properties: - */ - 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)); - } + /** + * The constructor creates a matrix with the specified number of rows and columns, and initializes all elements to a + * given initial value or 0 if not provided. + * @param options - An object containing the following properties: + */ + 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)); + } - /* The `toArray` method returns the matrix as a two-dimensional array. It converts the internal representation of the - matrix, which is an array of arrays, into a format that is more commonly used in JavaScript. */ - toArray(): Array> { - return this._matrix; - } + /* The `toArray` method returns the matrix as a two-dimensional array. It converts the internal representation of the + matrix, which is an array of arrays, into a format that is more commonly used in JavaScript. */ + toArray(): Array> { + return this._matrix; + } } diff --git a/src/data-structures/matrix/matrix2d.ts b/src/data-structures/matrix/matrix2d.ts index d07a291..0a91330 100644 --- a/src/data-structures/matrix/matrix2d.ts +++ b/src/data-structures/matrix/matrix2d.ts @@ -8,201 +8,201 @@ import Vector2D from './vector2d' export class Matrix2D { - private readonly _matrix: number[][]; + private readonly _matrix: number[][]; - /** - * The constructor function initializes a Matrix2D object with either a default identity matrix, or a provided matrix - * or Vector2D object. - * @param {number[][] | Vector2D} [value] - The `value` parameter can be either a 2D array of numbers (`number[][]`) or - * an instance of the `Vector2D` class. - */ - 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 + /** + * The constructor function initializes a Matrix2D object with either a default identity matrix, or a provided matrix + * or Vector2D object. + * @param {number[][] | Vector2D} [value] - The `value` parameter can be either a 2D array of numbers (`number[][]`) or + * an instance of the `Vector2D` class. + */ + 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 + } + } + + /** + * The function returns a 2D array with three empty arrays. + * @returns An empty 2-dimensional array with 3 empty arrays inside. + */ + static get empty(): number[][] { + return [[], [], []] + } + + /** + * The above function returns a 3x3 identity matrix. + * @returns The method is returning a 2-dimensional array of numbers representing the identity matrix. + */ + static get identity(): number[][] { + return [ + [1, 0, 0], + [0, 1, 0], + [0, 0, 1]] + } + + /** + * The function returns a two-dimensional array of numbers. + * @returns The getter method is returning the value of the private variable `_matrix`, which is a two-dimensional + * array of numbers. + */ + get m(): number[][] { + return this._matrix + } + + /** + * The function "toVector" returns a new Vector2D object with the values from the first and second elements of the + * _matrix array. + * @returns A new instance of the Vector2D class is being returned. The values of the returned vector are taken from + * the first column of the matrix. + */ + get toVector(): Vector2D { + return new Vector2D(this._matrix[0][0], this._matrix[1][0]) + } + + /** + * The function takes two 2D matrices as input and returns their sum as a new 2D matrix. + * @param {Matrix2D} matrix1 - Matrix2D - The first matrix to be added. + * @param {Matrix2D} matrix2 - The parameter `matrix2` is a Matrix2D object. + * @returns a new instance of the Matrix2D class, which is created using the result array. + */ + 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); + } + + /** + * The function subtracts two 2D matrices and returns the result as a new Matrix2D object. + * @param {Matrix2D} matrix1 - Matrix2D - The first matrix to subtract from. + * @param {Matrix2D} matrix2 - Matrix2D is a class representing a 2D matrix. It has a property `m` which is a 2D array + * representing the matrix elements. + * @returns a new instance of the Matrix2D class, which is created using the result array. + */ + 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); + } + + /** + * The function multiplies two 2D matrices and returns the result as a new Matrix2D object. + * @param {Matrix2D} matrix1 - A 2D matrix represented by the Matrix2D class. + * @param {Matrix2D} matrix2 - The parameter `matrix2` is a 2D matrix of size 3x3. + * @returns a new instance of the Matrix2D class, created using the result array. + */ + 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); + } - /** - * The function returns a 2D array with three empty arrays. - * @returns An empty 2-dimensional array with 3 empty arrays inside. - */ - static get empty(): number[][] { - return [[], [], []] + /** + * The function multiplies each element of a 2D matrix by a given value and returns the resulting matrix. + * @param {Matrix2D} matrix - The `matrix` parameter is an instance of the `Matrix2D` class, which represents a 2D + * matrix. It contains a property `m` that is a 2D array representing the matrix elements. + * @param {number} value - The `value` parameter is a number that you want to multiply each element of the `matrix` by. + * @returns a new instance of the Matrix2D class, which is created using the result array. + */ + 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); + } - /** - * The above function returns a 3x3 identity matrix. - * @returns The method is returning a 2-dimensional array of numbers representing the identity matrix. - */ - static get identity(): number[][] { - return [ - [1, 0, 0], - [0, 1, 0], - [0, 0, 1]] - } + /** + * The function multiplies a 2D matrix by a 2D vector and returns the result as a 2D vector. + * @param {Matrix2D} matrix - The parameter "matrix" is of type Matrix2D. It represents a 2-dimensional matrix. + * @param {Vector2D} vector - The "vector" parameter is a 2D vector, represented by an object of type Vector2D. + * @returns a Vector2D. + */ + static multiplyByVector(matrix: Matrix2D, vector: Vector2D): Vector2D { + return Matrix2D.multiply(matrix, new Matrix2D(vector)).toVector + } - /** - * The function returns a two-dimensional array of numbers. - * @returns The getter method is returning the value of the private variable `_matrix`, which is a two-dimensional - * array of numbers. - */ - get m(): number[][] { - return this._matrix - } + /** + * The function returns a 2D matrix that scales and flips a vector around the center of a given width and height. + * @param {number} width - The width parameter represents the width of the view or the canvas. It is a number that + * specifies the width in pixels or any other unit of measurement. + * @param {number} height - The height parameter represents the height of the view or the canvas. It is used to + * calculate the centerY value, which is the vertical center of the view. + * @returns a Matrix2D object. + */ + 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 - /** - * The function "toVector" returns a new Vector2D object with the values from the first and second elements of the - * _matrix array. - * @returns A new instance of the Vector2D class is being returned. The values of the returned vector are taken from - * the first column of the matrix. - */ - get toVector(): Vector2D { - return new Vector2D(this._matrix[0][0], this._matrix[1][0]) - } + return new Matrix2D([ + [scaleStep, 0, centerX], + [0, flipX * scaleStep, centerY], + [0, 0, 1]]) + } - /** - * The function takes two 2D matrices as input and returns their sum as a new 2D matrix. - * @param {Matrix2D} matrix1 - Matrix2D - The first matrix to be added. - * @param {Matrix2D} matrix2 - The parameter `matrix2` is a Matrix2D object. - * @returns a new instance of the Matrix2D class, which is created using the result array. - */ - 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); - } + /** + * The function scales a matrix by a given factor. + * @param {number} factor - The factor parameter is a number that represents the scaling factor by which the matrix + * should be scaled. + * @returns the result of multiplying a new instance of Matrix2D by the given factor. + */ + static scale(factor: number) { + return Matrix2D.multiplyByValue(new Matrix2D(), factor) + } - /** - * The function subtracts two 2D matrices and returns the result as a new Matrix2D object. - * @param {Matrix2D} matrix1 - Matrix2D - The first matrix to subtract from. - * @param {Matrix2D} matrix2 - Matrix2D is a class representing a 2D matrix. It has a property `m` which is a 2D array - * representing the matrix elements. - * @returns a new instance of the Matrix2D class, which is created using the result array. - */ - 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); - } + /** + * The function "rotate" takes an angle in radians and returns a 2D transformation matrix for rotating objects. + * @param {number} radians - The "radians" parameter is the angle in radians by which you want to rotate an object. + * @returns The code is returning a new instance of a Matrix2D object. + */ + static rotate(radians: number) { + const cos = Math.cos(radians) + const sin = Math.sin(radians) - /** - * The function multiplies two 2D matrices and returns the result as a new Matrix2D object. - * @param {Matrix2D} matrix1 - A 2D matrix represented by the Matrix2D class. - * @param {Matrix2D} matrix2 - The parameter `matrix2` is a 2D matrix of size 3x3. - * @returns a new instance of the Matrix2D class, created using the result array. - */ - 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); - } + return new Matrix2D([ + [cos, -sin, 0], + [sin, cos, 0], + [0, 0, 1]]) + } - /** - * The function multiplies each element of a 2D matrix by a given value and returns the resulting matrix. - * @param {Matrix2D} matrix - The `matrix` parameter is an instance of the `Matrix2D` class, which represents a 2D - * matrix. It contains a property `m` that is a 2D array representing the matrix elements. - * @param {number} value - The `value` parameter is a number that you want to multiply each element of the `matrix` by. - * @returns a new instance of the Matrix2D class, which is created using the result array. - */ - 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); - } - - /** - * The function multiplies a 2D matrix by a 2D vector and returns the result as a 2D vector. - * @param {Matrix2D} matrix - The parameter "matrix" is of type Matrix2D. It represents a 2-dimensional matrix. - * @param {Vector2D} vector - The "vector" parameter is a 2D vector, represented by an object of type Vector2D. - * @returns a Vector2D. - */ - static multiplyByVector(matrix: Matrix2D, vector: Vector2D): Vector2D { - return Matrix2D.multiply(matrix, new Matrix2D(vector)).toVector - } - - /** - * The function returns a 2D matrix that scales and flips a vector around the center of a given width and height. - * @param {number} width - The width parameter represents the width of the view or the canvas. It is a number that - * specifies the width in pixels or any other unit of measurement. - * @param {number} height - The height parameter represents the height of the view or the canvas. It is used to - * calculate the centerY value, which is the vertical center of the view. - * @returns a Matrix2D object. - */ - 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]]) - } - - /** - * The function scales a matrix by a given factor. - * @param {number} factor - The factor parameter is a number that represents the scaling factor by which the matrix - * should be scaled. - * @returns the result of multiplying a new instance of Matrix2D by the given factor. - */ - static scale(factor: number) { - return Matrix2D.multiplyByValue(new Matrix2D(), factor) - } - - /** - * The function "rotate" takes an angle in radians and returns a 2D transformation matrix for rotating objects. - * @param {number} radians - The "radians" parameter is the angle in radians by which you want to rotate an object. - * @returns The code is returning a new instance of a Matrix2D object. - */ - 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]]) - } - - /** - * The translate function takes a 2D vector and returns a 2D matrix that represents a translation transformation. - * @param {Vector2D} vector - The parameter "vector" is of type Vector2D. It represents a 2D vector with components x - * and y, and an optional w component. - * @returns The method is returning a new instance of the Matrix2D class. - */ - static translate(vector: Vector2D): Matrix2D { - return new Matrix2D([ - [1, 0, vector.x], - [0, 1, vector.y], - [0, 0, vector.w]]) - } + /** + * The translate function takes a 2D vector and returns a 2D matrix that represents a translation transformation. + * @param {Vector2D} vector - The parameter "vector" is of type Vector2D. It represents a 2D vector with components x + * and y, and an optional w component. + * @returns The method is returning a new instance of the Matrix2D class. + */ + 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 +export default Matrix2D diff --git a/src/data-structures/matrix/navigator.ts b/src/data-structures/matrix/navigator.ts index a76d0ef..a8e94d7 100644 --- a/src/data-structures/matrix/navigator.ts +++ b/src/data-structures/matrix/navigator.ts @@ -8,115 +8,115 @@ import type {Direction, NavigatorParams, Turning} from '../../types'; export class Character { - direction: Direction; - turn: () => Character; + direction: Direction; + turn: () => Character; - /** - * The constructor function takes in a direction and turning object and sets the direction and turn properties of the - * Character class. - * @param {Direction} direction - The direction parameter is used to specify the current direction of the character. It - * can be any value that represents a direction, such as "north", "south", "east", or "west". - * @param {Turning} turning - The `turning` parameter is an object that maps each direction to the corresponding - * turning direction. It is used to determine the new direction when the character turns. - */ - constructor(direction: Direction, turning: Turning) { - this.direction = direction; - this.turn = () => new Character(turning[direction], turning); - } + /** + * The constructor function takes in a direction and turning object and sets the direction and turn properties of the + * Character class. + * @param {Direction} direction - The direction parameter is used to specify the current direction of the character. It + * can be any value that represents a direction, such as "north", "south", "east", or "west". + * @param {Turning} turning - The `turning` parameter is an object that maps each direction to the corresponding + * turning direction. It is used to determine the new direction when the character turns. + */ + constructor(direction: Direction, turning: Turning) { + this.direction = direction; + this.turn = () => new Character(turning[direction], turning); + } } export class Navigator { - onMove: (cur: [number, number]) => void; - private readonly _matrix: T[][]; - private readonly _cur: [number, number]; - private _character: Character; - private readonly _VISITED: T; + onMove: (cur: [number, number]) => void; + private readonly _matrix: T[][]; + private readonly _cur: [number, number]; + private _character: Character; + private readonly _VISITED: T; - /** - * The constructor initializes the Navigator object with the given parameters and sets the current position as visited - * in the matrix. - * @param - - `matrix`: a 2D array representing the grid or map - */ - 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; + /** + * The constructor initializes the Navigator object with the given parameters and sets the current position as visited + * in the matrix. + * @param - - `matrix`: a 2D array representing the grid or map + */ + 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; + } + + /** + * The "start" function moves the character in its current direction until it encounters an obstacle, then it turns the + * character and repeats the process. + */ + 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(); + } + } + } + + /** + * The function checks if there is a valid move in the specified direction in a matrix. + * @param {Direction} direction - The direction parameter is a string that represents the direction in which to check. + * It can be one of the following values: 'up', 'right', 'down', or 'left'. + * @returns a boolean value. + */ + 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; + } + + /** + * The `move` function updates the current position based on the given direction and updates the matrix accordingly. + * @param {Direction} direction - The `direction` parameter is a string that represents the direction in which to move. + * It can have one of the following values: 'up', 'right', 'down', or 'left'. + */ + 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; } - /** - * The "start" function moves the character in its current direction until it encounters an obstacle, then it turns the - * character and repeats the process. - */ - 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(); - } - } - } - - /** - * The function checks if there is a valid move in the specified direction in a matrix. - * @param {Direction} direction - The direction parameter is a string that represents the direction in which to check. - * It can be one of the following values: 'up', 'right', 'down', or 'left'. - * @returns a boolean value. - */ - 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; - } - - /** - * The `move` function updates the current position based on the given direction and updates the matrix accordingly. - * @param {Direction} direction - The `direction` parameter is a string that represents the direction in which to move. - * It can have one of the following values: 'up', 'right', 'down', or 'left'. - */ - 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); - } + 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 index 4d6bdd8..adaed8c 100644 --- a/src/data-structures/matrix/vector2d.ts +++ b/src/data-structures/matrix/vector2d.ts @@ -6,311 +6,311 @@ * @license MIT License */ export class Vector2D { - constructor( - public x: number = 0, - public y: number = 0, - public w: number = 1 // needed for matrix multiplication - ) { + constructor( + public x: number = 0, + public y: number = 0, + public w: number = 1 // needed for matrix multiplication + ) { + } + + /** + * The function checks if the x and y values of a point are both zero. + * @returns A boolean value indicating whether both the x and y properties of the object are equal to 0. + */ + get isZero(): boolean { + return this.x === 0 && this.y === 0 + } + + /** + * The above function calculates the length of a vector using the Pythagorean theorem. + * @returns The length of a vector, calculated using the Pythagorean theorem. + */ + get length(): number { + return Math.sqrt((this.x * this.x) + (this.y * this.y)) + } + + /** + * The function calculates the square of the length of a vector. + * @returns The method is returning the sum of the squares of the x and y values. + */ + get lengthSq(): number { + return (this.x * this.x) + (this.y * this.y) + } + + /** + * The "rounded" function returns a new Vector2D object with the x and y values rounded to the nearest whole number. + * @returns The method is returning a new instance of the Vector2D class with the x and y values rounded to the nearest + * whole number. + */ + get rounded(): Vector2D { + return new Vector2D(Math.round(this.x), Math.round(this.y)) + } + + /** + * The function "add" takes two Vector2D objects as parameters and returns a new Vector2D object with the sum of their + * x and y components. + * @param {Vector2D} vector1 - The parameter `vector1` is an instance of the `Vector2D` class. It represents a + * 2-dimensional vector with an `x` and `y` component. + * @param {Vector2D} vector2 - The parameter "vector2" is of type Vector2D. It represents a 2-dimensional vector with + * an x and y component. + * @returns The method is returning a new instance of the Vector2D class with the x and y components of the two input + * vectors added together. + */ + static add(vector1: Vector2D, vector2: Vector2D): Vector2D { + return new Vector2D(vector1.x + vector2.x, vector1.y + vector2.y) + } + + /** + * The subtract function takes two Vector2D objects as parameters and returns a new Vector2D object with the x and y + * components subtracted. + * @param {Vector2D} vector1 - The parameter `vector1` is an instance of the `Vector2D` class, representing a + * 2-dimensional vector. It has properties `x` and `y` which represent the x and y components of the vector + * respectively. + * @param {Vector2D} vector2 - The parameter "vector2" is a Vector2D object. It represents the second vector that you + * want to subtract from the first vector. + * @returns The method is returning a new Vector2D object with the x and y components subtracted from vector1 and + * vector2. + */ + static subtract(vector1: Vector2D, vector2: Vector2D): Vector2D { + return new Vector2D(vector1.x - vector2.x, vector1.y - vector2.y) + } + + /** + * The function subtracts a given value from the x and y components of a Vector2D object and returns a new Vector2D + * object. + * @param {Vector2D} vector - The parameter "vector" is of type Vector2D, which represents a 2-dimensional vector with + * x and y components. + * @param {number} value - The "value" parameter is a number that will be subtracted from both the x and y components + * of the "vector" parameter. + * @returns A new Vector2D object with the x and y values subtracted by the given value. + */ + static subtractValue(vector: Vector2D, value: number): Vector2D { + return new Vector2D(vector.x - value, vector.y - value) + } + + /** + * The function multiplies a Vector2D object by a given value. + * @param {Vector2D} vector - The parameter "vector" is of type Vector2D, which represents a 2-dimensional vector with + * x and y components. + * @param {number} value - The "value" parameter is a number that represents the value by which the x and y components + * of the vector will be multiplied. + * @returns A new Vector2D object with the x and y values multiplied by the given value. + */ + static multiply(vector: Vector2D, value: number): Vector2D { + return new Vector2D(vector.x * value, vector.y * value) + } + + /** + * The function divides the x and y components of a Vector2D by a given value and returns a new Vector2D. + * @param {Vector2D} vector - The parameter "vector" is of type Vector2D, which represents a 2-dimensional vector with + * x and y components. + * @param {number} value - The value parameter is a number that will be used to divide the x and y components of the + * vector. + * @returns A new instance of the Vector2D class with the x and y values divided by the given value. + */ + static divide(vector: Vector2D, value: number): Vector2D { + return new Vector2D(vector.x / value, vector.y / value) + } + + /** + * The function checks if two Vector2D objects are equal by comparing their x and y values. + * @param {Vector2D} vector1 - The parameter `vector1` is of type `Vector2D`, which represents a 2-dimensional vector. + * It has two properties: `x` and `y`, which represent the x and y components of the vector, respectively. + * @param {Vector2D} vector2 - The parameter "vector2" is of type Vector2D. + * @returns a boolean value, which indicates whether the two input vectors are equal or not. + */ + static equals(vector1: Vector2D, vector2: Vector2D): boolean { + return vector1.x === vector2.x && vector1.y === vector2.y + } + + /** + * The function checks if two Vector2D objects are equal within a specified rounding factor. + * @param {Vector2D} vector1 - The first vector to compare. + * @param {Vector2D} vector2 - The parameter "vector2" is a Vector2D object, which represents a 2-dimensional vector. + * It is used as one of the inputs for the "equalsRounded" function. + * @param [roundingFactor=12] - The roundingFactor parameter is used to determine the threshold for considering two + * vectors as equal. If the absolute difference in the x and y components of the vectors is less than the + * roundingFactor, the vectors are considered equal. + * @returns a boolean value. + */ + 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 } - /** - * The function checks if the x and y values of a point are both zero. - * @returns A boolean value indicating whether both the x and y properties of the object are equal to 0. - */ - get isZero(): boolean { - return this.x === 0 && this.y === 0 + return false + } + + /** + * The normalize function takes a vector as input and returns a normalized version of the vector.Normalizes the vector if it matches a certain condition + * @param {Vector2D} vector - The parameter "vector" is of type Vector2D. + * @returns the normalized vector if its length is greater than a very small value (epsilon), otherwise it returns the + * original vector. + */ + static normalize(vector: Vector2D): Vector2D { + const length = vector.length + if (length > 2.220446049250313e-16) { // Epsilon + return Vector2D.divide(vector, length) } - /** - * The above function calculates the length of a vector using the Pythagorean theorem. - * @returns The length of a vector, calculated using the Pythagorean theorem. - */ - get length(): number { - return Math.sqrt((this.x * this.x) + (this.y * this.y)) + return vector + } + + /** + * The function truncates a vector to a maximum length if it exceeds that length.Adjusts x and y so that the length of the vector does not exceed max + * @param {Vector2D} vector - A 2D vector represented by the Vector2D class. + * @param {number} max - The `max` parameter is a number that represents the maximum length that the `vector` should + * have. + * @returns either the original vector or a truncated version of the vector, depending on whether the length of the + * vector is greater than the maximum value specified. + */ + static truncate(vector: Vector2D, max: number): Vector2D { + if (vector.length > max) { + return Vector2D.multiply(Vector2D.normalize(vector), max) } - /** - * The function calculates the square of the length of a vector. - * @returns The method is returning the sum of the squares of the x and y values. - */ - get lengthSq(): number { - return (this.x * this.x) + (this.y * this.y) + return vector + } + + /** + * The function returns a new Vector2D object that is perpendicular to the input vector.The vector that is perpendicular to this one + * @param {Vector2D} vector - The parameter "vector" is of type Vector2D. + * @returns A new Vector2D object is being returned. + */ + static perp(vector: Vector2D): Vector2D { + return new Vector2D(-vector.y, vector.x) + } + + /** + * The reverse function takes a Vector2D object and returns a new Vector2D object with the negated x and y values. + * @param {Vector2D} vector - The parameter "vector" is of type Vector2D, which represents a 2-dimensional vector. It + * has two properties: "x" and "y", which represent the x and y components of the vector, respectively. + * @returns A new Vector2D object with the negated x and y values of the input vector. Returns the vector that is the reverse of this vector + */ + static reverse(vector: Vector2D): Vector2D { + return new Vector2D(-vector.x, -vector.y) + } + + /** + * The function takes a Vector2D object as input and returns a new Vector2D object with the absolute values of its x + * and y components. + * @param {Vector2D} vector - The parameter "vector" is of type Vector2D, which represents a 2-dimensional vector. It + * has two properties: "x" and "y", which represent the x and y components of the vector, respectively. + * @returns The method is returning a new Vector2D object with the absolute values of the x and y components of the + * input vector. + */ + static abs(vector: Vector2D): Vector2D { + return new Vector2D(Math.abs(vector.x), Math.abs(vector.y)) + } + + /** + * The dot function calculates the dot product of two 2D vectors.The dot product of v1 and v2 + * @param {Vector2D} vector1 - The parameter `vector1` represents a 2D vector with its x and y components. + * @param {Vector2D} vector2 - The "vector2" parameter is a Vector2D object. It represents a two-dimensional vector + * with an x and y component. + * @returns The dot product of the two input vectors. + */ + static dot(vector1: Vector2D, vector2: Vector2D): number { + return (vector1.x * vector2.x) + (vector1.y * vector2.y) + } + + // /** + // * Transform vectors based on the current tranformation matrices: translation, rotation and scale + // * @param vectors The vectors to transform + // */ + // 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 + // */ + // static transformList(vectors: Vector2D[], transformation: Matrix2D): Vector2D[] { + // return vectors.map(vector => Matrix2D.multiplyByVector(transformation, vector)) + // } + + /** + * The function calculates the distance between two points in a two-dimensional space. + * @param {Vector2D} vector1 - The parameter `vector1` represents the first vector in 2D space, while `vector2` + * represents the second vector. Each vector has an `x` and `y` component, which represent their respective coordinates + * in the 2D space. + * @param {Vector2D} vector2 - The `vector2` parameter represents the second vector in the calculation of distance. It + * is an instance of the `Vector2D` class, which typically has properties `x` and `y` representing the coordinates of + * the vector in a 2D space. + * @returns The distance between vector1 and vector2. + */ + 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 function calculates the squared distance between two 2D vectors. + * @param {Vector2D} vector1 - The parameter `vector1` represents the first vector, which is an instance of the + * `Vector2D` class. It contains the x and y coordinates of the vector. + * @param {Vector2D} vector2 - The `vector2` parameter represents the second vector in a two-dimensional space. It has + * properties `x` and `y` which represent the coordinates of the vector. + * @returns the square of the distance between the two input vectors. + */ + static distanceSq(vector1: Vector2D, vector2: Vector2D): number { + const ySeparation = vector2.y - vector1.y + const xSeparation = vector2.x - vector1.x + return (ySeparation * ySeparation) + (xSeparation * xSeparation) + } + + /** + * The sign function determines the sign of the cross product between two 2D vectors. + * (assuming the Y axis is pointing down, X axis to right like a Window app) + * @param {Vector2D} vector1 - The parameter `vector1` is of type `Vector2D`, which represents a 2-dimensional vector. + * It likely has properties `x` and `y` representing the x and y components of the vector, respectively. + * @param {Vector2D} vector2 - The above code defines a function called "sign" that takes two parameters: vector1 and + * vector2. Both vector1 and vector2 are of type Vector2D. + * @returns either -1 or 1. Returns positive if v2 is clockwise of this vector, negative if counterclockwise + */ + static sign(vector1: Vector2D, vector2: Vector2D): number { + if (vector1.y * vector2.x > vector1.x * vector2.y) { + return -1 } - /** - * The "rounded" function returns a new Vector2D object with the x and y values rounded to the nearest whole number. - * @returns The method is returning a new instance of the Vector2D class with the x and y values rounded to the nearest - * whole number. - */ - get rounded(): Vector2D { - return new Vector2D(Math.round(this.x), Math.round(this.y)) - } + return 1 + } - /** - * The function "add" takes two Vector2D objects as parameters and returns a new Vector2D object with the sum of their - * x and y components. - * @param {Vector2D} vector1 - The parameter `vector1` is an instance of the `Vector2D` class. It represents a - * 2-dimensional vector with an `x` and `y` component. - * @param {Vector2D} vector2 - The parameter "vector2" is of type Vector2D. It represents a 2-dimensional vector with - * an x and y component. - * @returns The method is returning a new instance of the Vector2D class with the x and y components of the two input - * vectors added together. - */ - static add(vector1: Vector2D, vector2: Vector2D): Vector2D { - return new Vector2D(vector1.x + vector2.x, vector1.y + vector2.y) - } + /** + * The function calculates the angle between a given vector and the negative y-axis. + * @param {Vector2D} vector - The "vector" parameter is an instance of the Vector2D class, which represents a + * 2-dimensional vector. It has two properties: "x" and "y", which represent the x and y components of the vector, + * respectively. + * @returns the angle between the given vector and the vector (0, -1) in radians.Returns the angle between origin and the given vector in radians + */ + 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 + } - /** - * The subtract function takes two Vector2D objects as parameters and returns a new Vector2D object with the x and y - * components subtracted. - * @param {Vector2D} vector1 - The parameter `vector1` is an instance of the `Vector2D` class, representing a - * 2-dimensional vector. It has properties `x` and `y` which represent the x and y components of the vector - * respectively. - * @param {Vector2D} vector2 - The parameter "vector2" is a Vector2D object. It represents the second vector that you - * want to subtract from the first vector. - * @returns The method is returning a new Vector2D object with the x and y components subtracted from vector1 and - * vector2. - */ - static subtract(vector1: Vector2D, vector2: Vector2D): Vector2D { - return new Vector2D(vector1.x - vector2.x, vector1.y - vector2.y) - } + /** + * The function "random" generates a random Vector2D object with x and y values within the specified range. + * @param {number} maxX - The maxX parameter represents the maximum value for the x-coordinate of the random vector. + * @param {number} maxY - The `maxY` parameter represents the maximum value for the y-coordinate of the generated + * random vector. + * @returns a new instance of the Vector2D class with random x and y values. + */ + 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) + } - /** - * The function subtracts a given value from the x and y components of a Vector2D object and returns a new Vector2D - * object. - * @param {Vector2D} vector - The parameter "vector" is of type Vector2D, which represents a 2-dimensional vector with - * x and y components. - * @param {number} value - The "value" parameter is a number that will be subtracted from both the x and y components - * of the "vector" parameter. - * @returns A new Vector2D object with the x and y values subtracted by the given value. - */ - static subtractValue(vector: Vector2D, value: number): Vector2D { - return new Vector2D(vector.x - value, vector.y - value) - } - - /** - * The function multiplies a Vector2D object by a given value. - * @param {Vector2D} vector - The parameter "vector" is of type Vector2D, which represents a 2-dimensional vector with - * x and y components. - * @param {number} value - The "value" parameter is a number that represents the value by which the x and y components - * of the vector will be multiplied. - * @returns A new Vector2D object with the x and y values multiplied by the given value. - */ - static multiply(vector: Vector2D, value: number): Vector2D { - return new Vector2D(vector.x * value, vector.y * value) - } - - /** - * The function divides the x and y components of a Vector2D by a given value and returns a new Vector2D. - * @param {Vector2D} vector - The parameter "vector" is of type Vector2D, which represents a 2-dimensional vector with - * x and y components. - * @param {number} value - The value parameter is a number that will be used to divide the x and y components of the - * vector. - * @returns A new instance of the Vector2D class with the x and y values divided by the given value. - */ - static divide(vector: Vector2D, value: number): Vector2D { - return new Vector2D(vector.x / value, vector.y / value) - } - - /** - * The function checks if two Vector2D objects are equal by comparing their x and y values. - * @param {Vector2D} vector1 - The parameter `vector1` is of type `Vector2D`, which represents a 2-dimensional vector. - * It has two properties: `x` and `y`, which represent the x and y components of the vector, respectively. - * @param {Vector2D} vector2 - The parameter "vector2" is of type Vector2D. - * @returns a boolean value, which indicates whether the two input vectors are equal or not. - */ - static equals(vector1: Vector2D, vector2: Vector2D): boolean { - return vector1.x === vector2.x && vector1.y === vector2.y - } - - /** - * The function checks if two Vector2D objects are equal within a specified rounding factor. - * @param {Vector2D} vector1 - The first vector to compare. - * @param {Vector2D} vector2 - The parameter "vector2" is a Vector2D object, which represents a 2-dimensional vector. - * It is used as one of the inputs for the "equalsRounded" function. - * @param [roundingFactor=12] - The roundingFactor parameter is used to determine the threshold for considering two - * vectors as equal. If the absolute difference in the x and y components of the vectors is less than the - * roundingFactor, the vectors are considered equal. - * @returns a boolean value. - */ - 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 - } - - /** - * The normalize function takes a vector as input and returns a normalized version of the vector.Normalizes the vector if it matches a certain condition - * @param {Vector2D} vector - The parameter "vector" is of type Vector2D. - * @returns the normalized vector if its length is greater than a very small value (epsilon), otherwise it returns the - * original vector. - */ - static normalize(vector: Vector2D): Vector2D { - const length = vector.length - if (length > 2.220446049250313e-16) { // Epsilon - return Vector2D.divide(vector, length) - } - - return vector - } - - /** - * The function truncates a vector to a maximum length if it exceeds that length.Adjusts x and y so that the length of the vector does not exceed max - * @param {Vector2D} vector - A 2D vector represented by the Vector2D class. - * @param {number} max - The `max` parameter is a number that represents the maximum length that the `vector` should - * have. - * @returns either the original vector or a truncated version of the vector, depending on whether the length of the - * vector is greater than the maximum value specified. - */ - static truncate(vector: Vector2D, max: number): Vector2D { - if (vector.length > max) { - return Vector2D.multiply(Vector2D.normalize(vector), max) - } - - return vector - } - - /** - * The function returns a new Vector2D object that is perpendicular to the input vector.The vector that is perpendicular to this one - * @param {Vector2D} vector - The parameter "vector" is of type Vector2D. - * @returns A new Vector2D object is being returned. - */ - static perp(vector: Vector2D): Vector2D { - return new Vector2D(-vector.y, vector.x) - } - - /** - * The reverse function takes a Vector2D object and returns a new Vector2D object with the negated x and y values. - * @param {Vector2D} vector - The parameter "vector" is of type Vector2D, which represents a 2-dimensional vector. It - * has two properties: "x" and "y", which represent the x and y components of the vector, respectively. - * @returns A new Vector2D object with the negated x and y values of the input vector. Returns the vector that is the reverse of this vector - */ - static reverse(vector: Vector2D): Vector2D { - return new Vector2D(-vector.x, -vector.y) - } - - /** - * The function takes a Vector2D object as input and returns a new Vector2D object with the absolute values of its x - * and y components. - * @param {Vector2D} vector - The parameter "vector" is of type Vector2D, which represents a 2-dimensional vector. It - * has two properties: "x" and "y", which represent the x and y components of the vector, respectively. - * @returns The method is returning a new Vector2D object with the absolute values of the x and y components of the - * input vector. - */ - static abs(vector: Vector2D): Vector2D { - return new Vector2D(Math.abs(vector.x), Math.abs(vector.y)) - } - - /** - * The dot function calculates the dot product of two 2D vectors.The dot product of v1 and v2 - * @param {Vector2D} vector1 - The parameter `vector1` represents a 2D vector with its x and y components. - * @param {Vector2D} vector2 - The "vector2" parameter is a Vector2D object. It represents a two-dimensional vector - * with an x and y component. - * @returns The dot product of the two input vectors. - */ - static dot(vector1: Vector2D, vector2: Vector2D): number { - return (vector1.x * vector2.x) + (vector1.y * vector2.y) - } - - // /** - // * Transform vectors based on the current tranformation matrices: translation, rotation and scale - // * @param vectors The vectors to transform - // */ - // 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 - // */ - // static transformList(vectors: Vector2D[], transformation: Matrix2D): Vector2D[] { - // return vectors.map(vector => Matrix2D.multiplyByVector(transformation, vector)) - // } - - /** - * The function calculates the distance between two points in a two-dimensional space. - * @param {Vector2D} vector1 - The parameter `vector1` represents the first vector in 2D space, while `vector2` - * represents the second vector. Each vector has an `x` and `y` component, which represent their respective coordinates - * in the 2D space. - * @param {Vector2D} vector2 - The `vector2` parameter represents the second vector in the calculation of distance. It - * is an instance of the `Vector2D` class, which typically has properties `x` and `y` representing the coordinates of - * the vector in a 2D space. - * @returns The distance between vector1 and vector2. - */ - 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 function calculates the squared distance between two 2D vectors. - * @param {Vector2D} vector1 - The parameter `vector1` represents the first vector, which is an instance of the - * `Vector2D` class. It contains the x and y coordinates of the vector. - * @param {Vector2D} vector2 - The `vector2` parameter represents the second vector in a two-dimensional space. It has - * properties `x` and `y` which represent the coordinates of the vector. - * @returns the square of the distance between the two input vectors. - */ - static distanceSq(vector1: Vector2D, vector2: Vector2D): number { - const ySeparation = vector2.y - vector1.y - const xSeparation = vector2.x - vector1.x - return (ySeparation * ySeparation) + (xSeparation * xSeparation) - } - - /** - * The sign function determines the sign of the cross product between two 2D vectors. - * (assuming the Y axis is pointing down, X axis to right like a Window app) - * @param {Vector2D} vector1 - The parameter `vector1` is of type `Vector2D`, which represents a 2-dimensional vector. - * It likely has properties `x` and `y` representing the x and y components of the vector, respectively. - * @param {Vector2D} vector2 - The above code defines a function called "sign" that takes two parameters: vector1 and - * vector2. Both vector1 and vector2 are of type Vector2D. - * @returns either -1 or 1. Returns positive if v2 is clockwise of this vector, negative if counterclockwise - */ - static sign(vector1: Vector2D, vector2: Vector2D): number { - if (vector1.y * vector2.x > vector1.x * vector2.y) { - return -1 - } - - return 1 - } - - /** - * The function calculates the angle between a given vector and the negative y-axis. - * @param {Vector2D} vector - The "vector" parameter is an instance of the Vector2D class, which represents a - * 2-dimensional vector. It has two properties: "x" and "y", which represent the x and y components of the vector, - * respectively. - * @returns the angle between the given vector and the vector (0, -1) in radians.Returns the angle between origin and the given vector in radians - */ - 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 - } - - /** - * The function "random" generates a random Vector2D object with x and y values within the specified range. - * @param {number} maxX - The maxX parameter represents the maximum value for the x-coordinate of the random vector. - * @param {number} maxY - The `maxY` parameter represents the maximum value for the y-coordinate of the generated - * random vector. - * @returns a new instance of the Vector2D class with random x and y values. - */ - 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) - } - - /** - * The function sets the values of x and y to zero. - */ - zero(): void { - this.x = 0 - this.y = 0 - } + /** + * The function sets the values of x and y to zero. + */ + zero(): void { + this.x = 0 + this.y = 0 + } } -export default Vector2D \ No newline at end of file +export default Vector2D diff --git a/src/data-structures/priority-queue/max-priority-queue.ts b/src/data-structures/priority-queue/max-priority-queue.ts index 2d3f85b..cbbb86d 100644 --- a/src/data-structures/priority-queue/max-priority-queue.ts +++ b/src/data-structures/priority-queue/max-priority-queue.ts @@ -9,40 +9,40 @@ import {PriorityQueue} from './priority-queue'; import type {PriorityQueueOptions, SpecifyOptional} from '../../types'; export class MaxPriorityQueue extends PriorityQueue { - constructor(options?: Omit, 'comparator'>) - constructor(options: PriorityQueueOptions) - /** - * The constructor initializes a priority queue with an optional comparator function. - * @param [options] - The `options` parameter is an optional object that can contain various properties to configure - * the priority queue. - */ - constructor(options?: SpecifyOptional, 'comparator'>) { - super({ - ...options, - comparator: options?.comparator ? options.comparator : (a: T, b: T) => { - const aKey = a as unknown as number, bKey = b as unknown as number; - return bKey - aKey; - } - }); - } + constructor(options?: Omit, 'comparator'>) + constructor(options: PriorityQueueOptions) + /** + * The constructor initializes a priority queue with an optional comparator function. + * @param [options] - The `options` parameter is an optional object that can contain various properties to configure + * the priority queue. + */ + constructor(options?: SpecifyOptional, 'comparator'>) { + super({ + ...options, + comparator: options?.comparator ? options.comparator : (a: T, b: T) => { + const aKey = a as unknown as number, bKey = b as unknown as number; + return bKey - aKey; + } + }); + } - static override heapify(options?: Omit, 'comparator'>): MaxPriorityQueue - static override heapify(options: PriorityQueueOptions): MaxPriorityQueue - /** - * The function `heapify` creates a max priority queue from the given options and returns it. - * @param options - The `options` parameter is an object that contains configuration options for creating a priority - * queue. It can have the following properties: - * @returns a MaxPriorityQueue object. - */ - static override heapify(options: PriorityQueueOptions): MaxPriorityQueue { - const maxPQ = new MaxPriorityQueue({ - ...options, - comparator: options?.comparator ? options.comparator : (a: T, b: T) => { - const aKey = a as unknown as number, bKey = b as unknown as number; - return bKey - aKey; - } - }); - maxPQ._fix(); - return maxPQ; - } -} \ No newline at end of file + static override heapify(options?: Omit, 'comparator'>): MaxPriorityQueue + static override heapify(options: PriorityQueueOptions): MaxPriorityQueue + /** + * The function `heapify` creates a max priority queue from the given options and returns it. + * @param options - The `options` parameter is an object that contains configuration options for creating a priority + * queue. It can have the following properties: + * @returns a MaxPriorityQueue object. + */ + static override heapify(options: PriorityQueueOptions): MaxPriorityQueue { + const maxPQ = new MaxPriorityQueue({ + ...options, + comparator: options?.comparator ? options.comparator : (a: T, b: T) => { + const aKey = a as unknown as number, bKey = b as unknown as number; + return bKey - aKey; + } + }); + maxPQ._fix(); + return maxPQ; + } +} diff --git a/src/data-structures/priority-queue/min-priority-queue.ts b/src/data-structures/priority-queue/min-priority-queue.ts index dc0d591..817ced2 100644 --- a/src/data-structures/priority-queue/min-priority-queue.ts +++ b/src/data-structures/priority-queue/min-priority-queue.ts @@ -9,41 +9,41 @@ import {PriorityQueue} from './priority-queue'; import type {PriorityQueueOptions, SpecifyOptional} from '../../types'; export class MinPriorityQueue extends PriorityQueue { - constructor(options?: Omit, 'comparator'>) - constructor(options: PriorityQueueOptions) - /** - * The constructor initializes a priority queue with an optional comparator function. - * @param [options] - The `options` parameter is an optional object that can contain various configuration options for - * the `PriorityQueue` constructor. - */ - constructor(options?: SpecifyOptional, 'comparator'>) { - super({ - ...options, - comparator: options?.comparator ? options.comparator : (a: T, b: T) => { - const aKey = a as unknown as number, bKey = b as unknown as number; - return aKey - bKey; - } - }); - } + constructor(options?: Omit, 'comparator'>) + constructor(options: PriorityQueueOptions) + /** + * The constructor initializes a priority queue with an optional comparator function. + * @param [options] - The `options` parameter is an optional object that can contain various configuration options for + * the `PriorityQueue` constructor. + */ + constructor(options?: SpecifyOptional, 'comparator'>) { + super({ + ...options, + comparator: options?.comparator ? options.comparator : (a: T, b: T) => { + const aKey = a as unknown as number, bKey = b as unknown as number; + return aKey - bKey; + } + }); + } - static override heapify(options?: Omit, 'comparator'>): MinPriorityQueue - static override heapify(options: PriorityQueueOptions): MinPriorityQueue - /** - * The function `heapify` creates a new MinPriorityQueue instance and sets the comparator function based on the options - * provided, and then fixes the heap structure of the queue. - * @param options - The `options` parameter is an object that contains configuration options for creating a priority - * queue. It can have the following properties: - * @returns a MinPriorityQueue object. - */ - static override heapify(options: PriorityQueueOptions): MinPriorityQueue { - const minPQ = new MinPriorityQueue({ - ...options, - comparator: options?.comparator ? options.comparator : (a: T, b: T) => { - const aKey = a as unknown as number, bKey = b as unknown as number; - return aKey - bKey; - } - }); - minPQ._fix(); - return minPQ; - } -} \ No newline at end of file + static override heapify(options?: Omit, 'comparator'>): MinPriorityQueue + static override heapify(options: PriorityQueueOptions): MinPriorityQueue + /** + * The function `heapify` creates a new MinPriorityQueue instance and sets the comparator function based on the options + * provided, and then fixes the heap structure of the queue. + * @param options - The `options` parameter is an object that contains configuration options for creating a priority + * queue. It can have the following properties: + * @returns a MinPriorityQueue object. + */ + static override heapify(options: PriorityQueueOptions): MinPriorityQueue { + const minPQ = new MinPriorityQueue({ + ...options, + comparator: options?.comparator ? options.comparator : (a: T, b: T) => { + const aKey = a as unknown as number, bKey = b as unknown as number; + return aKey - bKey; + } + }); + minPQ._fix(); + return minPQ; + } +} diff --git a/src/data-structures/priority-queue/priority-queue.ts b/src/data-structures/priority-queue/priority-queue.ts index 59a8190..eabcaf0 100644 --- a/src/data-structures/priority-queue/priority-queue.ts +++ b/src/data-structures/priority-queue/priority-queue.ts @@ -8,347 +8,347 @@ import type {PriorityQueueComparator, PriorityQueueDFSOrderPattern, PriorityQueueOptions} from '../../types'; export class PriorityQueue { - /** - * The constructor initializes a priority queue with the given options, including an array of nodes and a comparator - * function. - * @param options - The `options` parameter is an object that contains the following properties: - */ - constructor(options: PriorityQueueOptions) { - const {nodes, comparator, isFix = true} = options; - this._comparator = comparator; + /** + * The constructor initializes a priority queue with the given options, including an array of nodes and a comparator + * function. + * @param options - The `options` parameter is an object that contains the following properties: + */ + constructor(options: PriorityQueueOptions) { + const {nodes, comparator, isFix = true} = options; + this._comparator = comparator; - if (nodes && Array.isArray(nodes) && nodes.length > 0) { - // TODO support distinct - this._nodes = [...nodes]; - isFix && this._fix(); - } + if (nodes && Array.isArray(nodes) && nodes.length > 0) { + // TODO support distinct + this._nodes = [...nodes]; + isFix && this._fix(); } + } - protected _nodes: T[] = []; + protected _nodes: T[] = []; - get nodes(): T[] { - return this._nodes; + get nodes(): T[] { + return this._nodes; + } + + get size(): number { + return this.nodes.length; + } + + /** + * The `heapify` function creates a new PriorityQueue instance and fixes the heap property. + * @param options - The "options" parameter is an object that contains the configuration options for the PriorityQueue. + * It can include properties such as "comparator" which specifies the comparison function used to order the elements in + * the priority queue, and "initialValues" which is an array of initial values to be added to the priority + * @returns a new instance of the PriorityQueue class after performing the heapify operation on it. + */ + static heapify(options: PriorityQueueOptions) { + const heap = new PriorityQueue(options); + heap._fix(); + return heap; + } + + /** + * The function checks if a priority queue is valid by creating a new priority queue with a fix option and then calling + * the isValid method. + * @param options - An object containing options for creating a priority queue. The options object should have the + * following properties: + * @returns the result of calling the `isValid()` method on a new instance of the `PriorityQueue` class. + */ + static isPriorityQueueified(options: Omit, 'isFix'>) { + return new PriorityQueue({...options, isFix: false}).isValid(); + } + + /** + * Starting from TypeScript version 5.0 and onwards, the use of distinct access modifiers for Getters and Setters is not permitted. As an alternative, to ensure compatibility, it is necessary to adopt a Java-style approach for Setters (using the same name as the property) while utilizing separate method names for Getters. + */ + getNodes(): T[] { + return this._nodes; + } + + /** + * The "add" function adds a node to the heap and ensures that the heap property is maintained. + * @param {T} node - The parameter "node" is of type T, which means it can be any data type. It represents the node + * that needs to be added to the heap. + */ + add(node: T) { + this.nodes.push(node); + this._heapifyUp(this.size - 1); + } + + /** + * The "has" function checks if a given node is present in the list of nodes. + * @param {T} node - The parameter `node` is of type `T`, which means it can be any type. It represents the node that + * we want to check if it exists in the `nodes` array. + * @returns a boolean value indicating whether the given node is included in the array of nodes. + */ + has(node: T): boolean { + return this.nodes.includes(node); + } + + /** + * The `peek` function returns the first element of the `nodes` array if it exists, otherwise it returns `null`. + * @returns The `peek()` function is returning the first element (`T`) of the `nodes` array if the `size` is not zero. + * Otherwise, it returns `null`. + */ + peek(): T | null { + return this.size ? this.nodes[0] : null; + } + + /** + * The `poll` function removes and returns the top element from a heap data structure. + * @returns The `poll()` method returns a value of type `T` or `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; + } - get size(): number { - return this.nodes.length; + /** + * The `leaf` function returns the last element in the `nodes` array or `null` if the array is empty. + * @returns The method `leaf()` is returning the last element (`T`) in the `nodes` array if it exists. If the array is + * empty or the last element is `null`, then it returns `null`. + */ + leaf(): T | null { + return this.nodes[this.size - 1] ?? null; + } + + /** + * The function checks if the size of an object is equal to zero and returns a boolean value indicating whether the + * object is empty or not. + * @returns The method `isEmpty()` is returning a boolean value indicating whether the size of the object is equal to + * 0. + */ + isEmpty() { + return this.size === 0; + } + + /** + * The clear function clears the nodes array. + */ + clear() { + this._setNodes([]); + } + + /** + * The toArray function returns an array containing all the elements in the nodes property. + * @returns An array of type T, which is the elements of the nodes property. + */ + toArray(): T[] { + return [...this.nodes]; + } + + /** + * The `clone` function returns a new instance of the `PriorityQueue` class with the same nodes and comparator as the + * original instance. + * @returns The `clone()` method is returning a new instance of the `PriorityQueue` class with the same `nodes` and + * `comparator` properties as the original instance. + */ + clone(): PriorityQueue { + return new PriorityQueue({nodes: this.nodes, comparator: this._comparator}); + } + + // --- start additional methods --- + /** + * The `isValid` function recursively checks if a binary tree satisfies a certain condition. + * @returns The function `isValid()` returns a boolean value. + */ + isValid(): boolean { + for (let i = 0; i < this.nodes.length; i++) { + const leftChildIndex = this._getLeft(i); + const rightChildIndex = this._getRight(i); + if (this._isValidIndex(leftChildIndex) && !this._compare(leftChildIndex, i)) { + return false; + } + if (this._isValidIndex(rightChildIndex) && !this._compare(rightChildIndex, i)) { + return false; + } } + return true; + } - /** - * The `heapify` function creates a new PriorityQueue instance and fixes the heap property. - * @param options - The "options" parameter is an object that contains the configuration options for the PriorityQueue. - * It can include properties such as "comparator" which specifies the comparison function used to order the elements in - * the priority queue, and "initialValues" which is an array of initial values to be added to the priority - * @returns a new instance of the PriorityQueue class after performing the heapify operation on it. - */ - static heapify(options: PriorityQueueOptions) { - const heap = new PriorityQueue(options); - heap._fix(); - return heap; + /** + * O(n log n), In scenarios with smaller data sizes, heap sort is generally expected to be slower than QuickSort or MergeSort. + */ + + /** + * The function sorts the elements in a data structure and returns them in an array. + * Plan to support sorting of duplicate elements. + * @returns The `sort()` method is returning an array of type `T[]`. + */ + sort(): T[] { + const visitedNode: T[] = []; + while (this.size !== 0) { + const top = this.poll(); + if (top) visitedNode.push(top); } + return visitedNode; + } - /** - * The function checks if a priority queue is valid by creating a new priority queue with a fix option and then calling - * the isValid method. - * @param options - An object containing options for creating a priority queue. The options object should have the - * following properties: - * @returns the result of calling the `isValid()` method on a new instance of the `PriorityQueue` class. - */ - static isPriorityQueueified(options: Omit, 'isFix'>) { - return new PriorityQueue({...options, isFix: false}).isValid(); - } + /** + * The DFS function performs a depth-first search traversal on a binary tree and returns an array of visited nodes + * based on the specified traversal order. + * @param {PriorityQueueDFSOrderPattern} dfsMode - The dfsMode parameter is a string that specifies the order in which + * the nodes should be visited during the Depth-First Search (DFS) traversal. It can have one of the following values: + * @returns an array of type `(T | null)[]`. + */ + DFS(dfsMode: PriorityQueueDFSOrderPattern): (T | null)[] { + const visitedNode: (T | null)[] = []; - /** - * Starting from TypeScript version 5.0 and onwards, the use of distinct access modifiers for Getters and Setters is not permitted. As an alternative, to ensure compatibility, it is necessary to adopt a Java-style approach for Setters (using the same name as the property) while utilizing separate method names for Getters. - */ - getNodes(): T[] { - return this._nodes; - } - - /** - * The "add" function adds a node to the heap and ensures that the heap property is maintained. - * @param {T} node - The parameter "node" is of type T, which means it can be any data type. It represents the node - * that needs to be added to the heap. - */ - add(node: T) { - this.nodes.push(node); - this._heapifyUp(this.size - 1); - } - - /** - * The "has" function checks if a given node is present in the list of nodes. - * @param {T} node - The parameter `node` is of type `T`, which means it can be any type. It represents the node that - * we want to check if it exists in the `nodes` array. - * @returns a boolean value indicating whether the given node is included in the array of nodes. - */ - has(node: T): boolean { - return this.nodes.includes(node); - } - - /** - * The `peek` function returns the first element of the `nodes` array if it exists, otherwise it returns `null`. - * @returns The `peek()` function is returning the first element (`T`) of the `nodes` array if the `size` is not zero. - * Otherwise, it returns `null`. - */ - peek(): T | null { - return this.size ? this.nodes[0] : null; - } - - /** - * The `poll` function removes and returns the top element from a heap data structure. - * @returns The `poll()` method returns a value of type `T` or `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; - } - - /** - * The `leaf` function returns the last element in the `nodes` array or `null` if the array is empty. - * @returns The method `leaf()` is returning the last element (`T`) in the `nodes` array if it exists. If the array is - * empty or the last element is `null`, then it returns `null`. - */ - leaf(): T | null { - return this.nodes[this.size - 1] ?? null; - } - - /** - * The function checks if the size of an object is equal to zero and returns a boolean value indicating whether the - * object is empty or not. - * @returns The method `isEmpty()` is returning a boolean value indicating whether the size of the object is equal to - * 0. - */ - isEmpty() { - return this.size === 0; - } - - /** - * The clear function clears the nodes array. - */ - clear() { - this._setNodes([]); - } - - /** - * The toArray function returns an array containing all the elements in the nodes property. - * @returns An array of type T, which is the elements of the nodes property. - */ - toArray(): T[] { - return [...this.nodes]; - } - - /** - * The `clone` function returns a new instance of the `PriorityQueue` class with the same nodes and comparator as the - * original instance. - * @returns The `clone()` method is returning a new instance of the `PriorityQueue` class with the same `nodes` and - * `comparator` properties as the original instance. - */ - clone(): PriorityQueue { - return new PriorityQueue({nodes: this.nodes, comparator: this._comparator}); - } - - // --- start additional methods --- - /** - * The `isValid` function recursively checks if a binary tree satisfies a certain condition. - * @returns The function `isValid()` returns a boolean value. - */ - isValid(): boolean { - for (let i = 0; i < this.nodes.length; i++) { - const leftChildIndex = this._getLeft(i); - const rightChildIndex = this._getRight(i); - if (this._isValidIndex(leftChildIndex) && !this._compare(leftChildIndex, i)) { - return false; - } - if (this._isValidIndex(rightChildIndex) && !this._compare(rightChildIndex, i)) { - return false; - } - } - return true; - } - - /** - * O(n log n), In scenarios with smaller data sizes, heap sort is generally expected to be slower than QuickSort or MergeSort. - */ - - /** - * The function sorts the elements in a data structure and returns them in an array. - * Plan to support sorting of duplicate elements. - * @returns The `sort()` method is returning an array of type `T[]`. - */ - sort(): T[] { - const visitedNode: T[] = []; - while (this.size !== 0) { - const top = this.poll(); - if (top) visitedNode.push(top); - } - return visitedNode; - } - - /** - * The DFS function performs a depth-first search traversal on a binary tree and returns an array of visited nodes - * based on the specified traversal order. - * @param {PriorityQueueDFSOrderPattern} dfsMode - The dfsMode parameter is a string that specifies the order in which - * the nodes should be visited during the Depth-First Search (DFS) traversal. It can have one of the following values: - * @returns an array of type `(T | null)[]`. - */ - 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; - } - - protected _setNodes(value: T[]) { - this._nodes = value; - } - - protected readonly _comparator: PriorityQueueComparator = (a: T, b: T) => { - const aKey = a as unknown as number, bKey = b as unknown as number; - return aKey - bKey; + 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; + } }; - /** - * The function compares two numbers using a custom comparator function. - * @param {number} a - The parameter "a" is a number that represents the index of a node in an array. - * @param {number} b - The parameter "b" is a number. - * @returns the result of the comparison between the elements at indices `a` and `b` in the `nodes` array. The - * comparison is done using the `_comparator` function, and if the result is greater than 0, `true` is returned, - * indicating that the element at index `a` is greater than the element at index `b`. - */ - protected _compare(a: number, b: number) { - return this._comparator(this.nodes[a], this.nodes[b]) > 0; + this._isValidIndex(0) && traverse(0); + return visitedNode; + } + + protected _setNodes(value: T[]) { + this._nodes = value; + } + + protected readonly _comparator: PriorityQueueComparator = (a: T, b: T) => { + const aKey = a as unknown as number, bKey = b as unknown as number; + return aKey - bKey; + }; + + /** + * The function compares two numbers using a custom comparator function. + * @param {number} a - The parameter "a" is a number that represents the index of a node in an array. + * @param {number} b - The parameter "b" is a number. + * @returns the result of the comparison between the elements at indices `a` and `b` in the `nodes` array. The + * comparison is done using the `_comparator` function, and if the result is greater than 0, `true` is returned, + * indicating that the element at index `a` is greater than the element at index `b`. + */ + protected _compare(a: number, b: number) { + return this._comparator(this.nodes[a], this.nodes[b]) > 0; + } + + /** + * The function swaps two elements in an array. + * @param {number} a - The parameter "a" is a number that represents the index of an element in an array. + * @param {number} b - The parameter "b" is a number. + */ + protected _swap(a: number, b: number) { + const temp = this.nodes[a]; + this.nodes[a] = this.nodes[b]; + this.nodes[b] = temp; + } + + /** + * The function checks if a given index is valid within an array. + * @param {number} index - The parameter "index" is of type number and represents the index value that needs to be + * checked for validity. + * @returns A boolean value indicating whether the given index is valid or not. + */ + protected _isValidIndex(index: number): boolean { + return index > -1 && index < this.nodes.length; + } + + /** + * The function returns the index of the parent node given the index of a child node in a binary tree. + * @param {number} child - The "child" parameter is a number representing the index of a child node in a binary tree. + * @returns the parent of the given child node. + */ + protected _getParent(child: number): number { + return Math.floor((child - 1) / 2); + } + + /** + * The function returns the index of the left child node in a binary tree given the index of its parent node. + * @param {number} parent - The parameter "parent" is a number that represents the index of a node in a binary tree. + * @returns the left child of a given parent node in a binary tree. + */ + protected _getLeft(parent: number): number { + return (2 * parent) + 1; + } + + /** + * The function returns the index of the right child node in a binary tree given the index of its parent node. + * @param {number} parent - The parameter "parent" is a number that represents the index of a node in a binary tree. + * @returns the right child of a given parent node in a binary tree. + */ + protected _getRight(parent: number): number { + return (2 * parent) + 2; + } + + /** + * The function returns the index of the smallest child node of a given parent node. + * @param {number} parent - The parent parameter is a number that represents the index of the parent node in a binary + * tree. + * @returns the minimum value between the parent node and its left and right child nodes. + */ + 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; } - - /** - * The function swaps two elements in an array. - * @param {number} a - The parameter "a" is a number that represents the index of an element in an array. - * @param {number} b - The parameter "b" is a number. - */ - protected _swap(a: number, b: number) { - const temp = this.nodes[a]; - this.nodes[a] = this.nodes[b]; - this.nodes[b] = temp; + if (right < this.size && this._compare(min, right)) { + min = right; } + return min; + } - /** - * The function checks if a given index is valid within an array. - * @param {number} index - The parameter "index" is of type number and represents the index value that needs to be - * checked for validity. - * @returns A boolean value indicating whether the given index is valid or not. - */ - protected _isValidIndex(index: number): boolean { - return index > -1 && index < this.nodes.length; + /** + * The function `_heapifyUp` is used to maintain the heap property by moving an element up the heap until it is in the + * correct position. + * @param {number} start - The start parameter is the index of the element that needs to be moved up in the heap. + */ + protected _heapifyUp(start: number) { + while (start > 0 && this._compare(this._getParent(start), start)) { + const parent = this._getParent(start); + this._swap(start, parent); + start = parent; } + } - /** - * The function returns the index of the parent node given the index of a child node in a binary tree. - * @param {number} child - The "child" parameter is a number representing the index of a child node in a binary tree. - * @returns the parent of the given child node. - */ - protected _getParent(child: number): number { - return Math.floor((child - 1) / 2); + /** + * The function performs a heapify operation by comparing and swapping elements in a binary heap. + * @param {number} start - The start parameter is the index of the element in the heap from where the heapifyDown + * operation should start. + */ + protected _heapifyDown(start: number) { + let min = this._getComparedChild(start); + while (this._compare(start, min)) { + this._swap(min, start); + start = min; + min = this._getComparedChild(start); } + } - /** - * The function returns the index of the left child node in a binary tree given the index of its parent node. - * @param {number} parent - The parameter "parent" is a number that represents the index of a node in a binary tree. - * @returns the left child of a given parent node in a binary tree. - */ - protected _getLeft(parent: number): number { - return (2 * parent) + 1; - } + /** + * The _fix function performs a heapify operation on the elements of the heap starting from the middle and moving + * towards the root. + */ + protected _fix() { + for (let i = Math.floor(this.size / 2); i > -1; i--) this._heapifyDown(i); + } - /** - * The function returns the index of the right child node in a binary tree given the index of its parent node. - * @param {number} parent - The parameter "parent" is a number that represents the index of a node in a binary tree. - * @returns the right child of a given parent node in a binary tree. - */ - protected _getRight(parent: number): number { - return (2 * parent) + 2; - } - - /** - * The function returns the index of the smallest child node of a given parent node. - * @param {number} parent - The parent parameter is a number that represents the index of the parent node in a binary - * tree. - * @returns the minimum value between the parent node and its left and right child nodes. - */ - 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; - } - - /** - * The function `_heapifyUp` is used to maintain the heap property by moving an element up the heap until it is in the - * correct position. - * @param {number} start - The start parameter is the index of the element that needs to be moved up in the heap. - */ - protected _heapifyUp(start: number) { - while (start > 0 && this._compare(this._getParent(start), start)) { - const parent = this._getParent(start); - this._swap(start, parent); - start = parent; - } - } - - /** - * The function performs a heapify operation by comparing and swapping elements in a binary heap. - * @param {number} start - The start parameter is the index of the element in the heap from where the heapifyDown - * operation should start. - */ - protected _heapifyDown(start: number) { - let min = this._getComparedChild(start); - while (this._compare(start, min)) { - this._swap(min, start); - start = min; - min = this._getComparedChild(start); - } - } - - /** - * The _fix function performs a heapify operation on the elements of the heap starting from the middle and moving - * towards the root. - */ - protected _fix() { - for (let i = Math.floor(this.size / 2); i > -1; i--) this._heapifyDown(i); - } - - // --- end additional methods --- -} \ No newline at end of file + // --- end additional methods --- +} diff --git a/src/data-structures/queue/deque.ts b/src/data-structures/queue/deque.ts index 7db2a5d..fc62a41 100644 --- a/src/data-structures/queue/deque.ts +++ b/src/data-structures/queue/deque.ts @@ -17,235 +17,235 @@ export class Deque extends DoublyLinkedList { // O(n) time complexity of adding at the beginning and the end // todo tested slowest one export class ObjectDeque { - constructor(capacity?: number) { - if (capacity !== undefined) this._capacity = capacity; + constructor(capacity?: number) { + if (capacity !== undefined) this._capacity = capacity; + } + + private _nodes: { [key: number]: T } = {}; + + get nodes(): { [p: number]: T } { + return this._nodes; + } + + private _capacity = Number.MAX_SAFE_INTEGER; + + get capacity(): number { + return this._capacity; + } + + set capacity(value: number) { + this._capacity = value; + } + + private _first: number = -1; + + get first(): number { + return this._first; + } + + set first(value: number) { + this._first = value; + } + + private _last: number = -1; + + get last(): number { + return this._last; + } + + set last(value: number) { + this._last = value; + } + + private _size: number = 0; + + get size(): number { + return this._size; + } + + addFirst(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++; + } - private _nodes: { [key: number]: T } = {}; - - get nodes(): { [p: number]: T } { - return this._nodes; + addLast(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++; + } - private _capacity = Number.MAX_SAFE_INTEGER; + pollFirst() { + if (!this._size) return; + const value = this.peekFirst(); + delete this._nodes[this._first]; + this._first++; + this._size--; + return value; + } - get capacity(): number { - return this._capacity; - } + peekFirst() { + if (this._size) return this._nodes[this._first]; + } - set capacity(value: number) { - this._capacity = value; - } + pollLast() { + if (!this._size) return; + const value = this.peekLast(); + delete this._nodes[this._last]; + this._last--; + this._size--; - private _first: number = -1; + return value; + } - get first(): number { - return this._first; - } + peekLast() { + if (this._size) return this._nodes[this._last]; + } - set first(value: number) { - this._first = value; - } + get(index: number) { + return this._nodes[this._first + index] || null; + } - private _last: number = -1; + isEmpty() { + return this._size <= 0; + } - get last(): number { - return this._last; - } + protected _seNodes(value: { [p: number]: T }) { + this._nodes = value; + } - set last(value: number) { - this._last = value; - } - - private _size: number = 0; - - get size(): number { - return this._size; - } - - addFirst(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++; - } - - addLast(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; - const 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; - const 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; - } - - protected _seNodes(value: { [p: number]: T }) { - this._nodes = value; - } - - protected _setSize(value: number) { - this._size = value; - } + protected _setSize(value: number) { + this._size = value; + } } // 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[] = []; + protected _nodes: T[] = []; - get size() { - return this._nodes.length; - } + get size() { + return this._nodes.length; + } - /** - * The function "addLast" adds a value to the end of an array. - * @param {T} value - The value parameter represents the value that you want to add to the end of the array. - * @returns The return value is the new length of the array after the value has been added. - */ - addLast(value: T) { - return this._nodes.push(value); - } + /** + * The function "addLast" adds a value to the end of an array. + * @param {T} value - The value parameter represents the value that you want to add to the end of the array. + * @returns The return value is the new length of the array after the value has been added. + */ + addLast(value: T) { + return this._nodes.push(value); + } - /** - * The function "pollLast" returns and removes the last element from an array, or returns null if the array is empty. - * @returns The method `pollLast()` returns the last element of the `_nodes` array, or `null` if the array is empty. - */ - pollLast(): T | null { - return this._nodes.pop() ?? null; - } + /** + * The function "pollLast" returns and removes the last element from an array, or returns null if the array is empty. + * @returns The method `pollLast()` returns the last element of the `_nodes` array, or `null` if the array is empty. + */ + pollLast(): T | null { + return this._nodes.pop() ?? null; + } - /** - * The `pollFirst` function removes and returns the first element from an array, or returns null if the array is empty. - * @returns The `pollFirst()` function returns the first element of the `_nodes` array, or `null` if the array is - * empty. - */ - pollFirst(): T | null { - return this._nodes.shift() ?? null; - } + /** + * The `pollFirst` function removes and returns the first element from an array, or returns null if the array is empty. + * @returns The `pollFirst()` function returns the first element of the `_nodes` array, or `null` if the array is + * empty. + */ + pollFirst(): T | null { + return this._nodes.shift() ?? null; + } - /** - * The function "addFirst" adds a value to the beginning of an array. - * @param {T} value - The value parameter represents the value that you want to add to the beginning of the array. - * @returns The return value of the `addFirst` function is the new length of the array `_nodes` after adding the - * `value` at the beginning. - */ - addFirst(value: T) { - return this._nodes.unshift(value); - } + /** + * The function "addFirst" adds a value to the beginning of an array. + * @param {T} value - The value parameter represents the value that you want to add to the beginning of the array. + * @returns The return value of the `addFirst` function is the new length of the array `_nodes` after adding the + * `value` at the beginning. + */ + addFirst(value: T) { + return this._nodes.unshift(value); + } - /** - * The `peekFirst` function returns the first element of an array or null if the array is empty. - * @returns The function `peekFirst()` is returning the first element (`T`) of the `_nodes` array. If the array is - * empty, it will return `null`. - */ - peekFirst(): T | null { - return this._nodes[0] ?? null; - } + /** + * The `peekFirst` function returns the first element of an array or null if the array is empty. + * @returns The function `peekFirst()` is returning the first element (`T`) of the `_nodes` array. If the array is + * empty, it will return `null`. + */ + peekFirst(): T | null { + return this._nodes[0] ?? null; + } - /** - * The `peekLast` function returns the last element of an array or null if the array is empty. - * @returns The method `peekLast()` returns the last element of the `_nodes` array, or `null` if the array is empty. - */ - peekLast(): T | null { - return this._nodes[this._nodes.length - 1] ?? null; - } + /** + * The `peekLast` function returns the last element of an array or null if the array is empty. + * @returns The method `peekLast()` returns the last element of the `_nodes` array, or `null` if the array is empty. + */ + peekLast(): T | null { + return this._nodes[this._nodes.length - 1] ?? null; + } - /** - * The get function returns the element at the specified index in an array, or null if the index is out of bounds. - * @param {number} index - The index parameter is a number that represents the position of the element you want to - * retrieve from the array. - * @returns The method is returning the element at the specified index in the `_nodes` array. If the element exists, it - * will be returned. If the element does not exist (i.e., the index is out of bounds), `null` will be returned. - */ - get(index: number): T | null { - return this._nodes[index] ?? null; - } + /** + * The get function returns the element at the specified index in an array, or null if the index is out of bounds. + * @param {number} index - The index parameter is a number that represents the position of the element you want to + * retrieve from the array. + * @returns The method is returning the element at the specified index in the `_nodes` array. If the element exists, it + * will be returned. If the element does not exist (i.e., the index is out of bounds), `null` will be returned. + */ + get(index: number): T | null { + return this._nodes[index] ?? null; + } - /** - * The set function assigns a value to a specific index in an array. - * @param {number} index - The index parameter is a number that represents the position of the element in the array - * that you want to set a new value for. - * @param {T} value - The value parameter represents the new value that you want to set at the specified index in the - * _nodes array. - * @returns The value that is being set at the specified index in the `_nodes` array. - */ - set(index: number, value: T) { - return this._nodes[index] = value; - } + /** + * The set function assigns a value to a specific index in an array. + * @param {number} index - The index parameter is a number that represents the position of the element in the array + * that you want to set a new value for. + * @param {T} value - The value parameter represents the new value that you want to set at the specified index in the + * _nodes array. + * @returns The value that is being set at the specified index in the `_nodes` array. + */ + set(index: number, value: T) { + return this._nodes[index] = value; + } - /** - * The insert function adds a value at a specified index in an array. - * @param {number} index - The index parameter specifies the position at which the value should be inserted in the - * array. It is a number that represents the index of the array where the value should be inserted. The index starts - * from 0, so the first element of the array has an index of 0, the second element has - * @param {T} value - The value parameter represents the value that you want to insert into the array at the specified - * index. - * @returns The splice method returns an array containing the removed elements, if any. In this case, since no elements - * are being removed, an empty array will be returned. - */ - insert(index: number, value: T) { - return this._nodes.splice(index, 0, value); - } + /** + * The insert function adds a value at a specified index in an array. + * @param {number} index - The index parameter specifies the position at which the value should be inserted in the + * array. It is a number that represents the index of the array where the value should be inserted. The index starts + * from 0, so the first element of the array has an index of 0, the second element has + * @param {T} value - The value parameter represents the value that you want to insert into the array at the specified + * index. + * @returns The splice method returns an array containing the removed elements, if any. In this case, since no elements + * are being removed, an empty array will be returned. + */ + insert(index: number, value: T) { + return this._nodes.splice(index, 0, value); + } - /** - * The remove function removes an element from an array at a specified index. - * @param {number} index - The index parameter specifies the position of the element to be removed from the array. It - * is a number that represents the index of the element to be removed. - * @returns The method is returning an array containing the removed element. - */ - remove(index: number) { - return this._nodes.splice(index, 1); - } + /** + * The remove function removes an element from an array at a specified index. + * @param {number} index - The index parameter specifies the position of the element to be removed from the array. It + * is a number that represents the index of the element to be removed. + * @returns The method is returning an array containing the removed element. + */ + remove(index: number) { + return this._nodes.splice(index, 1); + } - /** - * The function checks if an array called "_nodes" is empty. - * @returns The method `isEmpty()` is returning a boolean value. It returns `true` if the length of the `_nodes` array - * is 0, indicating that the array is empty. Otherwise, it returns `false`. - */ - isEmpty() { - return this._nodes.length === 0; - } -} \ No newline at end of file + /** + * The function checks if an array called "_nodes" is empty. + * @returns The method `isEmpty()` is returning a boolean value. It returns `true` if the length of the `_nodes` array + * is 0, indicating that the array is empty. Otherwise, it returns `false`. + */ + isEmpty() { + return this._nodes.length === 0; + } +} diff --git a/src/data-structures/queue/queue.ts b/src/data-structures/queue/queue.ts index 239127c..de77937 100644 --- a/src/data-structures/queue/queue.ts +++ b/src/data-structures/queue/queue.ts @@ -4,117 +4,117 @@ * @class */ export class Queue { - protected _nodes: T[]; - protected _offset: number; + protected _nodes: T[]; + protected _offset: number; - /** - * The constructor initializes an instance of a class with an optional array of elements and sets the offset to 0. - * @param {T[]} [elements] - The `elements` parameter is an optional array of elements of type `T`. If provided, it - * will be used to initialize the `_nodes` property of the class. If not provided, the `_nodes` property will be - * initialized as an empty array. - */ - constructor(elements?: T[]) { - this._nodes = elements || []; - this._offset = 0; - } + /** + * The constructor initializes an instance of a class with an optional array of elements and sets the offset to 0. + * @param {T[]} [elements] - The `elements` parameter is an optional array of elements of type `T`. If provided, it + * will be used to initialize the `_nodes` property of the class. If not provided, the `_nodes` property will be + * initialized as an empty array. + */ + constructor(elements?: T[]) { + this._nodes = elements || []; + this._offset = 0; + } - /** - * The function "fromArray" creates a new Queue object from an array of elements.Creates a queue from an existing array. - * @public - * @static - * @param {T[]} elements - The "elements" parameter is an array of elements of type T. - * @returns The method is returning a new instance of the Queue class, initialized with the elements from the input - * array. - */ - static fromArray(elements: T[]): Queue { - return new Queue(elements); - } + /** + * The function "fromArray" creates a new Queue object from an array of elements.Creates a queue from an existing array. + * @public + * @static + * @param {T[]} elements - The "elements" parameter is an array of elements of type T. + * @returns The method is returning a new instance of the Queue class, initialized with the elements from the input + * array. + */ + static fromArray(elements: T[]): Queue { + return new Queue(elements); + } - /** - * The add function adds an element to the end of the queue and returns the updated queue.Adds an element at the back of the queue. - * @param {T} element - The `element` parameter represents the element that you want to add to the queue. - * @returns The `add` method is returning a `Queue` object. - */ - add(element: T): Queue { - this._nodes.push(element); - return this; - } + /** + * The add function adds an element to the end of the queue and returns the updated queue.Adds an element at the back of the queue. + * @param {T} element - The `element` parameter represents the element that you want to add to the queue. + * @returns The `add` method is returning a `Queue` object. + */ + add(element: T): Queue { + this._nodes.push(element); + return this; + } - /** - * The `poll` function removes and returns the first element in the queue, and adjusts the internal data structure if - * necessary to optimize performance. - * @returns The function `poll()` returns either the first element in the queue or `null` if the queue is empty. - */ - poll(): T | null { - if (this.size() === 0) return null; + /** + * The `poll` function removes and returns the first element in the queue, and adjusts the internal data structure if + * necessary to optimize performance. + * @returns The function `poll()` returns either the first element in the queue or `null` if the queue is empty. + */ + poll(): T | null { + if (this.size() === 0) return null; - const first = this.peek(); - this._offset += 1; + const first = this.peek(); + this._offset += 1; - if (this._offset * 2 < this._nodes.length) return first; + 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; - } + // 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; + } - /** - * The `peek` function returns the first element of the array `_nodes` if it exists, otherwise it returns `null`. - * @returns The `peek()` method returns the first element of the data structure, represented by the `_nodes` array at - * the `_offset` index. If the data structure is empty (size is 0), it returns `null`. - */ - peek(): T | null { - return this.size() > 0 ? this._nodes[this._offset] : null; - } + /** + * The `peek` function returns the first element of the array `_nodes` if it exists, otherwise it returns `null`. + * @returns The `peek()` method returns the first element of the data structure, represented by the `_nodes` array at + * the `_offset` index. If the data structure is empty (size is 0), it returns `null`. + */ + peek(): T | null { + return this.size() > 0 ? this._nodes[this._offset] : null; + } - /** - * The `peekLast` function returns the last element in an array-like data structure, or null if the structure is empty. - * @returns The method `peekLast()` returns the last element of the `_nodes` array if the array is not empty. If the - * array is empty, it returns `null`. - */ - peekLast(): T | null { - return this.size() > 0 ? this._nodes[this._nodes.length - 1] : null; - } + /** + * The `peekLast` function returns the last element in an array-like data structure, or null if the structure is empty. + * @returns The method `peekLast()` returns the last element of the `_nodes` array if the array is not empty. If the + * array is empty, it returns `null`. + */ + peekLast(): T | null { + return this.size() > 0 ? this._nodes[this._nodes.length - 1] : null; + } - /** - * The size function returns the number of elements in an array. - * @returns {number} The size of the array, which is the difference between the length of the array and the offset. - */ - size(): number { - return this._nodes.length - this._offset; - } + /** + * The size function returns the number of elements in an array. + * @returns {number} The size of the array, which is the difference between the length of the array and the offset. + */ + size(): number { + return this._nodes.length - this._offset; + } - /** - * The function checks if a data structure is empty by comparing its size to zero. - * @returns {boolean} A boolean value indicating whether the size of the object is 0 or not. - */ - isEmpty(): boolean { - return this.size() === 0; - } + /** + * The function checks if a data structure is empty by comparing its size to zero. + * @returns {boolean} A boolean value indicating whether the size of the object is 0 or not. + */ + isEmpty(): boolean { + return this.size() === 0; + } - /** - * The toArray() function returns an array of elements from the current offset to the end of the _nodes array. - * @returns An array of type T is being returned. - */ - toArray(): T[] { - return this._nodes.slice(this._offset); - } + /** + * The toArray() function returns an array of elements from the current offset to the end of the _nodes array. + * @returns An array of type T is being returned. + */ + toArray(): T[] { + return this._nodes.slice(this._offset); + } - /** - * The clear function resets the nodes array and offset to their initial values. - */ - clear(): void { - this._nodes = []; - this._offset = 0; - } + /** + * The clear function resets the nodes array and offset to their initial values. + */ + clear(): void { + this._nodes = []; + this._offset = 0; + } - /** - * The `clone()` function returns a new Queue object with the same elements as the original Queue. - * @returns The `clone()` method is returning a new instance of the `Queue` class. - */ - clone(): Queue { - return new Queue(this._nodes.slice(this._offset)); - } + /** + * The `clone()` function returns a new Queue object with the same elements as the original Queue. + * @returns The `clone()` method is returning a new instance of the `Queue` class. + */ + clone(): Queue { + return new Queue(this._nodes.slice(this._offset)); + } } diff --git a/src/data-structures/stack/stack.ts b/src/data-structures/stack/stack.ts index e2f54ba..be48efd 100644 --- a/src/data-structures/stack/stack.ts +++ b/src/data-structures/stack/stack.ts @@ -4,95 +4,95 @@ * @class */ export class Stack { - protected _elements: T[]; + protected _elements: T[]; - /** - * The constructor initializes an array of elements, which can be provided as an optional parameter. - * @param {T[]} [elements] - The `elements` parameter is an optional parameter of type `T[]`, which represents an array - * of elements of type `T`. It is used to initialize the `_elements` property of the class. If the `elements` parameter - * is provided and is an array, it is assigned to the `_elements - */ - constructor(elements?: T[]) { - this._elements = Array.isArray(elements) ? elements : []; - } + /** + * The constructor initializes an array of elements, which can be provided as an optional parameter. + * @param {T[]} [elements] - The `elements` parameter is an optional parameter of type `T[]`, which represents an array + * of elements of type `T`. It is used to initialize the `_elements` property of the class. If the `elements` parameter + * is provided and is an array, it is assigned to the `_elements + */ + constructor(elements?: T[]) { + this._elements = Array.isArray(elements) ? elements : []; + } - /** - * The function "fromArray" creates a new Stack object from an array of elements. - * @param {T[]} elements - The `elements` parameter is an array of elements of type `T`. - * @returns {Stack} The method is returning a new instance of the Stack class, initialized with the elements from the input - * array. - */ - static fromArray(elements: T[]): Stack { - return new Stack(elements); - } + /** + * The function "fromArray" creates a new Stack object from an array of elements. + * @param {T[]} elements - The `elements` parameter is an array of elements of type `T`. + * @returns {Stack} The method is returning a new instance of the Stack class, initialized with the elements from the input + * array. + */ + static fromArray(elements: T[]): Stack { + return new Stack(elements); + } - /** - * The function checks if an array is empty and returns a boolean value. - * @returns A boolean value indicating whether the `_elements` array is empty or not. - */ - isEmpty(): boolean { - return this._elements.length === 0; - } + /** + * The function checks if an array is empty and returns a boolean value. + * @returns A boolean value indicating whether the `_elements` array is empty or not. + */ + isEmpty(): boolean { + return this._elements.length === 0; + } - /** - * The size() function returns the number of elements in an array. - * @returns The size of the elements array. - */ - size(): number { - return this._elements.length; - } + /** + * The size() function returns the number of elements in an array. + * @returns The size of the elements array. + */ + size(): number { + return this._elements.length; + } - /** - * The `peek` function returns the last element of an array, or null if the array is empty. - * @returns The `peek()` function returns the last element of the `_elements` array, or `null` if the array is empty. - */ - peek(): T | null { - if (this.isEmpty()) return null; + /** + * The `peek` function returns the last element of an array, or null if the array is empty. + * @returns The `peek()` function returns the last element of the `_elements` array, or `null` if the array is empty. + */ + peek(): T | null { + if (this.isEmpty()) return null; - return this._elements[this._elements.length - 1]; - } + return this._elements[this._elements.length - 1]; + } - /** - * The push function adds an element to the stack and returns the updated stack. - * @param {T} element - The parameter "element" is of type T, which means it can be any data type. - * @returns The `push` method is returning the updated `Stack` object. - */ - push(element: T): Stack { - this._elements.push(element); - return this; - } + /** + * The push function adds an element to the stack and returns the updated stack. + * @param {T} element - The parameter "element" is of type T, which means it can be any data type. + * @returns The `push` method is returning the updated `Stack` object. + */ + push(element: T): Stack { + this._elements.push(element); + return this; + } - /** - * The `pop` function removes and returns the last element from an array, or returns null if the array is empty. - * @returns The `pop()` method is returning the last element of the array `_elements` if the array is not empty. If the - * array is empty, it returns `null`. - */ - pop(): T | null { - if (this.isEmpty()) return null; + /** + * The `pop` function removes and returns the last element from an array, or returns null if the array is empty. + * @returns The `pop()` method is returning the last element of the array `_elements` if the array is not empty. If the + * array is empty, it returns `null`. + */ + pop(): T | null { + if (this.isEmpty()) return null; - return this._elements.pop() || null; - } + return this._elements.pop() || null; + } - /** - * The toArray function returns a copy of the elements in an array. - * @returns An array of type T. - */ - toArray(): T[] { - return this._elements.slice(); - } + /** + * The toArray function returns a copy of the elements in an array. + * @returns An array of type T. + */ + toArray(): T[] { + return this._elements.slice(); + } - /** - * The clear function clears the elements array. - */ - clear(): void { - this._elements = []; - } + /** + * The clear function clears the elements array. + */ + clear(): void { + this._elements = []; + } - /** - * The `clone()` function returns a new `Stack` object with the same elements as the original stack. - * @returns The `clone()` method is returning a new `Stack` object with a copy of the `_elements` array. - */ - clone(): Stack { - return new Stack(this._elements.slice()); - } + /** + * The `clone()` function returns a new `Stack` object with the same elements as the original stack. + * @returns The `clone()` method is returning a new `Stack` object with a copy of the `_elements` array. + */ + clone(): Stack { + return new Stack(this._elements.slice()); + } } diff --git a/src/data-structures/tree/tree.ts b/src/data-structures/tree/tree.ts index 608b820..fbdbcf1 100644 --- a/src/data-structures/tree/tree.ts +++ b/src/data-structures/tree/tree.ts @@ -1,69 +1,69 @@ export class TreeNode { - constructor(id: string, value?: T, children?: TreeNode[]) { - this._id = id; - this._value = value || undefined; - this._children = children || []; + constructor(id: string, value?: T, children?: TreeNode[]) { + this._id = id; + this._value = value || undefined; + this._children = children || []; + } + + private _id: string; + + get id(): string { + return this._id; + } + + set id(value: string) { + this._id = value; + } + + private _value?: T | undefined; + + get value(): T | undefined { + return this._value; + } + + set value(value: T | undefined) { + this._value = value; + } + + private _children?: TreeNode[] | undefined; + + get children(): TreeNode[] | undefined { + return this._children; + } + + set children(value: TreeNode[] | undefined) { + this._children = value; + } + + addChildren(children: TreeNode | TreeNode []) { + if (!this.children) { + this.children = []; } - - private _id: string; - - get id(): string { - return this._id; + if (children instanceof TreeNode) { + this.children.push(children); + } else { + this.children = this.children.concat(children); } + } - set id(value: string) { - this._id = value; - } - - private _value?: T | undefined; - - get value(): T | undefined { - return this._value; - } - - set value(value: T | undefined) { - this._value = value; - } - - private _children?: TreeNode[] | undefined; - - get children(): TreeNode[] | undefined { - return this._children; - } - - set children(value: TreeNode[] | undefined) { - this._children = value; - } - - addChildren(children: TreeNode | TreeNode []) { - if (!this.children) { - this.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; } - if (children instanceof TreeNode) { - this.children.push(children); - } else { - this.children = this.children.concat(children); + const {children} = node; + if (children) { + for (let i = 0, len = children.length; i < len; i++) { + bfs(children[i], level + 1); + } } + }; + bfs(beginRoot, 1); } - - 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; - } -} \ No newline at end of file + return maxDepth; + } +} diff --git a/src/data-structures/trie/trie.ts b/src/data-structures/trie/trie.ts index 4ee1e18..5594f63 100644 --- a/src/data-structures/trie/trie.ts +++ b/src/data-structures/trie/trie.ts @@ -7,221 +7,221 @@ */ export class TrieNode { - constructor(v: string) { - this._val = v; - this._isEnd = false; - this._children = new Map(); - } + constructor(v: string) { + this._val = v; + this._isEnd = false; + this._children = new Map(); + } - private _val; + private _val; - get val(): string { - return this._val; - } + get val(): string { + return this._val; + } - set val(v: string) { - this._val = v; - } + set val(v: string) { + this._val = v; + } - protected _children: Map; + protected _children: Map; - get children(): Map { - return this._children; - } + get children(): Map { + return this._children; + } - set children(v: Map) { - this._children = v; - } + set children(v: Map) { + this._children = v; + } - protected _isEnd: boolean; + protected _isEnd: boolean; - get isEnd(): boolean { - return this._isEnd; - } + get isEnd(): boolean { + return this._isEnd; + } - set isEnd(v: boolean) { - this._isEnd = v; - } + set isEnd(v: boolean) { + this._isEnd = v; + } } export class Trie { - constructor(words?: string[]) { - this._root = new TrieNode(''); - if (words) { - for (const i of words) { - this.add(i); + constructor(words?: string[]) { + this._root = new TrieNode(''); + if (words) { + for (const i of words) { + this.add(i); + } + } + } + + protected _root: TrieNode; + + get root() { + return this._root; + } + + set root(v: TrieNode) { + this._root = v; + } + + add(word: string): boolean { + let cur = this._root; + for (const c of word) { + let nodeC = cur.children.get(c); + if (!nodeC) { + nodeC = new TrieNode(c); + 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; } - } - - protected _root: TrieNode; - - get root() { - return this._root; - } - - set root(v: TrieNode) { - this._root = v; - } - - add(word: string): boolean { - let cur = this._root; - for (const c of word) { - let nodeC = cur.children.get(c); - if (!nodeC) { - nodeC = new TrieNode(c); - cur.children.set(c, nodeC); - } - cur = nodeC; + const res = dfs(child, i + 1); + if (res && !cur.isEnd && child.children.size === 0) { + cur.children.delete(char); + return true; } - cur.isEnd = true; - return true; + return false; + } + return false; } - 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; + dfs(this.root, 0); + return isDeleted; + } + + // --- start additional methods --- + /** + * The function checks if a given input string has an absolute prefix in a tree data structure.Only can present as a prefix, not a word + * @param {string} input - The input parameter is a string that represents the input value for the function. + * @returns a boolean value. + */ + 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; + } + + /** + * The function checks if a given input string is a prefix of any existing string in a tree structure.Can present as a abs prefix or word + * @param {string} input - The input parameter is a string that represents the prefix we want to check. + * @returns a boolean value. + */ + 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; + } + + /** + * The function checks if the input string is a common prefix in a Trie data structure.Check if the input string is the common prefix of all the words + * @param {string} input - The input parameter is a string that represents the common prefix that we want to check for + * in the Trie data structure. + * @returns a boolean value indicating whether the input string is a common prefix in the Trie data structure. + */ + isCommonPrefix(input: string): boolean { + let commonPre = ''; + const dfs = (cur: TrieNode) => { + commonPre += cur.val; + if (commonPre === input) return; + if (cur.isEnd) return; + if (cur && cur.children && cur.children.size === 1) dfs(Array.from(cur.children.values())[0]); + else return; + } + dfs(this._root); + return commonPre === input; + } + + /** + * The function `getLongestCommonPrefix` returns the longest common prefix among all the words stored in a Trie data + * structure. + * @returns The function `getLongestCommonPrefix` returns a string, which is the longest common prefix found in the + * Trie. + */ + getLongestCommonPrefix(): string { + let commonPre = ''; + const dfs = (cur: TrieNode) => { + commonPre += cur.val; + if (cur.isEnd) return; + if (cur && cur.children && cur.children.size === 1) dfs(Array.from(cur.children.values())[0]); + else return; + } + dfs(this._root); + return commonPre; + } + + /** + * The `getAll` function returns an array of all words in a Trie data structure that start with a given prefix. + * @param [prefix] - The `prefix` parameter is a string that represents the prefix that we want to search for in the + * trie. It is an optional parameter, so if no prefix is provided, it will default to an empty string. + * @returns an array of strings. + */ + 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)); } - return cur.isEnd; + } + if (node.isEnd) { + words.push(word); + } } - 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; - } + let startNode = this._root; - dfs(this.root, 0); - return isDeleted; + if (prefix) { + for (const c of prefix) { + const nodeC = startNode.children.get(c); + if (nodeC) startNode = nodeC; + } } - // --- start additional methods --- - /** - * The function checks if a given input string has an absolute prefix in a tree data structure.Only can present as a prefix, not a word - * @param {string} input - The input parameter is a string that represents the input value for the function. - * @returns a boolean value. - */ - 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; - } + dfs(startNode, prefix); + return words; + } - /** - * The function checks if a given input string is a prefix of any existing string in a tree structure.Can present as a abs prefix or word - * @param {string} input - The input parameter is a string that represents the prefix we want to check. - * @returns a boolean value. - */ - 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; - } - - /** - * The function checks if the input string is a common prefix in a Trie data structure.Check if the input string is the common prefix of all the words - * @param {string} input - The input parameter is a string that represents the common prefix that we want to check for - * in the Trie data structure. - * @returns a boolean value indicating whether the input string is a common prefix in the Trie data structure. - */ - isCommonPrefix(input: string): boolean { - let commonPre = ''; - const dfs = (cur: TrieNode) => { - commonPre += cur.val; - if (commonPre === input) return; - if (cur.isEnd) return; - if (cur && cur.children && cur.children.size === 1) dfs(Array.from(cur.children.values())[0]); - else return; - } - dfs(this._root); - return commonPre === input; - } - - /** - * The function `getLongestCommonPrefix` returns the longest common prefix among all the words stored in a Trie data - * structure. - * @returns The function `getLongestCommonPrefix` returns a string, which is the longest common prefix found in the - * Trie. - */ - getLongestCommonPrefix(): string { - let commonPre = ''; - const dfs = (cur: TrieNode) => { - commonPre += cur.val; - if (cur.isEnd) return; - if (cur && cur.children && cur.children.size === 1) dfs(Array.from(cur.children.values())[0]); - else return; - } - dfs(this._root); - return commonPre; - } - - /** - * The `getAll` function returns an array of all words in a Trie data structure that start with a given prefix. - * @param [prefix] - The `prefix` parameter is a string that represents the prefix that we want to search for in the - * trie. It is an optional parameter, so if no prefix is provided, it will default to an empty string. - * @returns an array of strings. - */ - 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 --- + // --- end additional methods --- } diff --git a/src/interfaces/abstract-binary-tree.ts b/src/interfaces/abstract-binary-tree.ts index 09f376c..1b1f059 100644 --- a/src/interfaces/abstract-binary-tree.ts +++ b/src/interfaces/abstract-binary-tree.ts @@ -1,190 +1,190 @@ import { - AbstractBinaryTreeNodeProperties, - AbstractBinaryTreeNodeProperty, - BinaryTreeDeletedResult, - BinaryTreeNodeId, - BinaryTreeNodePropertyName, - DFSOrderPattern, - FamilyPosition, - LoopType, - NodeOrPropertyName + AbstractBinaryTreeNodeProperties, + AbstractBinaryTreeNodeProperty, + BinaryTreeDeletedResult, + BinaryTreeNodeId, + BinaryTreeNodePropertyName, + DFSOrderPattern, + FamilyPosition, + LoopType, + NodeOrPropertyName } from '../types'; import {AbstractBinaryTreeNode} from '../data-structures'; export interface IAbstractBinaryTreeNode> { - get id(): BinaryTreeNodeId + get id(): BinaryTreeNodeId - set id(v: BinaryTreeNodeId) + set id(v: BinaryTreeNodeId) - get val(): T | undefined + get val(): T | undefined - set val(v: T | undefined) + set val(v: T | undefined) - get left(): NEIGHBOR | null | undefined + get left(): NEIGHBOR | null | undefined - set left(v: NEIGHBOR | null | undefined) + set left(v: NEIGHBOR | null | undefined) - get right(): NEIGHBOR | null | undefined + get right(): NEIGHBOR | null | undefined - set right(v: NEIGHBOR | null | undefined) + set right(v: NEIGHBOR | null | undefined) - get parent(): NEIGHBOR | null | undefined + get parent(): NEIGHBOR | null | undefined - set parent(v: NEIGHBOR | null | undefined) + set parent(v: NEIGHBOR | null | undefined) - get familyPosition(): FamilyPosition + get familyPosition(): FamilyPosition - get height(): number + get height(): number - set height(v: number) + set height(v: number) } export interface IAbstractBinaryTree> { - createNode(id: BinaryTreeNodeId, val?: N['val'], count?: number): N | null + createNode(id: BinaryTreeNodeId, val?: N['val'], count?: number): N | null - get loopType(): LoopType + get loopType(): LoopType - get visitedId(): BinaryTreeNodeId[] + get visitedId(): BinaryTreeNodeId[] - get visitedVal(): Array + get visitedVal(): Array - get visitedNode(): N[] + get visitedNode(): N[] - get visitedLeftSum(): number[] + get visitedLeftSum(): number[] - get root(): N | null + get root(): N | null - get size(): number + get size(): number - swapLocation(srcNode: N, destNode: N): N + swapLocation(srcNode: N, destNode: N): N - clear(): void + clear(): void - isEmpty(): boolean + isEmpty(): boolean - add(id: BinaryTreeNodeId | N, val?: N['val']): N | null | undefined + add(id: BinaryTreeNodeId | N, val?: N['val']): N | null | undefined - addMany(idsOrNodes: (BinaryTreeNodeId | N | null)[], data?: N['val'][]): (N | null | undefined)[] + addMany(idsOrNodes: (BinaryTreeNodeId | N | null)[], data?: N['val'][]): (N | null | undefined)[] - fill(idsOrNodes: (BinaryTreeNodeId | N | null)[], data?: N[] | Array): boolean + fill(idsOrNodes: (BinaryTreeNodeId | N | null)[], data?: N[] | Array): boolean - remove(id: BinaryTreeNodeId, ignoreCount?: boolean): BinaryTreeDeletedResult[] + remove(id: BinaryTreeNodeId, ignoreCount?: boolean): BinaryTreeDeletedResult[] - getDepth(node: N): number + getDepth(node: N): number - getHeight(beginRoot?: N | null): number + getHeight(beginRoot?: N | null): number - getMinHeight(beginRoot?: N | null): number + getMinHeight(beginRoot?: N | null): number - isPerfectlyBalanced(beginRoot?: N | null): boolean + isPerfectlyBalanced(beginRoot?: N | null): boolean - getNodes(nodeProperty: BinaryTreeNodeId | N, propertyName ?: BinaryTreeNodePropertyName, onlyOne ?: boolean): N[] + getNodes(nodeProperty: BinaryTreeNodeId | N, propertyName ?: BinaryTreeNodePropertyName, onlyOne ?: boolean): N[] - has(nodeProperty: BinaryTreeNodeId | N, propertyName ?: BinaryTreeNodePropertyName): boolean + has(nodeProperty: BinaryTreeNodeId | N, propertyName ?: BinaryTreeNodePropertyName): boolean - get(nodeProperty: BinaryTreeNodeId | N, propertyName ?: BinaryTreeNodePropertyName): N | null + get(nodeProperty: BinaryTreeNodeId | N, propertyName ?: BinaryTreeNodePropertyName): N | null - getPathToRoot(node: N): N[] + getPathToRoot(node: N): N[] - getLeftMost(): N | null; + getLeftMost(): N | null; - getLeftMost(node: N): N; + getLeftMost(node: N): N; - getLeftMost(node?: N | null): N | null + getLeftMost(node?: N | null): N | null - getRightMost(): N | null; + getRightMost(): N | null; - getRightMost(node: N): N; + getRightMost(node: N): N; - getRightMost(node?: N | null): N | null + getRightMost(node?: N | null): N | null - isSubtreeBST(node: N | null): boolean + isSubtreeBST(node: N | null): boolean - isBST(): boolean + isBST(): boolean - getSubTreeSize(subTreeRoot: N | null | undefined): number + getSubTreeSize(subTreeRoot: N | null | undefined): number - // --- start additional methods --- + // --- start additional methods --- - subTreeSum(subTreeRoot: N, propertyName ?: BinaryTreeNodePropertyName): number + subTreeSum(subTreeRoot: N, propertyName ?: BinaryTreeNodePropertyName): number - subTreeAdd(subTreeRoot: N, delta: number, propertyName ?: BinaryTreeNodePropertyName): boolean + subTreeAdd(subTreeRoot: N, delta: number, propertyName ?: BinaryTreeNodePropertyName): boolean - BFS(): BinaryTreeNodeId[]; + BFS(): BinaryTreeNodeId[]; - BFS(nodeOrPropertyName: 'id'): BinaryTreeNodeId[]; + BFS(nodeOrPropertyName: 'id'): BinaryTreeNodeId[]; - BFS(nodeOrPropertyName: 'val'): N['val'][]; + BFS(nodeOrPropertyName: 'val'): N['val'][]; - BFS(nodeOrPropertyName: 'node'): N[]; + BFS(nodeOrPropertyName: 'node'): N[]; - BFS(nodeOrPropertyName: 'count'): number[]; + BFS(nodeOrPropertyName: 'count'): number[]; - BFS(nodeOrPropertyName ?: NodeOrPropertyName): AbstractBinaryTreeNodeProperties + BFS(nodeOrPropertyName ?: NodeOrPropertyName): AbstractBinaryTreeNodeProperties - DFS(): BinaryTreeNodeId[]; + DFS(): BinaryTreeNodeId[]; - DFS(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'id'): BinaryTreeNodeId[]; + DFS(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'id'): BinaryTreeNodeId[]; - DFS(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'val'): N[]; + DFS(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'val'): N[]; - DFS(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'node'): N[]; + DFS(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'node'): N[]; - DFS(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'count'): number[]; + DFS(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'count'): number[]; - DFS(pattern ?: 'in' | 'pre' | 'post', nodeOrPropertyName ?: NodeOrPropertyName): AbstractBinaryTreeNodeProperties + DFS(pattern ?: 'in' | 'pre' | 'post', nodeOrPropertyName ?: NodeOrPropertyName): AbstractBinaryTreeNodeProperties - DFSIterative(): BinaryTreeNodeId[]; + DFSIterative(): BinaryTreeNodeId[]; - DFSIterative(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'id'): BinaryTreeNodeId[]; + DFSIterative(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'id'): BinaryTreeNodeId[]; - DFSIterative(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'val'): N[]; + DFSIterative(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'val'): N[]; - DFSIterative(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'node'): N[]; + DFSIterative(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'node'): N[]; - DFSIterative(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'count'): number[]; + DFSIterative(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'count'): number[]; - DFSIterative(pattern ?: 'in' | 'pre' | 'post', nodeOrPropertyName ?: NodeOrPropertyName): AbstractBinaryTreeNodeProperties + DFSIterative(pattern ?: 'in' | 'pre' | 'post', nodeOrPropertyName ?: NodeOrPropertyName): AbstractBinaryTreeNodeProperties - levelIterative(node: N | null): BinaryTreeNodeId[]; + levelIterative(node: N | null): BinaryTreeNodeId[]; - levelIterative(node: N | null, nodeOrPropertyName?: 'id'): BinaryTreeNodeId[]; + levelIterative(node: N | null, nodeOrPropertyName?: 'id'): BinaryTreeNodeId[]; - levelIterative(node: N | null, nodeOrPropertyName?: 'val'): N['val'][]; + levelIterative(node: N | null, nodeOrPropertyName?: 'val'): N['val'][]; - levelIterative(node: N | null, nodeOrPropertyName?: 'node'): N[]; + levelIterative(node: N | null, nodeOrPropertyName?: 'node'): N[]; - levelIterative(node: N | null, nodeOrPropertyName?: 'count'): number[]; + levelIterative(node: N | null, nodeOrPropertyName?: 'count'): number[]; - levelIterative(node: N | null, nodeOrPropertyName ?: NodeOrPropertyName): AbstractBinaryTreeNodeProperties + levelIterative(node: N | null, nodeOrPropertyName ?: NodeOrPropertyName): AbstractBinaryTreeNodeProperties - listLevels(node: N | null): BinaryTreeNodeId[][]; + listLevels(node: N | null): BinaryTreeNodeId[][]; - listLevels(node: N | null, nodeOrPropertyName?: 'id'): BinaryTreeNodeId[][]; + listLevels(node: N | null, nodeOrPropertyName?: 'id'): BinaryTreeNodeId[][]; - listLevels(node: N | null, nodeOrPropertyName?: 'val'): N['val'][][]; + listLevels(node: N | null, nodeOrPropertyName?: 'val'): N['val'][][]; - listLevels(node: N | null, nodeOrPropertyName?: 'node'): N[][]; + listLevels(node: N | null, nodeOrPropertyName?: 'node'): N[][]; - listLevels(node: N | null, nodeOrPropertyName?: 'count'): number[][]; + listLevels(node: N | null, nodeOrPropertyName?: 'count'): number[][]; - listLevels(node: N | null, nodeOrPropertyName?: NodeOrPropertyName): AbstractBinaryTreeNodeProperty[][] + listLevels(node: N | null, nodeOrPropertyName?: NodeOrPropertyName): AbstractBinaryTreeNodeProperty[][] - getPredecessor(node: N): N + getPredecessor(node: N): N - morris(): BinaryTreeNodeId[]; + morris(): BinaryTreeNodeId[]; - morris(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'id'): BinaryTreeNodeId[]; + morris(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'id'): BinaryTreeNodeId[]; - morris(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'val'): N[]; + morris(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'val'): N[]; - morris(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'node'): N[]; + morris(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'node'): N[]; - morris(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'count'): number[]; + morris(pattern?: DFSOrderPattern, nodeOrPropertyName?: 'count'): number[]; - morris(pattern?: 'in' | 'pre' | 'post', nodeOrPropertyName?: NodeOrPropertyName): AbstractBinaryTreeNodeProperties + morris(pattern?: 'in' | 'pre' | 'post', nodeOrPropertyName?: NodeOrPropertyName): AbstractBinaryTreeNodeProperties - // --- end additional methods --- -} \ No newline at end of file + // --- end additional methods --- +} diff --git a/src/interfaces/abstract-graph.ts b/src/interfaces/abstract-graph.ts index 59e9720..ed08b12 100644 --- a/src/interfaces/abstract-graph.ts +++ b/src/interfaces/abstract-graph.ts @@ -2,31 +2,31 @@ import {VertexId} from '../types'; export interface IAbstractGraph { - hasVertex(vertexOrId: V | VertexId): boolean; + hasVertex(vertexOrId: V | VertexId): boolean; - addVertex(id: VertexId, val?: V): boolean; + addVertex(id: VertexId, val?: V): boolean; - removeVertex(vertexOrId: V | VertexId): boolean; + removeVertex(vertexOrId: V | VertexId): boolean; - removeAllVertices(vertices: V[] | VertexId[]): boolean; + removeAllVertices(vertices: V[] | VertexId[]): boolean; - degreeOf(vertexOrId: V | VertexId): number; + degreeOf(vertexOrId: V | VertexId): number; - edgesOf(vertexOrId: V | VertexId): E[]; + edgesOf(vertexOrId: V | VertexId): E[]; - hasEdge(src: V | VertexId, dest: V | VertexId): boolean; + hasEdge(src: V | VertexId, dest: V | VertexId): boolean; - getEdge(srcOrId: V | VertexId, destOrId: V | VertexId): E | null; + getEdge(srcOrId: V | VertexId, destOrId: V | VertexId): E | null; - edgeSet(): E[]; + edgeSet(): E[]; - addEdge(src: V | VertexId, dest: V | VertexId, weight: number, val: E): boolean; + addEdge(src: V | VertexId, dest: V | VertexId, weight: number, val: E): boolean; - removeEdge(edge: E): E | null; + removeEdge(edge: E): E | null; - setEdgeWeight(srcOrId: V | VertexId, destOrId: V | VertexId, weight: number): boolean; + setEdgeWeight(srcOrId: V | VertexId, destOrId: V | VertexId, weight: number): boolean; - getMinPathBetween(v1: V | VertexId, v2: V | VertexId, isWeight?: boolean): V[] | null; + getMinPathBetween(v1: V | VertexId, v2: V | VertexId, isWeight?: boolean): V[] | null; - getNeighbors(vertexOrId: V | VertexId): V[]; -} \ No newline at end of file + getNeighbors(vertexOrId: V | VertexId): V[]; +} diff --git a/src/interfaces/avl-tree.ts b/src/interfaces/avl-tree.ts index c2b4fda..d01cd05 100644 --- a/src/interfaces/avl-tree.ts +++ b/src/interfaces/avl-tree.ts @@ -8,21 +8,21 @@ export interface IAVLTreeNode> ext export interface IAVLTree> extends IBST { - add(id: BinaryTreeNodeId, val?: N['val'] | null): N | null | undefined + add(id: BinaryTreeNodeId, val?: N['val'] | null): N | null | undefined - remove(id: BinaryTreeNodeId, isUpdateAllLeftSum?: boolean): BinaryTreeDeletedResult[] + remove(id: BinaryTreeNodeId, isUpdateAllLeftSum?: boolean): BinaryTreeDeletedResult[] - balanceFactor(node: N): number + balanceFactor(node: N): number - updateHeight(node: N): void + updateHeight(node: N): void - balancePath(node: N): void + balancePath(node: N): void - balanceLL(A: N): void + balanceLL(A: N): void - balanceLR(A: N): void + balanceLR(A: N): void - balanceRR(A: N): void + balanceRR(A: N): void - balanceRL(A: N): void -} \ No newline at end of file + balanceRL(A: N): void +} diff --git a/src/interfaces/bst.ts b/src/interfaces/bst.ts index c2d888a..1ace734 100644 --- a/src/interfaces/bst.ts +++ b/src/interfaces/bst.ts @@ -6,27 +6,27 @@ export interface IBSTNode> extends IBi } export interface IBST> extends IBinaryTree { - createNode(id: BinaryTreeNodeId, val?: N['val'], count?: number): N + createNode(id: BinaryTreeNodeId, val?: N['val'], count?: number): N - add(id: BinaryTreeNodeId, val?: N['val'] | null, count?: number): N | null | undefined + add(id: BinaryTreeNodeId, val?: N['val'] | null, count?: number): N | null | undefined - get(nodeProperty: BinaryTreeNodeId | N, propertyName ?: BinaryTreeNodePropertyName): N | null + get(nodeProperty: BinaryTreeNodeId | N, propertyName ?: BinaryTreeNodePropertyName): N | null - lastKey(): BinaryTreeNodeId + lastKey(): BinaryTreeNodeId - remove(id: BinaryTreeNodeId, ignoreCount?: boolean): BinaryTreeDeletedResult[] + remove(id: BinaryTreeNodeId, ignoreCount?: boolean): BinaryTreeDeletedResult[] - getNodes(nodeProperty: BinaryTreeNodeId | N, propertyName ?: BinaryTreeNodePropertyName, onlyOne ?: boolean): N[] + getNodes(nodeProperty: BinaryTreeNodeId | N, propertyName ?: BinaryTreeNodePropertyName, onlyOne ?: boolean): N[] - // --- start additional functions + // --- start additional functions - lesserSum(id: BinaryTreeNodeId, propertyName ?: BinaryTreeNodePropertyName): number + lesserSum(id: BinaryTreeNodeId, propertyName ?: BinaryTreeNodePropertyName): number - allGreaterNodesAdd(node: N, delta: number, propertyName ?: BinaryTreeNodePropertyName): boolean + allGreaterNodesAdd(node: N, delta: number, propertyName ?: BinaryTreeNodePropertyName): boolean - perfectlyBalance(): boolean + perfectlyBalance(): boolean - isAVLBalanced(): boolean + isAVLBalanced(): boolean - // --- end additional functions -} \ No newline at end of file + // --- end additional functions +} diff --git a/src/interfaces/directed-graph.ts b/src/interfaces/directed-graph.ts index 23e0336..a4d6166 100644 --- a/src/interfaces/directed-graph.ts +++ b/src/interfaces/directed-graph.ts @@ -2,19 +2,19 @@ import {VertexId} from '../types'; import {IAbstractGraph} from './abstract-graph'; export interface IDirectedGraph extends IAbstractGraph { - incomingEdgesOf(vertex: V): E[]; + incomingEdgesOf(vertex: V): E[]; - outgoingEdgesOf(vertex: V): E[]; + outgoingEdgesOf(vertex: V): E[]; - inDegreeOf(vertexOrId: V | VertexId): number; + inDegreeOf(vertexOrId: V | VertexId): number; - outDegreeOf(vertexOrId: V | VertexId): number; + outDegreeOf(vertexOrId: V | VertexId): number; - getEdgeSrc(e: E): V | null; + getEdgeSrc(e: E): V | null; - getEdgeDest(e: E): V | null; + getEdgeDest(e: E): V | null; - removeEdgeSrcToDest(srcOrId: V | VertexId, destOrId: V | VertexId): E | null; + removeEdgeSrcToDest(srcOrId: V | VertexId, destOrId: V | VertexId): E | null; - removeEdgesBetween(v1: V | VertexId, v2: V | VertexId): E[]; -} \ No newline at end of file + removeEdgesBetween(v1: V | VertexId, v2: V | VertexId): E[]; +} diff --git a/src/interfaces/rb-tree.ts b/src/interfaces/rb-tree.ts index f6e0859..28b6166 100644 --- a/src/interfaces/rb-tree.ts +++ b/src/interfaces/rb-tree.ts @@ -7,5 +7,5 @@ export interface IRBTreeNode> exten export interface IRBTree> extends IBST { - createNode(id: BinaryTreeNodeId, val?: N['val'], count?: number): N -} \ No newline at end of file + createNode(id: BinaryTreeNodeId, val?: N['val'], count?: number): N +} diff --git a/src/interfaces/undirected-graph.ts b/src/interfaces/undirected-graph.ts index 7e7200d..200801f 100644 --- a/src/interfaces/undirected-graph.ts +++ b/src/interfaces/undirected-graph.ts @@ -2,5 +2,5 @@ import {VertexId} from '../types'; import {IAbstractGraph} from './abstract-graph'; export interface IUNDirectedGraph extends IAbstractGraph { - removeEdgeBetween(v1: V | VertexId, v2: V | VertexId): E | null; -} \ No newline at end of file + removeEdgeBetween(v1: V | VertexId, v2: V | VertexId): E | null; +} diff --git a/src/types/data-structures/abstract-binary-tree.ts b/src/types/data-structures/abstract-binary-tree.ts index 4376d92..35ce815 100644 --- a/src/types/data-structures/abstract-binary-tree.ts +++ b/src/types/data-structures/abstract-binary-tree.ts @@ -10,13 +10,13 @@ export enum LoopType { ITERATIVE = 'ITERATIVE', RECURSIVE = 'RECURSIVE'} /* This enumeration defines the position of a node within a family tree composed of three associated nodes, where 'root' represents the root node of the family tree, 'left' represents the left child node, and 'right' represents the right child node. */ export enum FamilyPosition { - ROOT = 'ROOT', - LEFT = 'LEFT', - RIGHT = 'RIGHT', - ROOT_LEFT = 'ROOT_LEFT', - ROOT_RIGHT = 'ROOT_RIGHT', - ISOLATED = 'ISOLATED', - MAL_NODE = 'MAL_NODE' + ROOT = 'ROOT', + LEFT = 'LEFT', + RIGHT = 'RIGHT', + ROOT_LEFT = 'ROOT_LEFT', + ROOT_RIGHT = 'ROOT_RIGHT', + ISOLATED = 'ISOLATED', + MAL_NODE = 'MAL_NODE' } export type BinaryTreeNodePropertyName = 'id' | 'val'; @@ -26,15 +26,15 @@ export type BinaryTreeNodeId = number; export type BinaryTreeDeletedResult = { deleted: N | null | undefined, needBalanced: N | null }; export type AbstractBinaryTreeNodeProperty> = - N['val'] - | N - | number - | BinaryTreeNodeId; + N['val'] + | N + | number + | BinaryTreeNodeId; export type AbstractBinaryTreeNodeProperties> = AbstractBinaryTreeNodeProperty[]; export type AbstractBinaryTreeNodeNested = AbstractBinaryTreeNode>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> export type AbstractBinaryTreeOptions = { - loopType?: LoopType, -} \ No newline at end of file + loopType?: LoopType, +} diff --git a/src/types/data-structures/abstract-graph.ts b/src/types/data-structures/abstract-graph.ts index de820a4..40da6c2 100644 --- a/src/types/data-structures/abstract-graph.ts +++ b/src/types/data-structures/abstract-graph.ts @@ -1,5 +1,5 @@ export type VertexId = string | number; export type EdgeId = string; export type DijkstraResult = - { distMap: Map, distPaths?: Map, preMap: Map, seen: Set, paths: V[][], minDist: number, minPath: V[] } - | null; + { distMap: Map, distPaths?: Map, preMap: Map, seen: Set, paths: V[][], minDist: number, minPath: V[] } + | null; diff --git a/src/types/data-structures/bst.ts b/src/types/data-structures/bst.ts index d51564c..3af18af 100644 --- a/src/types/data-structures/bst.ts +++ b/src/types/data-structures/bst.ts @@ -6,7 +6,7 @@ export type BSTComparator = (a: BinaryTreeNodeId, b: BinaryTreeNodeId) => number export type BSTNodeNested = BSTNode>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> export type BSTOptions = BinaryTreeOptions & { - comparator?: BSTComparator, + comparator?: BSTComparator, } -export enum CP {lt = 'lt', eq = 'eq', gt = 'gt'} \ No newline at end of file +export enum CP {lt = 'lt', eq = 'eq', gt = 'gt'} diff --git a/src/types/data-structures/directed-graph.ts b/src/types/data-structures/directed-graph.ts index 4f7c4b3..c913831 100644 --- a/src/types/data-structures/directed-graph.ts +++ b/src/types/data-structures/directed-graph.ts @@ -2,7 +2,7 @@ export type TopologicalStatus = 0 | 1 | 2; export enum TopologicalProperty { - VAL = 'VAL', - NODE = 'NODE', - ID = 'ID', + VAL = 'VAL', + NODE = 'NODE', + ID = 'ID', } diff --git a/src/types/data-structures/heap.ts b/src/types/data-structures/heap.ts index 02dc2d4..12072fa 100644 --- a/src/types/data-structures/heap.ts +++ b/src/types/data-structures/heap.ts @@ -1,5 +1,5 @@ export type HeapOptions = { - priorityExtractor?: (element: T) => number; - // TODO there is an idea that support chaining which is for conveniently using the data structure - // isChaining? : boolean -} \ No newline at end of file + priorityExtractor?: (element: T) => number; + // TODO there is an idea that support chaining which is for conveniently using the data structure + // isChaining? : boolean +} diff --git a/src/types/data-structures/navigator.ts b/src/types/data-structures/navigator.ts index 231f6b9..a918556 100644 --- a/src/types/data-structures/navigator.ts +++ b/src/types/data-structures/navigator.ts @@ -2,12 +2,12 @@ export type Direction = 'up' | 'right' | 'down' | 'left'; export type Turning = { [key in Direction]: Direction }; export type NavigatorParams = { - matrix: T[][], - turning: Turning, - onMove: (cur: [number, number]) => void - init: { - cur: [number, number], - charDir: Direction, - VISITED: T, - } + matrix: T[][], + turning: Turning, + onMove: (cur: [number, number]) => void + init: { + cur: [number, number], + charDir: Direction, + VISITED: T, + } } diff --git a/src/types/data-structures/priority-queue.ts b/src/types/data-structures/priority-queue.ts index 3fe3b75..1bd2ae4 100644 --- a/src/types/data-structures/priority-queue.ts +++ b/src/types/data-structures/priority-queue.ts @@ -1,9 +1,9 @@ export type PriorityQueueComparator = (a: T, b: T) => number; export type PriorityQueueOptions = { - nodes?: T[]; - isFix?: boolean; - comparator: PriorityQueueComparator; + nodes?: T[]; + isFix?: boolean; + comparator: PriorityQueueComparator; } -export type PriorityQueueDFSOrderPattern = 'pre' | 'in' | 'post'; \ No newline at end of file +export type PriorityQueueDFSOrderPattern = 'pre' | 'in' | 'post'; diff --git a/src/types/utils/validate-type.ts b/src/types/utils/validate-type.ts index e3be25d..fbfdcd1 100644 --- a/src/types/utils/validate-type.ts +++ b/src/types/utils/validate-type.ts @@ -7,19 +7,19 @@ export type NonNumberNonObjectButDefined = string | boolean | symbol | null; export type ObjectWithoutId = Omit; export type ObjectWithNonNumberId = { - [key: string]: any, - id: string | boolean | symbol | null | object | undefined; + [key: string]: any, + id: string | boolean | symbol | null | object | undefined; } export type ObjectWithNumberId = { - [key: string]: any, - id: number; + [key: string]: any, + id: number; } export type RestrictValById = - NonNumberNonObjectButDefined - | ObjectWithoutId - | ObjectWithNonNumberId - | ObjectWithNumberId; + NonNumberNonObjectButDefined + | ObjectWithoutId + | ObjectWithNonNumberId + | ObjectWithNumberId; -export type DummyAny = string | number | boolean | null | undefined | object | symbol | void | Function | never; \ No newline at end of file +export type DummyAny = string | number | boolean | null | undefined | object | symbol | void | Function | never; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index d1df63c..a7c9074 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -8,71 +8,71 @@ import type {Thunk, ToThunkFn, TrlAsyncFn, TrlFn} from '../types'; export const uuidV4 = function () { - return 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'.replace(/[x]/g, function (c) { - const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); + return 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'.replace(/[x]/g, function (c) { + const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); }; 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 = []; + 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--; - } + 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; + return result; }; export const THUNK_SYMBOL = Symbol('thunk') export const isThunk = (fnOrValue: any) => { - return typeof fnOrValue === 'function' && fnOrValue.__THUNK__ === THUNK_SYMBOL + return typeof fnOrValue === 'function' && fnOrValue.__THUNK__ === THUNK_SYMBOL } export const toThunk = (fn: ToThunkFn): Thunk => { - const thunk = () => fn() - thunk.__THUNK__ = THUNK_SYMBOL - return thunk + const thunk = () => fn() + thunk.__THUNK__ = THUNK_SYMBOL + return thunk } export const trampoline = (fn: TrlFn) => { - const cont = (...args: [...Parameters]) => toThunk(() => fn(...args)) + const cont = (...args: [...Parameters]) => toThunk(() => fn(...args)) - return Object.assign( - (...args: [...Parameters]) => { - let result = fn(...args) + return Object.assign( + (...args: [...Parameters]) => { + let result = fn(...args) - while (isThunk(result) && typeof result === 'function') { - result = result() - } + while (isThunk(result) && typeof result === 'function') { + result = result() + } - return result - }, - {cont} - ) + return result + }, + {cont} + ) } export const trampolineAsync = (fn: TrlAsyncFn) => { - const cont = (...args: [...Parameters]) => toThunk(() => fn(...args)) + const cont = (...args: [...Parameters]) => toThunk(() => fn(...args)) - return Object.assign( - async (...args: [...Parameters]) => { - let result = await fn(...args) + return Object.assign( + async (...args: [...Parameters]) => { + let result = await fn(...args) - while (isThunk(result) && typeof result === 'function') { - result = await result() - } + while (isThunk(result) && typeof result === 'function') { + result = await result() + } - return result - }, - {cont} - ) + return result + }, + {cont} + ) } diff --git a/src/utils/validate-type.ts b/src/utils/validate-type.ts index 159aaf3..28193d1 100644 --- a/src/utils/validate-type.ts +++ b/src/utils/validate-type.ts @@ -3,67 +3,67 @@ import {NonNumberNonObjectButDefined, ObjectWithNonNumberId, ObjectWithNumberId, export const nonNumberNonObjectButDefinedSchema = z.union([z.string(), - z.boolean(), z.any()]) - .nullable() + z.boolean(), z.any()]) + .nullable() export const keyValueObjectSchema = z.record(z.unknown()) export const objectWithoutIdSchema = keyValueObjectSchema.refine(obj => !('id' in obj), { - message: 'Object cannot contain the \'id\' field', + message: 'Object cannot contain the \'id\' field', }); export const keyValueObjectWithIdSchema = z.record(z.any()).and( - z.object({ - id: z.union([z.string(), z.number(), z.any()]) - }) + z.object({ + id: z.union([z.string(), z.number(), z.any()]) + }) ) export const objectWithNonNumberIdSchema = z.record(z.any()).and( - z.object({ - id: z - .union([z.string(), z.boolean(), z.any(), z.any(), z.undefined()]) - .nullable() - }) + z.object({ + id: z + .union([z.string(), z.boolean(), z.any(), z.any(), z.undefined()]) + .nullable() + }) ) export const objectWithNumberIdSchema = z.record(z.any()).and( - z.object({ - id: z.number() - }) + z.object({ + id: z.number() + }) ) export const binaryTreeNodeValWithId = z.union([ - nonNumberNonObjectButDefinedSchema, - objectWithoutIdSchema, - objectWithNonNumberIdSchema, - objectWithNumberIdSchema + nonNumberNonObjectButDefinedSchema, + objectWithoutIdSchema, + objectWithNonNumberIdSchema, + objectWithNumberIdSchema ]) export function parseBySchema(schema: z.Schema, val: any) { - try { - schema.parse(val); - return true; - } catch (error) { - return false; - } + try { + schema.parse(val); + return true; + } catch (error) { + return false; + } } export function isNonNumberNonObjectButDefined(val: any): val is NonNumberNonObjectButDefined { - return parseBySchema(nonNumberNonObjectButDefinedSchema, val); + return parseBySchema(nonNumberNonObjectButDefinedSchema, val); } export function isObjectWithoutId(val: any): val is ObjectWithoutId { - return parseBySchema(objectWithoutIdSchema, val); + return parseBySchema(objectWithoutIdSchema, val); } export function isObjectWithNonNumberId(val: any): val is ObjectWithNonNumberId { - return parseBySchema(objectWithNonNumberIdSchema, val); + return parseBySchema(objectWithNonNumberIdSchema, val); } export function isObjectWithNumberId(val: any): val is ObjectWithNumberId { - return parseBySchema(objectWithNonNumberIdSchema, val); + return parseBySchema(objectWithNonNumberIdSchema, val); } export function isNumber(val: any): val is number { - return typeof val === 'number'; -} \ No newline at end of file + return typeof val === 'number'; +}