[binary-tree] Change the design so that when instantiating the BinaryTree data structure, there's no need to specify the node type; instead, use the second default generic parameter and its default value.

This commit is contained in:
Revone 2023-10-27 18:04:31 +08:00
parent af23af911a
commit 06dc8bdc4d
12 changed files with 64 additions and 63 deletions

View file

@ -24,6 +24,7 @@
"lint:test": "eslint --fix 'test/**/*.{js,ts}'",
"lint": "npm run lint:src && npm run lint:test",
"format:src": "prettier --write 'src/**/*.{js,ts}'",
"format:specific": "prettier --write 'src/data-structures/binary-tree/bst.ts'",
"format:test": "prettier --write 'test/**/*.{js,ts}'",
"format": "npm run format:src && npm run format:test",
"fix:src": "npm run lint:src && npm run format:src",

View file

@ -10,8 +10,8 @@ import type {AVLTreeNodeNested, AVLTreeOptions, BinaryTreeDeletedResult, BinaryT
import {MapCallback} from '../../types';
import {IBinaryTree} from '../../interfaces';
export class AVLTreeNode<V = any, FAMILY extends AVLTreeNode<V, FAMILY> = AVLTreeNodeNested<V>> extends
BSTNode<V, FAMILY> {
export class AVLTreeNode<V = any, N extends AVLTreeNode<V, N> = AVLTreeNodeNested<V>> extends
BSTNode<V, N> {
height: number;
constructor(key: BinaryTreeNodeKey, val?: V) {
@ -20,7 +20,7 @@ export class AVLTreeNode<V = any, FAMILY extends AVLTreeNode<V, FAMILY> = AVLTre
}
}
export class AVLTree<N extends AVLTreeNode<N['val'], N> = AVLTreeNode> extends BST<N> implements IBinaryTree<N> {
export class AVLTree<V = any, N extends AVLTreeNode<V, N> = AVLTreeNode> extends BST<V, N> implements IBinaryTree<V, N>{
/**
* 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
@ -36,12 +36,12 @@ export class AVLTree<N extends AVLTreeNode<N['val'], N> = AVLTreeNode> extends B
* @param {BinaryTreeNodeKey} key - The key parameter is the key value that will be associated with
* the new node. It is used to determine the position of the node in the binary search tree.
* @param [val] - The parameter `val` is an optional value that can be assigned to the node. It is of
* type `N['val']`, which means it can be any value that is assignable to the `val` property of the
* type `V`, which means it can be any value that is assignable to the `val` property of the
* node type `N`.
* @returns a new AVLTreeNode object with the specified key and value.
*/
override createNode(key: BinaryTreeNodeKey, val?: N['val']): N {
return new AVLTreeNode<N['val'], N>(key, val) as N;
override createNode(key: BinaryTreeNodeKey, val?: V): N {
return new AVLTreeNode<V, N>(key, val) as N;
}
/**
@ -53,7 +53,7 @@ export class AVLTree<N extends AVLTreeNode<N['val'], N> = AVLTreeNode> extends B
* are adding to the binary search tree.
* @returns The method is returning the inserted node (`N`), `null`, or `undefined`.
*/
override add(keyOrNode: BinaryTreeNodeKey | N | null, val?: N['val']): N | null | undefined {
override add(keyOrNode: BinaryTreeNodeKey | N | null, val?: V): N | null | undefined {
// TODO support node as a param
const inserted = super.add(keyOrNode, val);
if (inserted) this._balancePath(inserted);

View file

@ -23,9 +23,9 @@ import {Queue} from '../queue';
/**
* Represents a node in a binary tree.
* @template V - The type of data stored in the node.
* @template FAMILY - The type of the family relationship in the binary tree.
* @template N - The type of the family relationship in the binary tree.
*/
export class BinaryTreeNode<V = any, FAMILY extends BinaryTreeNode<V, FAMILY> = BinaryTreeNodeNested<V>> {
export class BinaryTreeNode<V = any, N extends BinaryTreeNode<V, N> = BinaryTreeNode<V, BinaryTreeNodeNested<V>>> {
/**
* The key associated with the node.
*/
@ -39,7 +39,7 @@ export class BinaryTreeNode<V = any, FAMILY extends BinaryTreeNode<V, FAMILY> =
/**
* The parent node of the current node.
*/
parent: FAMILY | null | undefined;
parent: N | null | undefined;
/**
* Creates a new instance of BinaryTreeNode.
@ -51,42 +51,42 @@ export class BinaryTreeNode<V = any, FAMILY extends BinaryTreeNode<V, FAMILY> =
this.val = val;
}
private _left: FAMILY | null | undefined;
private _left: N | null | undefined;
/**
* Get the left child node.
*/
get left(): FAMILY | null | undefined {
get left(): N | null | undefined {
return this._left;
}
/**
* Set the left child node.
* @param {FAMILY | null | undefined} v - The left child node.
* @param {N | null | undefined} v - The left child node.
*/
set left(v: FAMILY | null | undefined) {
set left(v: N | null | undefined) {
if (v) {
v.parent = this as unknown as FAMILY;
v.parent = this as unknown as N;
}
this._left = v;
}
private _right: FAMILY | null | undefined;
private _right: N | null | undefined;
/**
* Get the right child node.
*/
get right(): FAMILY | null | undefined {
get right(): N | null | undefined {
return this._right;
}
/**
* Set the right child node.
* @param {FAMILY | null | undefined} v - The right child node.
* @param {N | null | undefined} v - The right child node.
*/
set right(v: FAMILY | null | undefined) {
set right(v: N | null | undefined) {
if (v) {
v.parent = this as unknown as FAMILY;
v.parent = this as unknown as N;
}
this._right = v;
}
@ -96,7 +96,7 @@ export class BinaryTreeNode<V = any, FAMILY extends BinaryTreeNode<V, FAMILY> =
* @returns {FamilyPosition} - The family position of the node.
*/
get familyPosition(): FamilyPosition {
const that = this as unknown as FAMILY;
const that = this as unknown as N;
if (!this.parent) {
return this.left || this.right ? FamilyPosition.ROOT : FamilyPosition.ISOLATED;
}
@ -115,7 +115,7 @@ export class BinaryTreeNode<V = any, FAMILY extends BinaryTreeNode<V, FAMILY> =
* Represents a binary tree data structure.
* @template N - The type of the binary tree's nodes.
*/
export class BinaryTree<N extends BinaryTreeNode<N['val'], N> = BinaryTreeNode> implements IBinaryTree<N> {
export class BinaryTree<V = any, N extends BinaryTreeNode<V, N> = BinaryTreeNode> implements IBinaryTree<V, N>{
/**
* Creates a new instance of BinaryTree.
* @param {BinaryTreeOptions} [options] - The options for the binary tree.
@ -165,11 +165,11 @@ export class BinaryTree<N extends BinaryTreeNode<N['val'], N> = BinaryTreeNode>
/**
* Creates a new instance of BinaryTreeNode with the given key and value.
* @param {BinaryTreeNodeKey} key - The key for the new node.
* @param {N['val']} val - The value for the new node.
* @param {V} val - The value for the new node.
* @returns {N} - The newly created BinaryTreeNode.
*/
createNode(key: BinaryTreeNodeKey, val?: N['val']): N {
return new BinaryTreeNode<N['val'], N>(key, val) as N;
createNode(key: BinaryTreeNodeKey, val?: V): N {
return new BinaryTreeNode<V, N>(key, val) as N;
}
/**
@ -191,10 +191,10 @@ export class BinaryTree<N extends BinaryTreeNode<N['val'], N> = BinaryTreeNode>
/**
* Add a node with the given key and value to the binary tree.
* @param {BinaryTreeNodeKey | N | null} keyOrNode - The key or node to add to the binary tree.
* @param {N['val']} val - The value for the new node (optional).
* @param {V} val - The value for the new node (optional).
* @returns {N | null | undefined} - The inserted node, or null if nothing was inserted, or undefined if the operation failed.
*/
add(keyOrNode: BinaryTreeNodeKey | N | null, val?: N['val']): N | null | undefined {
add(keyOrNode: BinaryTreeNodeKey | N | null, val?: V): N | null | undefined {
const _bfs = (root: N, newNode: N | null): N | undefined | null => {
const queue = new Queue<N | null>([root]);
while (queue.size > 0) {
@ -249,12 +249,12 @@ export class BinaryTree<N extends BinaryTreeNode<N['val'], N> = BinaryTreeNode>
* values, and adds them to the binary tree.
* @param {(BinaryTreeNodeKey | null)[] | (N | null)[]} keysOrNodes - An array of BinaryTreeNodeKey or BinaryTreeNode
* objects, or null values.
* @param {N['val'][]} [values] - The `values` parameter is an optional array of values (`N['val'][]`) that corresponds to
* @param {V[]} [values] - The `values` parameter is an optional array of values (`V[]`) that corresponds to
* the nodes or node IDs being added. It is used to set the value of each node being added. If `values` is not provided,
* the value of the nodes will be `undefined`.
* @returns The function `addMany` returns an array of `N`, `null`, or `undefined` values.
*/
addMany(keysOrNodes: (BinaryTreeNodeKey | null)[] | (N | null)[], values?: N['val'][]): (N | null | undefined)[] {
addMany(keysOrNodes: (BinaryTreeNodeKey | null)[] | (N | null)[], values?: V[]): (N | null | undefined)[] {
// TODO not sure addMany not be run multi times
return keysOrNodes.map((keyOrNode, i) => {
if (keyOrNode instanceof BinaryTreeNode) {
@ -274,12 +274,12 @@ export class BinaryTree<N extends BinaryTreeNode<N['val'], N> = BinaryTreeNode>
* The `refill` function clears the binary tree and adds multiple nodes with the given IDs or nodes and optional data.
* @param {(BinaryTreeNodeKey | N)[]} keysOrNodes - The `keysOrNodes` parameter is an array that can contain either
* `BinaryTreeNodeKey` or `N` values.
* @param {N[] | Array<N['val']>} [data] - The `data` parameter is an optional array of values that will be assigned to
* @param {N[] | Array<V>} [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 `keysOrNodes`
* array. Each value in the `data` array will be assigned to the
* @returns The method is returning a boolean value.
*/
refill(keysOrNodes: (BinaryTreeNodeKey | null)[] | (N | null)[], data?: N[] | Array<N['val']>): boolean {
refill(keysOrNodes: (BinaryTreeNodeKey | null)[] | (N | null)[], data?: Array<V>): boolean {
this.clear();
return keysOrNodes.length === this.addMany(keysOrNodes, data).length;
}

View file

@ -18,13 +18,13 @@ import {BinaryTree, BinaryTreeNode} from './binary-tree';
import {IBinaryTree} from '../../interfaces';
import {Queue} from '../queue';
export class BSTNode<V = any, FAMILY extends BSTNode<V, FAMILY> = BSTNodeNested<V>> extends BinaryTreeNode<V, FAMILY> {
export class BSTNode<V = any, N extends BSTNode<V, N> = BSTNodeNested<V>> extends BinaryTreeNode<V, N> {
constructor(key: BinaryTreeNodeKey, val?: V) {
super(key, val);
}
}
export class BST<N extends BSTNode<N['val'], N> = BSTNode> extends BinaryTree<N> implements IBinaryTree<N> {
export class BST<V = any, N extends BSTNode<V, N> = BSTNode> extends BinaryTree<V, N> implements IBinaryTree<V, N> {
/**
* The constructor function initializes a binary search tree object with an optional comparator
* function.
@ -49,8 +49,8 @@ export class BST<N extends BSTNode<N['val'], N> = BSTNode> extends BinaryTree<N>
* represents the value associated with the node in a binary search tree.
* @returns a new instance of the BSTNode class with the specified key and value.
*/
override createNode(key: BinaryTreeNodeKey, val?: N['val']): N {
return new BSTNode<N['val'], N>(key, val) as N;
override createNode(key: BinaryTreeNodeKey, val?: V): N {
return new BSTNode<V, N>(key, val) as N;
}
/**
@ -63,7 +63,7 @@ export class BST<N extends BSTNode<N['val'], N> = BSTNode> extends BinaryTree<N>
* @returns the inserted node (N) if it was successfully added to the binary search tree. If the node
* was not added or if the parameters were invalid, it returns null or undefined.
*/
override add(keyOrNode: BinaryTreeNodeKey | N | null, val?: N['val']): N | null | undefined {
override add(keyOrNode: BinaryTreeNodeKey | N | null, val?: V): N | null | undefined {
// TODO support node as a parameter
let inserted: N | null = null;
let newNode: N | null = null;
@ -136,7 +136,7 @@ export class BST<N extends BSTNode<N['val'], N> = BSTNode> extends BinaryTree<N>
* represents an array of keys or nodes that need to be added to the binary search tree. It can be an
* array of `BinaryTreeNodeKey` or `N` (which represents the node type in the binary search tree) or
* `null
* @param {N['val'][]} data - The values of tree nodes
* @param {V[]} data - The values of tree nodes
* @param {boolean} isBalanceAdd - If true the nodes will be balance inserted in binary search method.
* @param iterationType - The `iterationType` parameter determines the type of iteration to be used.
* It can have two possible values:
@ -145,7 +145,7 @@ export class BST<N extends BSTNode<N['val'], N> = BSTNode> extends BinaryTree<N>
override addMany(
keysOrNodes: (BinaryTreeNodeKey | null)[] | (N | null)[],
data?: N['val'][],
data?: V[],
isBalanceAdd = true,
iterationType = this.iterationType
): (N | null | undefined)[] {
@ -174,7 +174,7 @@ export class BST<N extends BSTNode<N['val'], N> = BSTNode> extends BinaryTree<N>
}
let sortedKeysOrNodes: (number | N | null)[] = [],
sortedData: (N['val'] | undefined)[] | undefined = [];
sortedData: (V | undefined)[] | undefined = [];
if (isNodeOrNullTuple(combinedArr)) {
sorted = combinedArr.sort((a, b) => a[0].key - b[0].key);
@ -185,7 +185,7 @@ export class BST<N extends BSTNode<N['val'], N> = BSTNode> extends BinaryTree<N>
}
sortedKeysOrNodes = sorted.map(([keyOrNode]) => keyOrNode);
sortedData = sorted.map(([, val]) => val);
const recursive = (arr: (BinaryTreeNodeKey | null | N)[], data?: N['val'][]) => {
const recursive = (arr: (BinaryTreeNodeKey | null | N)[], data?: (V | undefined)[]) => {
if (arr.length === 0) return;
const mid = Math.floor((arr.length - 1) / 2);

View file

@ -2,9 +2,9 @@ import {BinaryTreeNodeKey, RBColor, RBTreeNodeNested, RBTreeOptions} from '../..
import {IBinaryTree} from '../../interfaces';
import {BST, BSTNode} from './bst';
export class RBTreeNode<V = any, FAMILY extends RBTreeNode<V, FAMILY> = RBTreeNodeNested<V>> extends BSTNode<
export class RBTreeNode<V = any, N extends RBTreeNode<V, N> = RBTreeNodeNested<V>> extends BSTNode<
V,
FAMILY
N
> {
constructor(key: BinaryTreeNodeKey, val?: V) {
super(key, val);
@ -22,16 +22,16 @@ export class RBTreeNode<V = any, FAMILY extends RBTreeNode<V, FAMILY> = RBTreeNo
}
}
export class RBTree<N extends RBTreeNode<N['val'], N> = RBTreeNode> extends BST<N> implements IBinaryTree<N> {
export class RBTree<V, N extends RBTreeNode<V, N> = RBTreeNode> extends BST<V, N> implements IBinaryTree<V, N> {
constructor(options?: RBTreeOptions) {
super(options);
}
override createNode(key: BinaryTreeNodeKey, val?: N['val']): N {
override createNode(key: BinaryTreeNodeKey, val?: V): N {
return new RBTreeNode(key, val) as N;
}
// override add(keyOrNode: BinaryTreeNodeKey | N | null, val?: N['val']): N | null | undefined {
// override add(keyOrNode: BinaryTreeNodeKey | N | null, val?: V): N | null | undefined {
// const inserted = super.add(keyOrNode, val);
// if (inserted) this._fixInsertViolation(inserted);
// return inserted;

View file

@ -12,8 +12,8 @@ import {AVLTree, AVLTreeNode} from './avl-tree';
export class TreeMultisetNode<
V = any,
FAMILY extends TreeMultisetNode<V, FAMILY> = TreeMultisetNodeNested<V>
> extends AVLTreeNode<V, FAMILY> {
N extends TreeMultisetNode<V, N> = TreeMultisetNodeNested<V>
> extends AVLTreeNode<V, N> {
count: number;
/**
@ -35,9 +35,9 @@ export class TreeMultisetNode<
/**
* The only distinction between a TreeMultiset and a AVLTree lies in the ability of the former to store duplicate nodes through the utilization of counters.
*/
export class TreeMultiset<N extends TreeMultisetNode<N['val'], N> = TreeMultisetNode>
extends AVLTree<N>
implements IBinaryTree<N> {
export class TreeMultiset<V = any, N extends TreeMultisetNode<V, N> = TreeMultisetNode>
extends AVLTree<V, N>
implements IBinaryTree<V, N> {
/**
* The constructor function for a TreeMultiset class in TypeScript, which extends another class and sets an option to
* merge duplicated values.
@ -63,7 +63,7 @@ export class TreeMultiset<N extends TreeMultisetNode<N['val'], N> = TreeMultiset
* 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 key, value, and count (if provided).
*/
override createNode(key: BinaryTreeNodeKey, val?: N['val'], count?: number): N {
override createNode(key: BinaryTreeNodeKey, val?: V, count?: number): N {
return new TreeMultisetNode(key, val, count) as N;
}
@ -80,7 +80,7 @@ export class TreeMultiset<N extends TreeMultisetNode<N['val'], N> = TreeMultiset
* count is specified, the default count will be 1.
* @returns The function `add` returns a value of type `N | null | undefined`.
*/
override add(keyOrNode: BinaryTreeNodeKey | N | null, val?: N['val'], count = 1): N | null | undefined {
override add(keyOrNode: BinaryTreeNodeKey | N | null, val?: V, count = 1): N | null | undefined {
let inserted: N | null | undefined = undefined,
newNode: N | null;
if (keyOrNode instanceof TreeMultisetNode) {
@ -186,14 +186,14 @@ export class TreeMultiset<N extends TreeMultisetNode<N['val'], N> = TreeMultiset
* inserted nodes.
* @param {(BinaryTreeNodeKey | null)[] | (N | null)[]} keysOrNodes - An array of keys or nodes to be
* added to the multiset. Each element can be either a BinaryTreeNodeKey or a TreeMultisetNode.
* @param {N['val'][]} [data] - The `data` parameter is an optional array of values that correspond
* @param {V[]} [data] - The `data` parameter is an optional array of values that correspond
* to the keys or nodes being added to the multiset. It is used to associate additional data with
* each key or node.
* @returns The function `addMany` returns an array of `N`, `null`, or `undefined` values.
*/
override addMany(
keysOrNodes: (BinaryTreeNodeKey | null)[] | (N | null)[],
data?: N['val'][]
data?: V[]
): (N | null | undefined)[] {
const inserted: (N | null | undefined)[] = [];
@ -206,7 +206,7 @@ export class TreeMultiset<N extends TreeMultisetNode<N['val'], N> = TreeMultiset
}
if (keyOrNode === null) {
inserted.push(this.add(NaN, null, 0));
inserted.push(this.add(NaN, undefined, 0));
continue;
}

View file

@ -1,7 +1,7 @@
import {BinaryTreeNode} from '../data-structures';
import {BinaryTreeDeletedResult, BinaryTreeNodeKey, MapCallback} from '../types';
import {BinaryTreeDeletedResult, BinaryTreeNodeKey, BinaryTreeNodeNested, MapCallback} from '../types';
export interface IBinaryTree<N extends BinaryTreeNode<N['val'], N>> {
export interface IBinaryTree<V = any, N extends BinaryTreeNode<V, N> = BinaryTreeNodeNested<V>> {
createNode(key: BinaryTreeNodeKey, val?: N['val']): N;
add(keyOrNode: BinaryTreeNodeKey | N | null, val?: N['val']): N | null | undefined;

View file

@ -3,7 +3,7 @@ import {AVLTree, AVLTreeNode, CP} from '../../../../src';
describe('AVL Tree Test', () => {
it('should perform various operations on a AVL Tree', () => {
const arr = [11, 3, 15, 1, 8, 13, 16, 2, 6, 9, 12, 14, 4, 7, 10, 5];
const tree = new AVLTree<AVLTreeNode<number>>();
const tree = new AVLTree<number>();
for (const i of arr) tree.add(i, i);
@ -110,7 +110,7 @@ describe('AVL Tree Test', () => {
});
describe('AVLTree APIs test', () => {
const avl = new AVLTree<AVLTreeNode<{id: number; text: string}>>();
const avl = new AVLTree<{id: number; text: string}>();
beforeEach(() => {
avl.clear();
});

View file

@ -196,7 +196,7 @@ describe('BinaryTree Morris Traversal', () => {
});
describe('BinaryTree APIs test', () => {
const avl = new AVLTree<AVLTreeNode<{id: number; text: string}>>();
const avl = new AVLTree<{id: number; text: string}>();
beforeEach(() => {
avl.clear();
});

View file

@ -189,7 +189,7 @@ describe('BST operations test', () => {
});
it('should perform various operations on a Binary Search Tree with object values', () => {
const objBST = new BST<BSTNode<{key: number; keyA: number}>>();
const objBST = new BST<{key: number; keyA: number}>();
expect(objBST).toBeInstanceOf(BST);
objBST.add(11, {key: 11, keyA: 11});
objBST.add(3, {key: 3, keyA: 3});

View file

@ -29,7 +29,7 @@ describe('Overall BinaryTree Test', () => {
bfsIDs[0] === 11; // true
expect(bfsIDs[0]).toBe(11);
const objBST = new BST<BSTNode<{key: number; keyA: number}>>();
const objBST = new BST<{key: number; keyA: number}>();
objBST.add(11, {key: 11, keyA: 11});
objBST.add(3, {key: 3, keyA: 3});

View file

@ -206,7 +206,7 @@ describe('TreeMultiset operations test', () => {
});
it('should perform various operations on a Binary Search Tree with object values', () => {
const objTreeMultiset = new TreeMultiset<TreeMultisetNode<{key: number; keyA: number}>>();
const objTreeMultiset = new TreeMultiset<{key: number; keyA: number}>();
expect(objTreeMultiset).toBeInstanceOf(TreeMultiset);
objTreeMultiset.add(11, {key: 11, keyA: 11});
objTreeMultiset.add(3, {key: 3, keyA: 3});