Javascript : Search hierarchical tree - javascript

i have to get child object by name from hierarchical tree in JavaScript
My Sample data is as followed
{
id: 2,
name: "Alphabet",
parent: null,
path: "Alphabet",
children: [
{ id: 3,
name: "unit3",
parent: 2,
path: "Alphabet/unit3",
children:[
{ id: 5,
name: "unit15",
parent: 3,
path: "Alphabet/unit3/unit15",
children:[]
}
]
},
{ id: 4,
name: "unit6",
parent: 2,
path: "Alphabet/unit6",
children: []
}
]
}
I have tried as followed :
getChildFromTree(treeObj,name) : any {
if(treeObj.name == name) {
return treeObj;
}
var child;
for(var i=0;i<treeObj.children.length;i++) {
if(treeObj.children[i].name == name) {
child = treeObj.children[i];
break;
} else {
child = this.getChildFromTree(treeObj.children[i],name);
}
}
if(child) {
return child;
}
}
i am getting undefined when i search "unit15" please let me know, what wrong i am doing here

You are iterating over treeObj.children with for(var i = 0, however even if you find a child when using the recursive function this.getChildFromTree, it will not be returned since for(var i = 0 is not stopped (no break nor return in else branch inside the for loop).
You can simply add a if (child) return child; inside the loop.

Not a big fan of re-inveting the wheel and I'd recommend you use a library. We use object-scan for data processing stuff. It's pretty powerful once you wrap your head around it. Here is how you could answer your question:
// const objectScan = require('object-scan');
const find = (haystack, name) => objectScan(['**.name'], {
rtn: 'parent',
abort: true,
filterFn: ({ value }) => value === name
})(haystack);
const data = { id: 2, name: 'Alphabet', parent: null, path: 'Alphabet', children: [{ id: 3, name: 'unit3', parent: 2, path: 'Alphabet/unit3', children: [{ id: 5, name: 'unit15', parent: 3, path: 'Alphabet/unit3/unit15', children: [] }] }, { id: 4, name: 'unit6', parent: 2, path: 'Alphabet/unit6', children: [] }] };
console.log(find(data, 'unit15'));
/* =>
{ id: 5,
name: 'unit15',
parent: 3,
path: 'Alphabet/unit3/unit15',
children: [] }
*/
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.7.1"></script>
Disclaimer: I'm the author of object-scan

Related

How to make recursive function vue.js to make data from children

I am using vue.js and v-tree + vue2vis network,
I have a tree with all my items like this one :
items: [
{
id: 1,
name: 'root',
children: [
{
id: 2,
name: 'child1',
children: [
{
id: 3,
name: 'child3',
},
{
id: 4,
name: 'child34',
},
],
},
{
id: 5,
name: 'subroot',
children: [
{
id: 6,
name: 'Mike',
children:[
{
id: 7,
name: 'Mini Mike',
}
]
},
{
id: 8,
name: 'Hunt',
},
],
},
{
id: 9,
name: 'test',
children: [
{
id: 10,
name: 'Brandon',
},
{
id: 11,
name: 'Sean',
},
],
},
],
},
],
And what i want to do is when i click on a item in the tree it will generate data for the network like this:
nodes: [{'id':1 , label: 'root'},{'id':2 , label: 'child1'},{'id':3 , label: 'child3'}]
and so on for all the children and parents
same goes for the edage i want to create conection between parent and child
edage: [{'from': 1, 'to':2},{'from': 2, 'to':3}]
I try this function for this idea but its not working
makeNetConnection(items , itemKey) {
//items -> all the tree data
//itemKey-> its the item i click on the tree that i want to create the view
for (let item of items) {
if (item.name == itemKey) {
this.nodes.push({'id':item.id , 'label':item.name});
return item;
}
if (item.children) {
let i = this.makeNetConnection(item.children ,itemKey)
if (i) {
this.nodes.push({'id':item.id , 'label':item.name});
this.edges.push({'from': item.id , 'to': i.id});
return i;
}
}
}
its duplicate the data in the arrays and not make connect with the parent
i expect to have [{'from': 1, 'to':2},{'from': 2, 'to':3},{'from': 2, 'to':4}]
and os on for all the items
but i have [{'from': 1, 'to':2},{'from': 1, 'to':3} , {'from': 1, 'to':2},{'from': 1, 'to':3}, {'from': 1, 'to':4}, {'from': 1, 'to':4}]
i dont get the middel connection
any idea how to make its work?
Maybe overkill, but use traverse
const traverse = require('traverse');
const returnData=[];
traverse.forEach(function(item){
returnData.push({id:item.id,label:item.value});
});
I would build this upon simpler functions to collect the nodes and to collect the edges. It means running two traversals of your tree, but it makes for much simpler code.
Here we have a recursive function to flatten the nodes of a tree into an array, transforming the name property to a label one as we go. And we have a second recursive function to collect the edges as from-to pairs.
Then we write a very simple function to combine them:
const collectNodes = (xs) =>
xs .flatMap (({id, name, children = []}) => [
{id, label: name},
... collectNodes (children)
])
const collectEdges = (xs) =>
xs .flatMap (({id: from, children = []}) => [
... children .map (({id: to}) => ({from, to})),
... collectEdges (children),
])
const collect = (items) => ({
nodes: collectNodes (items),
edges: collectEdges (items)
})
const items = [{id: 1, name: "root", children: [{id: 2, name: "child1", children: [{id: 3, name: "child3"}, {id: 4, name: "child34"}]}, {id: 5, name: "subroot", children: [{id: 6, name: "Mike", children: [{id: 7, name: "Mini Mike"}]}, {id: 8, name: "Hunt"}]}, {id: 9, name: "test", children: [{id: 10, name: "Brandon"}, {id: 11, name: "Sean"}]}]}]
console .log (collect (items))
.as-console-wrapper {max-height: 100% !important; top: 0}
Could we do this in a single traversal? Certainly, but I think it would make for more convoluted code. I would only bother doing so if the performance of this is not acceptable.
I ignored this: "when i click on a item in the tree". I'm assuming that you want to convert all the data. If you only want it up to a certain node, then please add a clarification to the question.

Remove matched object from deeply nested array of objects

I have a data tree structure with children:
{ id: 1,
name: "Dog",
parent_id: null,
children: [
{
id: 2,
name: "Food",
parent_id: 1,
children: []
},
{
id: 3,
name: "Water",
parent_id: 1,
children: [
{
id: 4,
name: "Bowl",
parent_id: 3,
children: []
},
{
id: 5,
name: "Oxygen",
parent_id: 3,
children: []
},
{
id: 6,
name: "Hydrogen",
parent_id: 3,
children: []
}
]
}
]
}
This represents a DOM structure that a user could select an item from to delete by clicking the corresponding button in the DOM.
I have a known text title of the selected item for deletion from the DOM set as the variable clickedTitle. I am having trouble finding an algorithm that will allow me to delete the correct object data from the deeply nested tree.
Here is my code:
function askUserForDeleteConfirmation(e) {
const okToDelete = confirm( 'Are you sure you want to delete the item and all of its sub items?' );
if(!okToDelete) {
return;
}
const tree = getTree(); // returns the above data structure
const clickedTitle = getClickedTitle(e); // returns string title of clicked on item from DOM - for example "Dog" or "Bowl"
const updatedTree = removeFromTree(tree, tree, clickedTitle);
return updatedTree;
}
function removeFromTree(curNode, newTree, clickedTitle) {
if(curNode.name === clickedTitle) {
// this correctly finds the matched data item to delete but the next lines don't properly delete it... what to do?
const index = curNode.children.findIndex(child => child.name === clickedTitle);
newTree = curNode.children.slice(index, index + 1);
// TODO - what to do here?
}
for(const node of curNode.children) {
removeFromTree(node, newTree, clickedTitle);
}
return newTree;
}
I have tried to use the info from Removing matched object from array of objects using javascript without success.
If you don't mind modifying the parameter tree in-place, this should do the job. Note that it'll return null if you attempt to remove the root.
const tree = { id: 1, name: "Dog", parent_id: null, children: [ { id: 2, name: "Food", parent_id: 1, children: [] }, { id: 3, name: "Water", parent_id: 1, children: [ { id: 4, name: "Bowl", parent_id: 3, children: [] }, { id: 5, name: "Oxygen", parent_id: 3, children: [] }, { id: 6, name: "Hydrogen", parent_id: 3, children: [] } ] } ] };
const removeFromTree = (root, nameToDelete, parent, idx) => {
if (root.name === nameToDelete) {
if (parent) {
parent.children.splice(idx, 1);
}
else return null;
}
for (const [i, e] of root.children.entries()) {
removeFromTree(e, nameToDelete, root, i);
}
return tree;
};
console.log(removeFromTree(tree, "Oxygen"));
Your current code is very much on the right track. However:
newTree = curNode.children.slice(index, index + 1);
highlights a few issues: we need to manipulate the parent's children array to remove curNode instead of curNode's own children array. I pass parent objects and the child index recursively through the calls, saving the trouble of the linear operation findIndex.
Additionally, slicing from index to index + 1 only extracts one element and doesn't modify curNode.children. It's not obvious how to go about using newArray or returning it through the call stack. splice seems like a more appropriate tool for the task at hand: extracting one element in-place.
Note that this function will delete multiple entries matching nameToDelete.
I like #VictorNascimento's answer, but by applying map then filter, each children list would be iterated twice. Here is an alternative with reduce to avoid that:
function removeFromTree(node, name) {
return node.name == name
? undefined
: {
...node,
children: node.children.reduce(
(children, child) => children.concat(removeFromTree (child, name) || []), [])
}
}
In the case you want a way to remove the items in-place, as #ggorlen proposed, I'd recommend the following solution, that is simpler in my opinion:
function removeFromTree(node, name) {
if (node.name == name) {
node = undefined
} else {
node.children.forEach((child, id) => {
if (!removeFromTree(child, name)) node.children.splice(id, 1)
})
}
return node
}
I've built the algorithm as follows:
function omitNodeWithName(tree, name) {
if (tree.name === name) return undefined;
const children = tree.children.map(child => omitNodeWithName(child, name))
.filter(node => !!node);
return {
...tree,
children
}
}
You can use it to return a new tree without the item:
noHydrogen = omitNodeWithName(tree, "Hydrogen")
If it's ok to use Lodash+Deepdash, then:
let cleaned = _.filterDeep([tree],(item)=>item.name!='Hydrogen',{tree:true});
Here is a Codepen
We use object-scan for many data processing tasks. It's powerful once you wrap your head around it. Here is how you could answer your question
// const objectScan = require('object-scan');
const prune = (name, input) => objectScan(['**[*]'], {
rtn: 'bool',
abort: true,
filterFn: ({ value, parent, property }) => {
if (value.name === name) {
parent.splice(property, 1);
return true;
}
return false;
}
})(input);
const obj = { id: 1, name: 'Dog', parent_id: null, children: [{ id: 2, name: 'Food', parent_id: 1, children: [] }, { id: 3, name: 'Water', parent_id: 1, children: [{ id: 4, name: 'Bowl', parent_id: 3, children: [] }, { id: 5, name: 'Oxygen', parent_id: 3, children: [] }, { id: 6, name: 'Hydrogen', parent_id: 3, children: [] }] }] };
console.log(prune('Oxygen', obj)); // return true iff pruned
// => true
console.log(obj);
// => { id: 1, name: 'Dog', parent_id: null, children: [ { id: 2, name: 'Food', parent_id: 1, children: [] }, { id: 3, name: 'Water', parent_id: 1, children: [ { id: 4, name: 'Bowl', parent_id: 3, children: [] }, { id: 6, name: 'Hydrogen', parent_id: 3, children: [] } ] } ] }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.8.0"></script>
Disclaimer: I'm the author of object-scan

Recursive iteration over deeply nested object to find parent

I have a data tree structure with children:
{ id: 1,
name: "Dog",
parent_id: null,
children: [
{
id: 2,
name: "Food",
parent_id: 1,
children: []
},
{
id: 3,
name: "Water",
parent_id: 1,
children: [
{
id: 4,
name: "Bowl",
parent_id: 3,
children: []
},
{
id: 5,
name: "Oxygen",
parent_id: 3,
children: []
},
{
id: 6,
name: "Hydrogen",
parent_id: 3,
children: []
}
]
}
]
}
Any child data object can have more children as shown in the above data. This represents a DOM structure that a user could select an item from to add a child to.
I have a known text title of the selected item from the DOM as well as the data the user wants to insert. I am having trouble finding a recursive algorithm that will allow me to add the new data to the correct level of the tree.
Here is a list of me thinking through the problem and trying to pseudo code it:
inputs:
tree (data from above)
parentTitle from clicked item in DOM
outputs:
tree with item inserted
steps:
determine highest used id to know what next unique id is
check current level of data for match with the title of parent
if matched then set id and parent_id in new data and push into children of parent
if no match then check if current level data have children
if current level has children needs to repeat steps 2+ for each until match is found
Here is my code:
function askUserForNewItem(e) {
const tree = getTree(); // returns above tree data structure
const name = prompt( 'Enter new item’s name:' ); // user input to match and insert as new item in tree
const clickedTitle = getClickedTitle(e); // returns string title of clicked on item from DOM - for example "Dog" or "Bowl"
const parent = determineParent(tree, clickedTitle);
const parent_id = parent[0].id;
// TODO - needs to set real unique id (highest unused id)
const newId = 101; // hard coded for now, needs to be dynamic
// TODO - needs to insert into correct level of children array in tree
return tree.children.push({ id: newId, name, emoji, children: [], parent_id: parent_id });
}
function determineParent(tree, clickedTitle) {
if(tree.children.length === 0) {
return false;
}
let treeLevel = tree;
let parent = [];
while(treeLevel.children.length !== 0) {
parent = treeLevel.children.filter(child => child.name === clickedTitle);
if(parent.length !== 0) {
break;
}
else {
// what to do here to make this recursive?
}
}
return parent;
}
So if a user typed "Cat" while clicking the add button for "Dog" then a new object
{
id: 7,
name: "Cat",
parent_id: 1,
children: []
}
Would be inserted into children of the first level "Dog" object in the data tree.
If you want a recursive solution, you should modify the determineParent method so it searches down the tree.
Not sure this is exactly what you are searching for, but i hope you get the general idea
function determineParent(curNode, clickedTitle) {
if(curNode.name===clickedTitle)
return curNode; // found the parent node with the correct clickedTitle
// not found yet, do a recusive search down the tree.
for(const node of curNode.children) {
return determineParent(node,clickedTitle);
}
return null; // not found.
}
the idea is to start at the topmost node (curNode) and first determine if it is the correct parent, if not then take the first children see if it matches and if not search down it's children and so on.
When dealing with recursion it may be necessary to handle the situation where you may experience circular references, consider a scenario where a node has a child that points to the nodes parent or grandparent, the recursive method will run forever (in real life it will run out of stack space and throw an exception).
One way it so include a safeguard counter that is decreased on each recursive call, and then bail out when it reaches zero.
function determineParent(curNode, clickedTitle, safeGuard) {
if(curNode.name===clickedTitle)
return curNode; // found the parent node with the correct clickedTitle
if(safeGuard===0)
return null; // bail out
// not found yet, do a recusive search down the tree.
for(const node of curNode.children) {
return determineParent(node,clickedTitle,--safeGuard);
}
return null; // not found.
}
and then call it like
this.determineParent(tree,"title",100);
to limit the number of searches to 100.
If it's ok to use Lodash+Deepdash, then:
let child = {
name: "Cat",
children: []
};
let maxUsedId=-1;
_.eachDeep([data],(val)=>{
if(val.id>maxUsedId){
maxUsedId = val.id;
}
if(val.name==parentName){
val.children.push(child);
child.parent_id = val.id;
}
},{tree:true});
child.id=maxUsedId+1;
Codepen for this
Not a big fan of reinventing the wheel, especially when it comes to algorithms. Here is how you could use object-scan to solve your problem. We use it for data processing since it is quite powerful once you wrap your head around it
// const objectScan = require('object-scan');
const insert = (tree, parentName, childName) => objectScan(['**.name'], {
abort: true,
rtn: 'bool',
filterFn: ({ value, parent }) => {
if (value === parentName) {
parent.children.push({
id: Math.max(...objectScan(['**.id'], { rtn: 'value' })(tree)) + 1,
name: childName,
parent_id: parent.id,
children: []
});
return true;
}
return false;
}
})(tree);
const data = { id: 1, name: 'Dog', parent_id: null, children: [{ id: 2, name: 'Food', parent_id: 1, children: [] }, { id: 3, name: 'Water', parent_id: 1, children: [{ id: 4, name: 'Bowl', parent_id: 3, children: [] }, { id: 5, name: 'Oxygen', parent_id: 3, children: [] }, { id: 6, name: 'Hydrogen', parent_id: 3, children: [] }] }] };
console.log(insert(data, 'Dog', 'Cat')); // true iff insert was successful
// => true
console.log(data);
// => { id: 1, name: 'Dog', parent_id: null, children: [ { id: 2, name: 'Food', parent_id: 1, children: [] }, { id: 3, name: 'Water', parent_id: 1, children: [ { id: 4, name: 'Bowl', parent_id: 3, children: [] }, { id: 5, name: 'Oxygen', parent_id: 3, children: [] }, { id: 6, name: 'Hydrogen', parent_id: 3, children: [] } ] }, { id: 7, name: 'Cat', parent_id: 1, children: [] } ] }
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan#13.7.1"></script>
Disclaimer: I'm the author of object-scan

How to build the path to each node in a tree recursively - JavaScript?

My data structure will look like this:
var tree = [
{
id: 1,
children: []
}, {
id: 2,
children: [
{
id: 3,
children: []
}
]
}
];
There can be any number of nodes or children on one branch.
My goal is to build a path to every node.
For example id: 3 will have a path of 1 > 2 > 3
id: 2 will have a path of 1 > 2
I want to run my tree through the algorithm so it will be modified like this:
var tree = [
{
id: 1,
path: [1],
children: []
}, {
id: 2,
path: [2],
children: [
{
id: 3,
path: [2, 3],
children: []
}
]
}
];
I have written an algorithm that will visit all of the nodes in the tree:
https://plnkr.co/edit/CF1VNofzpafhd1MOMVfj
How can I build the path to each node?
Here is my attempt:
function traverse(branch, parent) {
for (var i = 0; i < branch.length; i++) {
branch[i].visited = true;
if (branch[i].path === undefined) {
branch[i].path = [];
}
if (parent != null) {
branch[i].path.push(parent);
}
if (branch[i].children.length > 0) {
traverse(branch[i].children, branch[i].id);
}
}
}
Beside the unclear taking of not directly involved parents, you could store the path as arrray and take it for each nested iteration.
function iter(path) {
path = path || [];
return function (o) {
o.path = path.concat(o.id);
if (o.children) {
o.children.forEach(iter(o.path));
}
}
}
var tree = [{ id: 1, children: [] }, { id: 2, children: [{ id: 3, children: [] }] }];
tree.forEach(iter());
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You made a mistake
Your root node is an array, but all other nodes are objects.
This makes your program inconsistent and needlessly complex to handle the root node difference – the solution is to stop writing data using literals – you're bound to make mistakes like you did above
Instead, just make some simple data constructors and your complexities vanish into thin air
const Node = (id, ...children) =>
({ id, children })
const PathNode = (id, path, ...children) =>
({ id, path, children })
const addPaths = ({id, children}, acc = []) =>
PathNode (id, acc, children.map (child =>
addPaths (child, [...acc, id])))
const tree =
Node (0, Node (1),
Node (2, Node (3)))
console.log (tree)
// { id: 0, children: [
// { id: 1, children: [ ] },
// { id: 2, children: [
// { id: 3, children: [ ] } ] } ] }
console.log (addPaths (tree))
// { id: 0, path: [ ], children: [
// { id: 1, path: [ 0 ], children: [ ] },
// { id: 2, path: [ 0 ], children: [
// { id: 3, path: [ 0, 2 ], children: [ ] } ] } ] }
You could use reduce method to create a recursive function and pass the previous path values in recursive calls as an array of id's.
var tree = [{ id: 1, children: [] }, { id: 2, children: [{ id: 3, children: [] }] }];
function getPaths(data, prev = []) {
return data.reduce((r, { id, children }) => {
const o = { id, children, path: [...prev, id] }
if (children) {
o.children = getPaths(children, o.path)
}
r.push(o)
return r
}, [])
}
console.log(getPaths(tree))

How do I find the path to a deeply nested unique key in an object and assign child objects accordingly?

With a given flat list:
let list = [
{
key: 1,
parent: null,
},
{
key: 2,
parent: 1,
},
{
key: 3,
parent: null,
},
{
key: 4,
parent: 1,
},
{
key: 5,
parent: 2,
}
]
How do I create a nested object like the one below?
let nest = {
children: [
{
key: 1,
children: [
{
key: 2,
children: [
{
key: 5,
children: []
}
]
},
{
key: 4,
children: []
}
]
},
{
key: 3,
children: []
}
]
}
I'm not sure how to approach this. The solution I have in mind would have to iterate over the list over and over again, to check if the object's parent is either null, in which case it gets assigned as a top-level object, or the object's parent already exists, in which case we get the path to the parent, and assign the child to that parent.
P.S.
I don't think this is a duplicate of any of the below
this checks for a key in a flat object.
this doesn't show anything that would return a path, given a unique key.
For building a tree, you could use a single loop approach, by using not only the given key for building a node, but using parent as well for building a node, where the dendency is obviously.
It uses an object where all keys are usesd as reference, like
{
1: {
key: 1,
children: [
{
/**id:4**/
key: 2,
children: [
{
/**id:6**/
key: 5,
children: []
}
]
},
{
/**id:8**/
key: 4,
children: []
}
]
},
2: /**ref:4**/,
3: {
key: 3,
children: []
},
4: /**ref:8**/,
5: /**ref:6**/
}
The main advantage beside the single loop is, it works with unsorted data, because of the structure to use keys and parent information together.
var list = [{ key: 1, parent: null, }, { key: 2, parent: 1, }, { key: 3, parent: null, }, { key: 4, parent: 1, }, { key: 5, parent: 2, }],
tree = function (data, root) {
var r = [], o = {};
data.forEach(function (a) {
var temp = { key: a.key };
temp.children = o[a.key] && o[a.key].children || [];
o[a.key] = temp;
if (a.parent === root) {
r.push(temp);
} else {
o[a.parent] = o[a.parent] || {};
o[a.parent].children = o[a.parent].children || [];
o[a.parent].children.push(temp);
}
});
return r;
}(list, null),
nest = { children: tree };
console.log(nest);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories

Resources