UNPKG

@dsinjs/binary-tree

Version:
807 lines (750 loc) 24.3 kB
import { BTreeNode } from "./btreenode"; declare let module: any; export declare class ExtendedWindow extends Window { DSinJS: { BTreeNode: typeof BTreeNode; BTree: typeof BTree; }; BTreeNode: typeof BTreeNode; BTree: typeof BTree; } declare let window: ExtendedWindow; export declare class BTreeNodeStruct<T> { value?: T | null; lNode?: BTreeNodeStruct<T> | null; rNode?: BTreeNodeStruct<T> | null; } declare class BTreeRootAttrStruct<T> { root?: T; } declare class BTreeValueAttrStruct<T> { value?: T; } class UnreachableError extends Error { constructor(msg: string) { super(msg); this.name = "UnreachableError"; } } class FilteredRootError extends Error { constructor(msg: string) { super(msg); this.name = "FilteredRootError"; } } // if Symbol is not available in window if (typeof window !== "undefined") { if (typeof Symbol === "undefined") { let win: any = window; win.Symbol = {}; win.Symbol.iterator = "==iterator=="; } } /** * BTree main class * @class * @public * @example * new BTree(10); * new BTree({ root: 10 }); * new BTree({ value: 10 }); */ export class BTree<T = any> { /** * Root node of the binary tree. * @type {BTreeNode} * @property root */ root: BTreeNode<T>; /** * Depth of the binary tree. * @type {number} * @property depth */ depth: number = 1; /** * Constructor for Binary Tree. * @param {BTreeRootAttrStruct|BTreeValueAttrStruct|T} attr Can be of type object, string, number. In case of object root/value property is expected to be value of root node. * @constructor */ constructor(attr: BTreeRootAttrStruct<T> | BTreeValueAttrStruct<T> | T) { if (typeof attr == "object" && typeof (attr as BTreeRootAttrStruct<T>).root !== "undefined") { this.root = new BTreeNode<T>({ value: (attr as BTreeRootAttrStruct<T>).root }); } else if (typeof attr == "object" && typeof (attr as BTreeValueAttrStruct<T>).value !== "undefined") { this.root = new BTreeNode<T>({ value: (attr as BTreeValueAttrStruct<T>).value }); } else { this.root = new BTreeNode<T>({ value: attr as T }); } this.depth = this.root.getDepth(); } /** * Returns string value of given tree. * @method toString * @member * @public * @example * var tree = new BTree(10); * tree.insert(10); * tree.insert(20); * tree.insert(30); * tree.toString(); // "10102030" */ toString() { return this.root.toString(); } /** * Returns JSON Form. * @method toJSON * @member * @public * @returns {BTreeNodeStruct} Returns json form of a given tree. * @example * var tree = new BTree(10); * tree.insert(20); * tree.toJSON(); // {value:10,lNode:{value:20,lNode:null,rNode:null},rNode:null} */ toJSON() { return this.root.toJSON(); } /** * Returns array value. * @method toArray * @member * @public * @returns {Array<BTreeNode>} Returns array form of given tree. * @example * var tree = new BTree(10); * tree.insert(20); * tree.toArray(); // => [{value:10,...},{value:20,...}] */ toArray(): BTreeNode<T>[] { const arr: BTreeNode<T>[] = []; this.each((node: BTreeNode<T>, index: number) => { arr.push(node); }); return arr; } /** * Returns array of values of the tree. * @method toFlatArray * @member * @public * @returns {Array<T>} Returns array form of given tree. * @example * var tree = new BTree(10); * tree.insert(20); * tree.toFlatArray(); // => [10,20] */ toFlatArray(): T[] { const arr: T[] = []; this.each((node: BTreeNode<T>, index: number) => { arr.push(node.value as T); }); return arr; } /** * Inserts the given value to the tree where first free left child node is found. * @param {any} val any type of value to be added to tree node. * @returns {BTreeNode} Returns newly created BTreeNode. * @method insert * @member * @example * var tree = new BTree(10); * tree.insert(10); * tree.insert(20); * tree.insert(30); * tree.toString(); // "10102030" */ insert(val: T) { return this.insertLeftMost(val); } /** * Inserts the given value to the tree where first free left child node is found. * @param {T} val any type of value to be added to tree node. * @method insertLeftMost * @member * @returns {BTreeNode<T>} Returns newly created BTreeNode. */ insertLeftMost(val: T): BTreeNode<T> { let node = this.root; while (node.lNode != null) { node = node.lNode; } node.lNode = new BTreeNode<T>({ value: val }); this.depth = this.root.getDepth(); return node.lNode; } /** * Inserts the given value to the tree where first free right child node is found. * @param {T} val any type of value to be added to tree node. * @method insertRightMost * @member * @public * @returns {BTreeNode<T>} Returns newly created BTreeNode. */ insertRightMost(val: T) { let node = this.root; while (node.rNode != null) { node = node.rNode; } node.rNode = new BTreeNode<T>({ value: val }); this.depth = this.root.getDepth(); return node.rNode; } /** * Deletes given value from tree. * Travarsal = Root -> L -> R. * @param {T} val Value to be removed. * @returns {BTreeNode<T>} Returns removed BTreeNode. * @method delete * @member * @public */ delete(val: T): BTreeNode<T> { /** * @private * @param {BTreeNode<T>} currNode Current node. * @returns {BTreeNode<T>} Returns removed BTreeNode. */ const recDel = (currNode: BTreeNode<T>): BTreeNode<T> | null => { if (currNode == null) { return currNode; } let cacheRetLeft = currNode.lNode; let cacheRetRight = currNode.rNode; if (cacheRetLeft == null && cacheRetRight == null) { return null; } if (currNode.lNode && currNode.lNode.value === val) { currNode.lNode = null; return cacheRetLeft; } if (currNode.rNode && currNode.rNode.value === val) { currNode.rNode = null; return cacheRetRight; } const delL = recDel(currNode.lNode as BTreeNode<T>); const delR = (!delL) ? recDel(currNode.rNode as BTreeNode<T>) : null; return delL || delR; }; const delItem = recDel(this.root); this.depth = this.root.getDepth(); return delItem as BTreeNode<T>; } /** * Inserts given element at given location. If location is already taken then it does not insert any value. * @param {T} val value to insert. * @param {number} index index at which to append new element to. * @method insertAt * @member * @public * @throws UnreachableError * @example * tree.insertAt(20,2); */ insertAt(val: T, index: number) { const path = BTree.getPathFromIndex(index); let currNode = this.root; for (const [index, item] of path.entries()) { if (item === 'L') { if (currNode.lNode == null && path.length !== index + 1) { throw new UnreachableError('Given index cannot be reached'); } else if (currNode.lNode == null) { currNode.lNode = new BTreeNode({ value: val }); } else { currNode = currNode.lNode; } } if (item === 'R') { if (currNode.rNode == null && path.length !== index + 1) { throw new UnreachableError('Given index cannot be reached'); } else if (currNode.rNode == null) { currNode.rNode = new BTreeNode({ value: val }); } else { currNode = currNode.rNode; } } } this.depth = this.root.getDepth(); } /** * Breadth first search. Executes given callback functions with parameters BTreeNode and path index for each node in BFS fashion. * @param {{(node: BTreeNode<T>, index: number) => any}} callback A callback function for execution of each node. * @method traverseBFS * @member * @public * @returns {void} no value. */ traverseBFS(callback: (node: BTreeNode<T>, index: number) => any): void { let currCount = 0; const children: { node: BTreeNode<T>, path: Array<'U' | 'L' | 'R'> }[] = []; /** * * @param {BTreeNode<T>} currNode current node in recursion. * @private */ const recInser = (currNode: BTreeNode<T>, currPath: Array<'U' | 'L' | 'R'>): void => { if (currNode != null) { let currPathL: Array<'U' | 'L' | 'R'> = JSON.parse(JSON.stringify(currPath)); currPathL.push('L'); let currPathR: Array<'U' | 'L' | 'R'> = JSON.parse(JSON.stringify(currPath)); currPathR.push('R'); children.push({ node: currNode.lNode as BTreeNode<T>, path: currPathL }); children.push({ node: currNode.rNode as BTreeNode<T>, path: currPathR }); callback(currNode, BTree.getIndexFromPath(currPath)); } currCount++; if (children.length) { const item = children.splice(0, 1)[0]; return recInser(item.node, item.path); } else { return; } }; recInser(this.root, ['U']); } /** * Depth first search, Executes given callback functions with parameters BTreeNode and path index for each node in DFS fashion. * @param {{(node: BTreeNode<T>, index: number) => any}} callback A callback function for execution of each node. * @method traverseDFS * @member * @public * @returns {void} no value. */ traverseDFS(callback: (node: BTreeNode<T>, index: number) => any): void { /** * * @param {BTreeNode<T>} currNode Currently processing node. * @param {Array<'U'|'L'|'R'>} path current path * @private */ const recFnc = (currNode: BTreeNode<T>, path: Array<'U' | 'L' | 'R'>) => { if (currNode !== null) { callback(currNode, BTree.getIndexFromPath(path)); if (currNode.lNode !== null) { let lPath = JSON.parse(JSON.stringify(path)); lPath.push('L'); recFnc(currNode.lNode, lPath); } if (currNode.rNode !== null) { let rPath = JSON.parse(JSON.stringify(path)); rPath.push('R'); recFnc(currNode.rNode, rPath); } } }; recFnc(this.root, ['U']); } /** * Breadth first search. Executes given callback functions with parameters BTreeNode and path index for each node in BFS fashion. * @param {{(node: BTreeNode<T>, index: number) => any}} callback A callback function for execution of each node. * @method each * @member * @public * @returns {void} no value. */ each(callback: (node: BTreeNode<T>, index: number) => any): void { return this.traverseBFS(callback); } /** * Breadth first search. Executes given callback functions with parameters BTreeNode and path index for each node in BFS fashion. * @param {{(node: BTreeNode<T>, index: number) => any}} callback A callback function for execution of each node. * @method forEach * @member * @public * @returns {void} no value. */ forEach(callback: (node: BTreeNode<T>, index: number) => any): void { return this.traverseBFS(callback); } /** * Returns an iterable of key, value pairs for every entry in the tree. * @method [Symbol.iterator] * @member * @public * @example * var tree = new BTree(10); * for (const node of tree) { * console.log(node.value); // 10 * } */ [Symbol.iterator]() { let curr = -1; const arr = this.toArray(); return { /** * @returns { {value: BTreeNode<T>, done: boolean} } * @private */ next(): { value: BTreeNode<T>; done: boolean; } { curr++; return { value: (arr[curr] === void 0) ? void 0 as any : arr[curr], done: !!(curr === arr.length) }; } } } /** * Returns an iterable of key, value pairs for every entry in the tree. * @method entries * @member * @public * @returns {IterableIterator<[number, BTreeNode<T>]>} Iterable for iterations. * @example * var tree = new BTree(10); * for (const [index, node] of tree.entries()) { * console.log(index, node.value); // 0, 10 * } */ entries(): IterableIterator<[number, BTreeNode<T>]> { return this.toArray().entries(); } /** * Maps current tree values to a new tree with modifying the values using given callback function. * Uses BFS. * @param {{(value: T) => T}} callback callback function for value modifier. * @method map * @member * @public * @returns {BTree<T>} A new BTree * @example * var tree = BTree.fromArray([10, 20, 30, 40]); * var tree2 = tree.map(n => n * 2); * var arr2 = tree2.toArray(); // [{value:20,...},{value:40,...},{value:60,...},{value:80,...}] */ map(callback: (value: T) => T): BTree<T> { const newTree = new BTree(callback(this.root.value as T)); this.each((node, index) => { if (index !== 1) { const retVal = callback(node.value as T); newTree.insertAt(retVal, index); } }); return newTree; } /** * Filters each item based on given filter function * @param {{(value: T) => boolean}} filterFnc callback function for filtering purpose. * @method filter * @member * @public * @throws FilteredRootError, Error when root node gets filtered out. * @returns {BTree<T>} New filtered instance of tree. * @example * var tree = BTree.fromArray([10, 20, 30, 40]); * var tree2 = tree.filter(n => !!(n % 4 === 0 || n === 10)); * var arr2 = tree2.toArray(); // [{value:10,...},{value:20,...},{value:40,...}] */ filter(filterFnc: (value: T) => boolean): BTree<T> { if (!filterFnc(this.root.value as T)) { throw new FilteredRootError("Root node cannot be filtered. If you want to filter out root node, you can use emptry BTree instance."); } const newTree = new BTree(this.root.value as T); this.each((node, index) => { if (index !== 1) { const canBeInserted = filterFnc(node.value as T); if (canBeInserted) { newTree.insertAt(node.value as T, index); } } }); return newTree; } /** * Reduces each node values using reduceFunction and returns final value. * @param {(next: T2, value: T, index: number, tree: BTree<T>) => T2} reduceFunction callback function for reducing each node value to a final value. * @param {T2} initialValue Optional, Accumulator/Initial value. * @method reduce<T2> * @member * @public * @returns {T2} Returns reduceed value * @example * var tree = BTree.fromArray([10, 20, 30, 40]); * var sum = tree.reduce((acc, node) => acc + node); // => 100 */ reduce<T2 = any>(reduceFunction: (next: T2, value: T, index: number, tree: BTree<T>) => T2, initialValue: T2 = 0 as any): T2 { let next = initialValue; this.each((node: BTreeNode<T>, index) => { next = reduceFunction(next, node.value as T, index, this); }); return next; } /** * Reverses the current Binary Tree, Left Node becomes Right node and vise versa. * Does not return new instance, returns current tree instance. * @method reverse * @member * @public * @returns {BTree<T>} Returns current tree instance. * @example * var tree = BTree.fromArray([10, 20, 30, 40, 50, 60, 70, 80]); * tree.reverse().toArray(); // => [10, 30, 20, 70, 60, 50, 40, 80] */ reverse(): BTree<T> { const trav = (currNode: BTreeNode<T>) => { if (currNode === null) { return; } const temp = currNode.lNode; currNode.lNode = currNode.rNode; currNode.rNode = temp; trav(currNode.lNode as BTreeNode<T>); trav(currNode.rNode as BTreeNode<T>); }; trav(this.root); return this; } /** * Returns first index of a value matched, if it is not present, it returns -1. * @param {T} value Any value to find. * @method indexOf * @member * @public * @returns {number} Returns index of given item. * @example * var tree = BTree.fromArray([10, 20, 30, 40, 50, 60, 70, 80]); * tree.indexOf(30); // => 3 * tree.indexOf(51); // => -1 */ indexOf(value: T): number { let retIndex = -1; this.each((node, index) => { if (node.value === value && retIndex === -1) { retIndex = index; } }); return retIndex; } /** * Checks if given item exists or not, returns boolean. * @param {T} value Any value to check if it exists or not. * @method includes * @member * @public * @returns {boolean} Returns true if it is present, otherwise false. * @example * var tree = BTree.fromArray([10, 20, 30, 40, 50, 60, 70, 80]); * tree.includes(30); // true * tree.includes(51); // false */ includes(value: T): boolean { return this.indexOf(value) !== -1; } /** * Checks if given item exists or not, returns boolean. * @param {T} value Any value to check if it exists or not. * @method exists * @member * @public * @returns {boolean} Returns true if it is present, otherwise false. * @example * var tree = BTree.fromArray([10, 20, 30, 40, 50, 60, 70, 80]); * tree.exists(30); // true * tree.exists(51); // false */ exists(value: T): boolean { return this.indexOf(value) !== -1; } /** * Checks if given item exists or not, returns boolean. * @param {T} value Any value to check if it exists or not. * @method has * @member * @public * @returns {boolean} Returns true if it is present, otherwise false. * @example * var tree = BTree.fromArray([10, 20, 30, 40, 50, 60, 70, 80]); * tree.has(30); // true * tree.has(51); // false */ has(value: T): boolean { return this.indexOf(value) !== -1; } /** * Sorts the tree based on compare function, Has option to sort only at children level. * @param {Function} compareFnc Function used to determine the order of the elements. It is expected to return * a negative value if first argument is less than second argument, zero if they're equal and a positive * value otherwise. If omitted, the elements are sorted in ascending, ASCII character order. * ```ts * (a, b) => a - b) * ``` * @param {boolean} atOnlyFirstChildLevel Optiona, Flag to specify if first child of each node should sorted. Default is `false`. * @method sort * @member * @public * @returns {void} Returns undefined. * @example * var tree = BTree.fromArray([10, 200, 100, 50, 60, 90, 5, 3]); * tree.sort().toFlatArray(); // => [3,5,10,50,60,90,100,200] */ sort(compareFnc: <T2>(a: T2, b: T2) => number = (a = 0 as any, b = 0 as any) => { return (a < b) ? -1 : (a == b) ? 0 : 1; }, atOnlyFirstChildLevel: boolean = false): void { if (atOnlyFirstChildLevel) { const DFS = (node: BTreeNode<T>) => { if (node !== null) { const out = compareFnc(node.lNode, node.rNode); if (out > 0) { const temp = node.lNode; node.lNode = node.rNode; node.rNode = temp; } DFS(node.lNode as BTreeNode<T>); DFS(node.rNode as BTreeNode<T>); } }; DFS(this.root); } else { const arr: T[] = []; const arrBFS: BTreeNode<T>[] = []; let counter = 0; const children: BTreeNode<T>[] = []; const BFS = (node: BTreeNode<T>) => { if (node !== null && node.lNode !== null) { children.push(node.lNode); } if (node !== null && node.rNode !== null) { children.push(node.rNode); } if (node !== null) { arrBFS.push(node); arr.push(node.value as T); } if (children.length !== 0) { const first = children[0]; children.splice(0, 1); BFS(first); } }; BFS(this.root); while (arr.length !== 0) { let min = arr[0]; let minIndex = 0; for (let i = 1; i < arr.length; i++) { const out = compareFnc(min, arr[i]); if (out > 0) { min = arr[i]; minIndex = i; } } arrBFS[counter].value = min; arr.splice(minIndex, 1); counter++; } } } /** * Prints entire tree on the console, useful for logging and checking status. * @method print * @member * @public * @returns {void} Returns undefined. * @example * var tree = BTree.fromArray([1, 2, 3]); * tree.print(); * 1 (1) * |- 2 (2) * |- 3 (3) */ print(): void { let isit = false; this.traverseDFS((node, index) => { const len = BTree.getPathFromIndex(index).length; const isFirst = (isit) ? " |-".repeat(len - 1) : ""; console.log(isFirst, node.value, "(" + index + ")"); isit = true; }); } /** * Returns the first matched tree node. Traverses using BFS. * @param {T} item any value to find inside the tree. * @method find * @member * @public * @returns {BTreeNode<T> | null} Returns the first matched tree node, if not found, returns null. * @example */ find(item: T): BTreeNode<T> | null { let retNode: BTreeNode<T> | null = null; this.each((node, index) => { if (node.value === item && retNode === null) { retNode = node; } }); return retNode; } /** * Returns index value from given path. * @param {Array<'U'|'L'|'R'>} path Array for U L or R, which represents the quickest path to get to a node. * @returns {number} Returns index value. * @method getIndexFromPath * @public * @static * @member */ static getIndexFromPath(path: Array<'U' | 'L' | 'R'>): number { path = JSON.parse(JSON.stringify(path)); let score = 1; while (path.length != 0) { if (path[0] === 'U') { path.splice(0, 1); } else if (path[0] === 'L') { score = score * 2; path.splice(0, 1); } else if (path[0] === 'R') { score = score * 2 + 1; path.splice(0, 1); } } return score; } /** * Returns Path equivalent to the given index. * @param {number} index Index number from which path to be calculated. * @returns {Array<'U'|'L'|'R'>} Path equivalent to the given index. * @method getPathFromIndex * @public * @static */ static getPathFromIndex(index: number): Array<'U' | 'L' | 'R'> { let path: Array<'U' | 'L' | 'R'> = []; while (index != 1) { if (index % 2 === 0) { path.push('L'); } else { path.push('R'); index = index - 1; } index = index / 2; } path.push('U'); path = path.reverse(); return path; } /** * Converts given values into a Binary Tree. * @param {T2[]} arr Any array of values. * @returns {BTree<T2>} Newly generated tree. * @method fromArray * @static * @public * @example * var tree = BTree.fromArray([10,20,30,40]); */ static fromArray<T2>(arr: T2[]): BTree<T2> { const newArr = JSON.parse(JSON.stringify(arr)); const tree = new BTree<T2>(newArr[0]); for (const [index, item] of arr.entries()) { if (index !== 0) { tree.insertAt(item, index + 1); } } return tree; } } if (typeof module != "undefined") { module.exports = { BTree, BTreeNode }; } if (typeof window != "undefined") { window.DSinJS = window.DSinJS || {}; window.DSinJS.BTree = BTree; }