nope/modules/binary-tree/src/tree.ts
2020-11-16 13:06:45 +01:00

314 lines
9.1 KiB
TypeScript

/**
* @author Martin Karkowski
* @email m.karkowski@zema.de
* @create date 2018-06-08 11:29:49
* @modify date 2020-11-13 16:12:16
* @desc [description]
*/
import { inject, injectable, interfaces } from 'inversify';
import { memoize } from 'lodash';
import { generateId } from '../../../lib/helpers/idMethods';
import { SPLITCHAR } from '../../../lib/helpers/objectMethods';
import { INodeOfTree, ITree } from '../type/interfaces';
import * as TREE from '../type/types';
/**
* A Tree containing all Nodes
*
* @export
* @class BaseTree
* @implements {ITree<T>}
* @template T
*/
@injectable()
export class BaseTree<T extends INodeOfTree<T>> implements ITree<T> {
protected _nodes: Set<T>;
protected _nodesByPath: Map<string, T>
protected _getSegmentsOfNodePath: (path: string) => string[];
/**
*Creates an instance of BaseTree.
* @param {interfaces.Newable<T>} _nodeCtor
* @param {IIdGenerator} _idGenerator
* @memberof BaseTree
*/
constructor(
@inject(TREE.TYPES.INodeOfTreeFactory) protected _nodeFactory: () => T,
) {
const _self = this;
/** Create an Array. */
this._nodes = new Set<T>();
this._nodePathes = new Set<string>();
this._nodesByPath = new Map<string, T>();
/** Disable Memoize in the Beginning */
this._getSegmentsOfNodePath = memoize(
(_path: string) => _path.split('.')
);
/** Create a Memoized Function for generate a Child */
this.lazyGetNode = memoize((_path: string) => _self._lazyGetNode(_path));
}
protected updateMap(): void {
/** Search the Node */
this._nodesByPath.clear()
for (const _node of this._nodes) {
/** Check whether the required Path Matches the Path */
this._nodesByPath.set(_node.path, _node);
}
}
/**
* Generate A Child.
*
* @param {string} path
* @returns {T}
* @memberof BaseNodeTree
*/
protected _lazyGetNode(path: string): T {
if (path.length === 0) {
throw Error('A Path must be given');
}
/** Search the Node */
let _ret: T | null = this._nodesByPath.has(path) ? this._nodesByPath.get(path) as T : null;
if (_ret === null) {
/** The Node wasn't found ==> Extract the Effected Elements */
const _segmentsOfPath = this._getSegmentsOfNodePath(path);
/** Create the Structure. */
for (const [_idx, _segment] of _segmentsOfPath.entries()) {
const _pathToNode = _segmentsOfPath.slice(0, _idx + 1).join(SPLITCHAR);
/** Try to Find the Node */
let _node = this._nodesByPath.get(_pathToNode);
if (_node === undefined) {
/** Node is not defined */
/** Create a Node */
_node = this._nodeFactory();
_node.segment = _segment;
/** Set its Parent */
_node.parent = _ret;
if (_ret) {
/** Mark as Child on the Parent */
_ret.addDirectSubNode(_node);
}
/** Add the Node */
this._addNode(_node);
}
_ret = _node;
}
}
/** Return the Node */
return _ret as T;
}
public lazyGetNode: (path: string) => T;
public insertNode(path: string, segment: string) {
if (path.length > 0) {
const _toAdapt = this.lazyGetNode(path);
const _childsToAdapt = Array.from(_toAdapt.getDirectSubNodes());
/** Delete All Childs. */
_toAdapt.getDirectSubNodes().clear();
const _insertion = this.lazyGetNode(path + SPLITCHAR + segment);
/** Change the Parent and Add the Childs to the Insertion */
for (const _child of _childsToAdapt) {
_child.parent = _insertion;
_child.path = '';
}
/** Delete the Memoized Cache */
(this.lazyGetNode as any).cache.clear();
if (typeof this.onInsertNode === 'function') {
this.onInsertNode(_insertion);
}
} else {
/** A common root should be entered. */
const _insertion = this.lazyGetNode(segment);
const _roots = Array.from(this.rootNodes);
for (const _root of _roots) {
/** Prevent Endless Loops by checking the Identity */
if (_root != _insertion) {
_root.parent = _insertion;
_insertion.addDirectSubNode(_root);
}
}
/** Update All Pathes */
for (const _node of this._nodes) {
_node.path = '';
}
}
}
public removeNode(path: string, count: number | 'all' = 'all') {
/** Get the path of the parent */
if (path != '') {
const _segments = this._getSegmentsOfNodePath(path);
/** Define the number of segments, that should be removed */
let counter = (count == 'all' ? _segments.length : (typeof count === 'number' ? count : 1));
let _affectParentNode: T | null = null;
const _affectedChildNodes = Array.from(this.lazyGetNode(path).getDirectSubNodes());
/** Generate the Path of the Parent */
let _parentPath = '';
while (counter > 0) {
/** Drop the Last Element */
_segments.pop();
/** Get the node which should be Removed.*/
const _nodeToRemove = this.lazyGetNode(path);
/** Generate the Path of the Parent */
_parentPath = _segments.join(SPLITCHAR);
/** Adapt the Counter */
counter -= 1;
/** Determine the Parent Node */
_affectParentNode = _parentPath.length > 0 ? this.lazyGetNode(_parentPath) : null;
if (_affectParentNode !== null) {
_affectParentNode.removeDirectSubNode(_nodeToRemove);
}
/** Delete the Memoized Cache */
(this.lazyGetNode as any).cache.clear();
}
/** Change the Parent and Add the Childs to the _affectParentNode */
for (const _child of _affectedChildNodes) {
_child.parent = _affectParentNode;
if (_affectParentNode)
_affectParentNode.addDirectSubNode(_child);
}
/** Delete the Memoized Cache */
(this.lazyGetNode as any).cache.clear();
if (typeof this.onRemoveNode === 'function') {
this.onRemoveNode(_affectParentNode as T);
}
} else {
console.log((new Date()).toISOString(), '- error - Path to remove a Node of a Tree must be setted.');
throw Error('Path to remove a Node of a Tree must be setted!');
}
}
protected _addNode(_node: T) {
/** Add the Node */
this._nodes.add(_node);
this._nodePathes.add(_node.path);
this._nodesByPath.set(_node.path, _node);
/** Fire an Event, that the new Node has been created. */
if (typeof this.onNewNode === 'function') {
this.onNewNode(_node);
}
}
protected _removeNode(_node: T) {
/** Add the Node */
this._nodes.delete(_node);
this._nodePathes.delete(_node.path);
this._nodesByPath.delete(_node.path);
// (this._lazyFindNode as any).cache.delete(_node.path);
if (typeof this.onRemoveNode === 'function' && _node.parent) {
this.onRemoveNode(_node.parent as T);
}
}
/**
* List Returning all Leafs
*
* @readonly
* @type {Array<T>}
* @memberof BaseTree
*/
public get leafNodes(): Set<T> {
const _ret = new Set<T>();
Array.from(this._nodes).filter((node) => (node.getDirectSubNodes().size === 0 ? node : undefined)).map((node) => _ret.add(node));
return _ret;
}
/**
* Returns all contained Nodes
*
* @readonly
* @type {Array<T>}
* @memberof BaseTree
*/
public get nodes(): Set<T> {
return this._nodes;
}
/**
* Returns a List containing all Roots.
*
* @readonly
* @type {Array<T>}
* @memberof BaseTree
*/
public get rootNodes(): Set<T> {
const _ret = new Set<T>();
Array.from(this._nodes).filter((node) => (node.parent == undefined ? node : undefined)).map((node) => _ret.add(node));
return _ret;
}
protected _nodePathes: Set<string>;
/**
* Getter to get all Ids of the Provided Nodes.
*
* @readonly
* @type {Array<string>}
* @memberof BaseTree
*/
public get NodePathes(): Set<string> {
return this._nodePathes;
}
public onNewNode: (_topic: T) => void = () => { };
public onInsertNode: (insertedNode: T) => void = () => { };
public onRemoveNode: (parentOfremovedNode: T) => void = () => { };
}