UNPKG

simple-tree-utils

Version:

Simple Tree Utils is the library to convert and manipulate with tree-like structures.

439 lines (438 loc) 14.9 kB
const p = "id", P = "parentId", u = "children"; class o { /** * Constructor of class * @param config - to configure class, if configuration option is omitted, default one is used */ constructor(e) { this.idProp = (e == null ? void 0 : e.idProp) || p, this.parentIdProp = (e == null ? void 0 : e.parentIdProp) || P, this.childrenProp = (e == null ? void 0 : e.childrenProp) || u; } /** * Convert list to tree like structure * @param list list of objects, objects need to have id (as you configured, or 'id' by default) and parentId property (as you configured, or 'parentId' by default) * @param rootParentId - id of root parent nodes (if not specified, root nodes are nodes with parentId of null) * @returns tree structure */ list2Tree(e, t = null) { return e.filter((r) => r[this.parentIdProp] === t).map((r) => ({ ...r, [this.childrenProp]: this.list2Tree(e, r[this.idProp]) })); } /** * Convert tree like structure to list * @param tree - tree of objects, objects need to have children (as you configured, or 'children' by default) and parentId property (as you configured, or 'parentId' by default) * @returns list */ tree2List(e) { return this._tree2List(o.deepCopy(e)); } /** * Convert tree like structure to list (helper method) * @param tree tree of objects, objects need to have children (as you configured, or 'children' by default) and parentId property (as you configured, or 'parentId' by default) * @param parentId - id of parent node * @private * @returns list */ _tree2List(e, t = null) { return o.deepCopy(e).reduce((r, s) => { const { [this.childrenProp]: i, ...h } = s; return [ ...r, { ...h, [this.parentIdProp]: t }, ...i != null && i.length ? this._tree2List(i, h[this.idProp]) : [] ]; }, []); } /** * Method to get node in tree structure by given id * @param tree - tree structure to search in * @param id - identifier of node * @returns found node */ get(e, t) { return this.find(e, (r) => r[this.idProp] === t); } /** * Method to find node in tree structure by given callback function * @param tree - tree structure to search in * @param fn - callback function to find node * @returns found node * @example * ```ts * utils.find(tree, item => item.id === myId); * ``` */ find(e, t) { const r = e.find((s) => t(s)); return r || e.reduce( (s, i) => s || this.find(i[this.childrenProp] || [], t), null ); } /** * Method to iterate over all nodes * @param tree - tree structure to iterate over * @param fn - callback function to perform */ forEach(e, t) { e == null || e.forEach((r) => { t(r), this.forEach(r[this.childrenProp], t); }); } /** * Method to find all nodes in tree structure by given callback function * @param tree - tree structure to search in * @param fn - callback function to find all nodes * @returns all found nodes * @example * ```ts * utils.filter(tree, item => item.id === myId); * ``` */ filter(e, t) { const r = e.filter((s) => t(s)); return e.reduce((s, i) => [...s, ...i[this.childrenProp].length ? this.filter(i[this.childrenProp], t) : []], r); } /** * Method to delete node in tree by given id (mutable operation!) * @param tree - tree structure for node deleting * @param id - identifier of node to delete * @returns deleted node, if nothing deleted then returns null */ delete(e, t) { return this.deleteBy(e, (r) => r[this.idProp] === t)[0] || null; } /** * Method to delete node in tree by given callback function (mutable operation!) * @param tree - tree structure for node deleting * @param fn - callback function to remove all nodes * @returns deleted nodes * @example * ```ts * utils.deleteBy(tree, item => item.id === myId); * ``` */ deleteBy(e, t) { const s = e.map((i, h) => ({ index: h, filter: t(i) })).filter(({ filter: i }) => i).map(({ index: i }) => i).reverse().reduce((i, h) => [...i, ...e.splice(h, 1)], []); return e.reduce((i, h) => [...i, ...this.deleteBy(h[this.childrenProp] ?? [], t)], s); } /** * Method to add new node to tree, node will be added as last child (mutable operation!) * @param tree - tree structure for node adding * @param parentId - identifier of parent node, null if new node should be on root level * @param childData - data of new node * @param anotherChildData - data of new additional nodes */ add(e, t, r, ...s) { this._add("push", e, t, r, ...s); } /** * Method to add new node to tree, node will be added as first child (mutable operation!) * @param tree - tree structure for node adding * @param parentId - identifier of parent node, null if new node should be on root level * @param childData - data of new node * @param anotherChildData - data of new additional nodes */ addUnshift(e, t, r, ...s) { this._add("unshift", e, t, r, ...s); } /** * Method to add new node to tree at the end or at the beginning (helper method) * @param operation - how item should be added into tree * @param tree - tree structure for node adding * @param parentId - identifier of parent node, null if new node should be on root level * @param childData - data of new node * @param anotherChildData - data of new additional nodes */ _add(e, t, r, s, ...i) { if (r == null) { t[e](s, ...i); return; } const h = t.findIndex((n) => n[this.idProp] == r); if (h != -1) { t[h][this.childrenProp][e]( { [this.childrenProp]: [], ...s }, ...i.map((n) => ({ [this.childrenProp]: [], ...n })) ); return; } t.forEach((n) => this.add(n[this.childrenProp], r, s, ...i)); } /** * Method to update node by id with given data in tree (mutable operation!) * @param tree - tree structure for node editing * @param id - identifier of node to be updated * @param data - new data of node (you should also pass children if you want to keep it) */ edit(e, t, r) { const s = e.findIndex((i) => i[this.idProp] == t); if (s != -1) { e[s] = { [this.idProp]: e[s][this.idProp], [this.childrenProp]: [], ...r }; return; } e.forEach((i) => this.edit(i[this.childrenProp], t, r)); } /** * Method to get descendant nodes of given node in tree structure * @param tree - tree structure to search in * @param id - identifier of node * @returns all found children nodes */ getDescendants(e, t) { const r = this.get(e, t); return r ? this._getDescendants(r) : []; } /** * Helper method to recursively get all descendant nodes of given node in tree structure * @param node - we want to get all of its children * @private * @returns all found children nodes */ _getDescendants(e) { return [ ...e[this.childrenProp], ...e[this.childrenProp].reduce((t, r) => [...t, ...this._getDescendants(r)], []) ]; } /** * Method to get ancestors of given node in tree structure * @param tree - tree structure to search in * @param id - identifier of node * @returns all found parent nodes */ getAncestors(e, t) { const r = []; let s = this.getParent(e, t); for (; s; ) r.push(s), s = this.getParent(e, s[this.idProp]); return r.reverse(); } /** * Method to get nodes that are part of path from root * Alias for {@link TreeUtils.getAncestors | getAncestors} method * @param tree - tree structure to search in * @param id - identifier of node * @returns all nodes that are part of path (ordered from root) */ getPathNodes(e, t) { return this.getAncestors(e, t); } /** * Method to get parent of given node in tree structure * @param tree - tree structure to search in * @param id - identifier of node * @returns found parent node, otherwise null */ getParent(e, t) { return this._getParent(e, t); } /** * Method to get parent of given node in tree structure * @param tree - tree structure to search in * @param id - identifier of node * @param parent - parent node, if we found something (for recursion only) * @returns found parent node, otherwise null */ _getParent(e, t, r = null) { return e.find((i) => i[this.idProp] === t) ? r : e.reduce( (i, h) => i || this._getParent(h[this.childrenProp] || [], t, h), null ); } /** * Method to get children of given node in tree structure * @param tree - tree structure to search in * @param id - identifier of node * @returns children nodes of node */ getChildren(e, t) { var r; return ((r = this.get(e, t)) == null ? void 0 : r[this.childrenProp]) || []; } /** * Method to get neighbours (neighbour is parent or child) of given node in tree structure * @param tree - tree structure to search in * @param id - identifier of node * @returns neighbours of node */ getNeighbours(e, t) { return [this.getParent(e, t), ...this.getChildren(e, t)].filter((r) => r); } /** * Method to get siblings of given node in tree structure * @param tree - tree structure to search in * @param id - identifier of node * @returns siblings of node */ getSiblings(e, t) { var r; return (((r = this.getParent(e, t)) == null ? void 0 : r[this.childrenProp]) || []).filter((s) => s[this.idProp] !== t); } /** * Method to get leafs of subtree from given node * @param tree - tree structure to search in * @param id - identifier of node * @returns leafs of nodes */ getLeafs(e, t) { return this.filter(this.getSubTree(e, t), (r) => !r[this.childrenProp].length); } /** * Method to get subtree from given node (children of node) * Alias for {@link TreeUtils.getChildren | getChildren} method * @param tree - tree structure to search in * @param id - identifier of node * @returns subtree */ getSubTree(e, t) { return this.getChildren(e, t); } /** * Method to get size of subtree (number of nodes in the subtree) * @param tree - tree structure * @param id - identifier of node * @returns size of subtree */ getSize(e, t) { return this.tree2List(this.getSubTree(e, t)).length + 1; } /** * Method to get breath of subtree (the number of leaves in subtree) * @param tree - tree structure * @param id - identifier of node * @returns breath of subtree */ getBreath(e, t) { return this.getLeafs(e, t).length; } /** * Method to get depth of node (the depth of a node is the length of the path to its root i.e., its root path) * @param tree - tree structure * @param id - identifier of node * @returns depth of node */ getDepth(e, t) { return this.getPathNodes(e, t).length; } /** * Method to get level of node (the level of a node is the number of edges along the unique path between it and the root node) * @param tree - tree structure * @param id - identifier of node * @returns level of node */ getLevel(e, t) { return this.getDepth(e, t) + 1; } /** * Method to get degree of node (for a given node, its number of children. A leaf, by definition, has degree zero) * @param tree - tree structure * @param id - identifier of node * @returns degree of node */ getDegree(e, t) { return this.getChildren(e, t).length; } /** * Method to get degree of tree (the degree of a tree is the maximum degree of a node in the tree) * @param tree - tree structure * @returns degree of tree */ getTreeDegree(e) { return e.reduce((t, r) => Math.max(t, r[this.childrenProp].length, this.getTreeDegree(r[this.childrenProp])), 0); } /** * Method to get nodes in tree at specific level * @param tree - tree structure to search in * @param level - desired level * @returns all nodes, that are on specific level */ getNodesAtLevel(e, t) { return this._getNodesAtLevel(e, t); } /** * Helper method to get nodes in tree at specific level recursively * @param tree - tree structure to search in * @param level - desired level * @param actualLevel - actual level, that is searched * @private * @returns all nodes, that are on specific level */ _getNodesAtLevel(e, t, r = 0) { return e.reduce((s, i) => [ ...s, ...t === r ? [i] : [], ...r < t ? this._getNodesAtLevel(i[this.childrenProp], t, r + 1) : [] ], []); } /** * Method to get width on level in tree (the number of nodes in a level) * @param tree - tree structure * @param level - desired level * @returns width on desired level */ getWidth(e, t) { return this.getNodesAtLevel(e, t).length; } /** * Method to get height of node (the height of a node is the length of the longest downward path to a leaf from that node) * @param tree - tree structure * @param id - identifier of node * @returns height of node */ getHeight(e, t) { return this.getHeightNode(this.getSubTree(e, t)); } /** * Helper method to get height of node from children recursively * @param tree - tree structure * @param height - actual computed height * @private * @returns height of node */ getHeightNode(e, t = 0) { return e.reduce((r, s) => Math.max(r, this.getHeightNode(s[this.childrenProp], t + 1)), t); } /** * Method to get distance between 2 nodes * @param tree - tree structure * @param id1 - identifier of first node * @param id2 - identifier of second node * @returns distance between 2 nodes, returns -1, if there is no connection between nodes */ getDistance(e, t, r) { const s = [...this.getPathNodes(e, t), this.get(e, t)], i = [...this.getPathNodes(e, r), this.get(e, r)], h = [...s].reverse().find((d) => i.includes(d)); if (!h) return -1; const n = s.findIndex((d) => d.id === h.id), l = i.findIndex((d) => d.id === h.id); return s.length - n - 1 + (i.length - l - 1); } /** * Method to compute paths for nodes (mutable operation) * path property will be added into each node * e.g. {path: "parent/child"...} * @param tree - tree structure * @param pathComputationProperty - property to use for path computation * @param delimiter - to delimit path * @param pathProperty - property where path will be stored * @param originPath - path of top level nodes */ computePaths(e, t, r = "/", s = "path", i = "/") { e == null || e.forEach((h) => { h[s] = i, this.computePaths(h[this.childrenProp], t, r, s, `${h.path}${h[t]}${r}`); }); } /** * Helper method to deep clone object * @param obj - object to be cloned * @private * @returns deep cloned object */ static deepCopy(e) { return JSON.parse(JSON.stringify(e)); } } export { o as TreeUtils };