Recurse through tree to create a list of breadcrumbs - javascript

I have a tree of data and am trying to create a recursive function to add each path in the tree as an array of strings to better understand recursion. I am not sure why my method is not producing the expect
var tree = {
"name": "home",
"children": [
{
"name": "cars",
"children": [
{
"name": "ford",
"children": [
{
"name": "mustang"
},
{
"name": "explorer"
}
]
}
]
},
{
"name": "food",
"children": [
{
"name": "pizza"
}
]
}
]
};
var list = [];
var path = [];
function traverse(node) {
if (node.name) {
path.push(node.name)
}
if (!node.children) {
if (path.length) {
list.push(path);
}
return;
} else {
node.children.forEach(function(item) {
traverse(item);
});
}
}
traverse(tree);
console.log(list);
The output I am looking to create is:
[
["home"],
["home", "cars"],
["home", "cars", "ford"],
["home", "cars", "ford", "mustang"],
["home", "cars", "ford", "explorer"],
["home", "food"],
["home", "food", "pizza"]
]

You modify the same path array in all iterations. You should copy it instead:
var list = [];
function traverse(node, path) {
if ( !path )
path = [];
if (node.name) {
path.push(node.name)
}
list.push(path);
if (node.children) {
node.children.forEach(function(item) {
traverse(item, path.slice());
});
}
}
traverse(tree, []);

I have corrected your code, this solution copies the path variable from one function call to the other:
var tree = {
"name": "home",
"children": [{
"name": "cars",
"children": [{
"name": "ford",
"children": [{
"name": "mustang"
}, {
"name": "explorer"
}]
}]
}, {
"name": "food",
"children": [{
"name": "pizza"
}]
}]
};
var path = [];
var list = [];
function traverse(node, path) {
if ( !path )
path = [];
if (node.name) {
path.push(node.name)
}
list.push(path);
if (node.children) {
node.children.forEach(function(item) {
traverse(item, path.slice());
});
}
document.write(JSON.stringify(path )+ '<br>')
}
traverse(tree, []);

Related

How do I destructure this deep nested json objects and map it in JS

I have a nested array like below. There are about 100 de objects in the array. The de objects also have deg[0] array but most likely I will only have the first index. Now the trick is that the de are subset of deg. Which means each deg can have say 10 de. How can I retrieve the deg and there associated de and map it into a new array like:
newArray = [
deg1: [
{de1},
{de2}
],
deg2: [
{de1},
{de2}
]
]
Here is my nested array. I posted four but the list is over a 100.
{
"name": "Report",
"id": "2YYUEZ6I1r9",
"dse1": [
{
"de1": {
"name": "Number",
"id": "HjMOngg3kuy",
"de1-av": [
{
"value": "FHaQMPv9zc7",
"attribute": {
"id": "uwVkIP7PZDt"
}
},
{
"value": "something",
"attribute": {
"id": "FHaQMPv9zc7"
}
}
],
"deg1": [
{
"name": "TB",
"id": "2XJB1JO9qX8"
}
]
}
},
{
"de2": {
"name": "Number of",
"id": "a3dtGETTawy",
"de2-av": [
{
"value": "FHaQMPv9zc7",
"attribute": {
"id": "uwVkIP7PZDt"
}
},
{
"value": "something",
"attribute": {
"id": "FHaQMPv9zc7"
}
}
],
"deg1": [
{
"name": "Secondary",
"id": "w99RWzXHgtw"
}
]
}
},
{
"de1": {
"name": "Number of",
"id": "a3dtGETTawy",
"de1av": [
{
"value": "FHaQMPv9zc7",
"attribute": {
"id": "uwVkIP7PZDt"
}
},
{
"value": "something",
"attribute": {
"id": "FHaQMPv9zc7"
}
}
],
"deg2": [
{
"name": "Secondary",
"id": "w99RWzXHgtw"
}
]
}
},
{
"de2": {
"name": "Number of",
"id": "a3dtGETTawy",
"de2av": [
{
"value": "FHaQMPv9zc7",
"attribute": {
"id": "uwVkIP7PZDt"
}
},
{
"value": "something",
"attribute": {
"id": "FHaQMPv9zc7"
}
}
],
"deg2": [
{
"name": "Tertiary",
"id": "w99RWzXHgtw"
}
]
}
}
]
}
Group array of objects by property (this time a property to be matched by a reg exp) using Array.reduce.
Update: Ignoring missing keys.
var input={name:"Report",id:"2YYUEZ6I1r9",dse1:[{de1:{name:"Number",id:"HjMOngg3kuy","de1-av":[{value:"FHaQMPv9zc7",attribute:{id:"uwVkIP7PZDt"}},{value:"something",attribute:{id:"FHaQMPv9zc7"}}],deg1:[{name:"TB",id:"2XJB1JO9qX8"}]}},{de2:{name:"Number of",id:"a3dtGETTawy","de2-av":[{value:"FHaQMPv9zc7",attribute:{id:"uwVkIP7PZDt"}},{value:"something",attribute:{id:"FHaQMPv9zc7"}}],deg1:[{name:"Secondary",id:"w99RWzXHgtw"}]}},{de1:{name:"Number of",id:"a3dtGETTawy",de1av:[{value:"FHaQMPv9zc7",attribute:{id:"uwVkIP7PZDt"}},{value:"something",attribute:{id:"FHaQMPv9zc7"}}],deg2:[{name:"Secondary",id:"w99RWzXHgtw"}]}},{de2:{name:"Number of",id:"a3dtGETTawy",de2av:[{value:"FHaQMPv9zc7",attribute:{id:"uwVkIP7PZDt"}},{value:"something",attribute:{id:"FHaQMPv9zc7"}}],deg2:[{name:"Tertiary",id:"w99RWzXHgtw"}]}}]}
var reg = new RegExp("^de[0-9]+$");
var reg2 = new RegExp("^deg[0-9]+$");
let obj = input['dse1'].reduce(function(agg, item) {
// do your group by logic below this line
var key = Object.keys(item).find(function(key) {
return key.match(reg) ? key : null;
})
if (key) {
var key2 = Object.keys(item[key]).find(function(key) {
return key.match(reg2) ? key : null;
})
agg[key] = agg[key] || [];
if (key2) {
var to_push = {}
to_push[key2] = item[key][key2]
agg[key].push(to_push)
}
}
// do your group by logic above this line
return agg
}, {});
console.log(obj)
.as-console-wrapper {
max-height: 100% !important;
}

How to delete / prune all tree nodes where the nodes do not exist in a separate array

I have the following JSON tree representation:
let tree = [
{
"label": "Org01",
"children": [
{
"label": "Dist01",
"children": [
{
"label": "School1",
"children": [
{
"label": "class1",
"children": []
},
{
"label": "class2",
"children": []
}
]
},
{
"label": "School1tst",
"children": []
}
]
},
{
"label": "Dist02",
"children": []
}
]
},
{
"label": "contoso01",
"children": [
{
"label": "Dist A",
"children": [
{
"label": "School A",
"children": [
{
"label": "classA",
"children": []
}
]
},
{
"label": "School B",
"children": [
{
"label": "classB",
"children": []
}
]
}
]
},
{
"label": "Dist B",
"children": [
{
"label": "School1",
"children": [
{
"label": "class1",
"children": []
}
]
}
]
}
]
}
];
I have a list of nodes in an array like here:
let whitelist = ['class1', 'School1', 'Dist01'];
How can I delete all the nodes from the tree which do not exist in the above array. However parent nodes need to be displayed on the tree if their children are in the whitelist.
Deleting a specific node from the tree is possible for me, but I could not figure out a way to delete all nodes from the tree except the few which are in the array.
Final output required:
output = [
{
"label": "razor01",
"children": [
{
"label": "Dist01",
"children": [
{
"label": "School1",
"children": [
{
"label": "class1",
"children": []
}
]
}
]
}
]
},
{
"label": "contoso01",
"children": [
{
"label": "Dist B",
"children": [
{
"label": "School1",
"children": [
{
"label": "class1",
"children": []
}
]
}
]
}
]
}];
Thanks and I appreciate any help with this.
const prunedNode = node => {
const pruned = whitelist.includes(node.label) ? node : null;
if (pruned) {
node.children = node.children.reduce((prunedChildren, child) => {
const prunedChildNode = prunedNode(child);
if (prunedChildNode) {
prunedChildren.push(prunedChildNode);
}
return prunedChildren;
}, []);
}
return pruned;
};
console.log(prunedNode(tree));
This should get the job done:
function filter(tree, list){
let output = [];
for(i in tree){
if(list.indexOf(tree[i].label) >= 0){
tree[i].children = filter(tree[i].children, list);
output.push(tree[i]);
}else{
output = output.concat(filter(tree[i].children, list));
}
}
return output;
}
I solved it this way:
function deleteNodes(tree, list) {
if (tree.length > 0) {
tree.forEach((node, i) => {
this.deleteNodes(node.subItems, list);
if (node.subItems) {
if (node.subItems.length === 0 && !list.includes(node.text))
{
tree.splice(i, 1);
}
}
});
}
}

Traverse in nested arrays in JSON data

I have the data like this:
{
{
"text" : "parent1",
"nodes" :[
{
"text": "child1",
"nodes": [
{
"text": "grandchild1",
"nodes":[
{
"text": "hello",
"nodes": []
}
]
},
{
"text": "hello",
"nodes":[]
}
]
}
]
},
{
"text" : "parent2",
"nodes" :[
{
"text": "child2",
"nodes": [
{
"text": "grandchild2",
"nodes": [
{
"text": "grandgrandchild1",
"nodes": [
{
"text": "hello",
"nodes": []
}
]
}
]
}
]
}
]
}
}
What I want is, creating a path array that contains the path of the elements whose "text" value is "hello". For example, according to this data:
var paths: any[][] = [
["parent1","child1","grandchild1","hello"],
["parent1","child1","hello"],
["parent2","child2","grandchild2","grandgrandchild1","hello"]
];
I want this "paths" array. Please notice, if the element's text value is "hello", then this element's "nodes" length is 0. I think I'm making a mistake in recursion.
You need to
Traverse the object using recursion,
Assign paths along the way
and finally keep record those paths when the current text matches the text.
Demo
var obj = [{
"text": "parent1",
"nodes": [{
"text": "child1",
"nodes": [{
"text": "grandchild1",
"nodes": [{
"text": "hello",
"nodes": []
}]
},
{
"text": "hello",
"nodes": []
}
]
}]
},
{
"text": "parent2",
"nodes": [{
"text": "child2",
"nodes": [{
"text": "grandchild2",
"nodes": [{
"text": "grandgrandchild1",
"nodes": [{
"text": "hello",
"nodes": []
}]
}]
}]
}]
}
];
var helloPaths = [];
var valueToSearch = "hello";
function traverseAndCreatePath( obj, parent, valueToSearch )
{
if ( Array.isArray( obj ) )
{
obj.forEach( function(item){
traverseAndCreatePath( item, parent, valueToSearch );
});
}
else
{
if ( parent )
{
obj.path = parent.path.slice();
obj.path.push( obj.text );
}
else
{
obj.path = [ obj.text ];
}
if ( obj.text == valueToSearch )
{
helloPaths.push( obj.path.slice() );
}
if ( obj.nodes && obj.nodes.length )
{
obj.nodes.forEach( function(item){
traverseAndCreatePath( item, obj, valueToSearch );
});
}
}
return obj;
}
traverseAndCreatePath( obj, null, valueToSearch );
console.log( helloPaths );
This is a Depth First Traversal
var counter = 0;
var finalArray = []
function test(arr, node) {
arr.push(node.text);
if (node.hasOwnProperty("nodes") && Array.isArray(node.nodes) && node.nodes.length != 0) {
node.nodes.forEach(function(nodeChild) {
test(arr, nodeChild);
})
} else {
finalArray[counter] = arr.slice();
counter++;
}
arr = arr.slice(0, -1);
}
var b =[ { "text" : "parent1", "nodes" :[ { "text": "child1", "nodes": [ { "text": "grandchild1", "nodes":[ { "text": "hello", "nodes": [] } ] }, { "text": "hello", "nodes":[] } ] } ] }, { "text" : "parent2", "nodes" :[ { "text": "child2", "nodes": [ { "text": "grandchild2", "nodes": [ { "text": "grandgrandchild1", "nodes": [ { "text": "hello", "nodes": [] } ] } ] } ] } ] } ]
b.forEach(function(nodeVal) {
test([], nodeVal)
})
console.log(finalArray);
Your function should recieve:
A list of nodes of any length
And return:
A list of paths of any length
For the base of this behavior, we'll use reduce. It loops over the list once, and can keep adding to the result when needed.
const helloPathsFromNodes = (nodes = [], paths = []) =>
nodes.reduce(
/* TODO */ ,
paths
)
For each node, you check whether it's the end of an individual path to determine if you need to recurse. To keep track of the current path, we'll need to pass along an additional parameter (path) and add to it along the way:
if (node.text === "hello") return [...path, "hello"]
else return goDeeper([...path, node.text])
Putting this together, you get:
const helloPathsFromNodes = (nodes = [], paths = [], path = []) =>
nodes.reduce(
(acc, { text, nodes }) =>
text === "hello" ?
[...acc, [...path, text]] :
helloPathsFromNodes(nodes, acc, [...path, text]),
paths
);
console.log(helloPathsFromNodes(getData()));
// Example Data
function getData() { return [ { "text" : "parent1", "nodes" :[ { "text": "child1", "nodes": [ { "text": "grandchild1", "nodes":[ { "text": "hello", "nodes": [] } ] }, { "text": "hello", "nodes":[] } ] } ] }, { "text" : "parent2", "nodes" :[ { "text": "child2", "nodes": [ { "text": "grandchild2", "nodes": [ { "text": "grandgrandchild1", "nodes": [ { "text": "hello", "nodes": [] } ] } ] } ] } ] } ] };

Building new JSON from existing one

I want to build an new JSON from existing one. The source has sections and rubrics that I no longer need for a listing. The new object called 'items' should have an array of the items.
The final JSON should be sorted by attribute 'name' and look like
{
"items": [
{
"id": 10000006,
"name": "Boah"
},
{
"id": 10000013,
"name": "Gut"
},
{
"id": 10000003,
"name": "Ipsum"
},
{
"id": 10000001,
"name": "Lorem"
},
{
"id": 10000005,
"name": "Lorum"
},
{
"id": 10000004,
"name": "Name"
},
{
"id": 10000002,
"name": "Stet"
}
]
}
For building the new JSON I get this source:
{
"sections": [
{
"name": "FooBar",
"rubrics": [
{
"name": "Foo",
"items": [
{
"id": 10000001,
"name": "Lorem"
},
{
"id": 10000002,
"name": "Stet"
},
{
"id": 10000003,
"name": "Ipsum"
}
]
},
{
"name": "Bar",
"items": [
{
"id": 10000004,
"name": "Name"
},
{
"id": 10000005,
"name": "Lorum"
},
{
"id": 10000006,
"name": "Boah"
}
]
}
]
},
{
"name": "BlahBloob",
"rubrics": [
{
"name": "Bla",
"items": [
{
"id": 10000013,
"name": "Gut"
}
]
},
{
"name": "Bloob",
"items": [
{
"id": 10000014,
"name": "Name"
},
{
"id": 10000015,
"name": "Lorem"
}
]
}
]
}
]
}
What do you think? How can I do this with plain JavaScript or maybe TypeScript?
Thanks for reading and have time for my question. And thanks for reply in advance.
Here you go. You just need to iterate over each rubric of each section of your source to get the items. At the end, sort your list of items by items, and you're done.
This example uses ES6 syntax, but it's easy to convert it to ES5 if needed.
function extractItems(source) {
const items = [];
for (const section of source.sections) {
for (const rubric of section.rubrics) {
items.push(...rubric.items);
}
}
items.sort((a, b) => a.name.localeCompare(b.name));
return { items };
}
A more functional approach use map and reduce to pick the rubrics and merge them.
data.sections
.map(section => section.rubrics) // get rubrics
.reduce((a, b) => a.concat(b)) // merge rubrics
.map(rubric => rubric.items) // get items from each rubric
.reduce((a, b) => a.concat(b)) // merge items
.sort((a, b) => a.name.localeCompare(b.name)); // sort
function(oldObj) {
var newObj = {
"items": []
};
oldObj.sections.forEach(function(section) {
section.rubrics.forEach(function(rubric) {
rubric.items.forEach(function(item) {
newObj.items.push(item);
});
});
});
newObj.items = newObj.items.sort(function(a, b) {
if (a.name < b.name) { return -1; }
if (a.name > b.name) { return 1; }
return 0;
});
return newObj;
}
And simply use JSON.parse() and JSON.stringify() to convert JSON to and from objects.
It might help you
var data ={
"sections": [
{
"name": "FooBar",
"rubrics": [{"name": "Foo", "items": [{"id": 10000001,"name": "Lorem"}, {"id": 10000002,"name": "Stet"}, {"id": 10000003,"name": "Ipsum"}]
}, {
"name": "Bar",
"items": [{
"id": 10000004,
"name": "Name"
}, {
"id": 10000005,
"name": "Lorum"
}, {
"id": 10000006,
"name": "Boah"
}]
}]
}, {
"name": "BlahBloob",
"rubrics": [{
"name": "Bla",
"items": [{
"id": 10000013,
"name": "Gut"
}]
}, {
"name": "Bloob",
"items": [{
"id": 10000014,
"name": "Name"
}, {
"id": 10000015,
"name": "Lorem"
}]
}]
}]
};
var itemObj = {};
var itemArr = [];
var sections = data.sections;
for(var i=0;i<sections.length;i++)
{
for(var j=0;j<sections[i].rubrics.length;j++){
for(var k=0;k<sections[i].rubrics[j].items.length;k++){
var itemObj;
itemObj['id'] = sections[i].rubrics[j].items[k].id;
itemObj['name'] = sections[i].rubrics[j].items[k].name;
itemArr.push(itemObj);
}
}
}
var finalObj = {"items":itemArr};
console.log(finalObj);
JSFiddle

Underscore Convert array to object keys

I am trying to convert this array to an object. Using underscore, I want to convert this array :
[
{
"id": "parentA",
"children": [
{
"name": "name1"
},
{
"name": "name2"
},
{
"name": "name3"
}
]
},
{
"id": "parentB",
"children": [
{
"name": "name4"
},
{
"name": "name5"
},
{
"name": "name6"
}
]
}]
into an object that looks like this:
{
"name1": "parentA",
"name2": "parentA",
"name3": "parentA",
"name4": "parentB",
"name5": "parentB",
"name6": "parentB"
}
I'm really just looking for the cleanest/simplest way possible.
Here's a fairly short way to do it with two reduce:
var data = [
{
"id": "parentA",
"children": [
{
"name": "name1"
},
{
"name": "name2"
},
{
"name": "name3"
}
]
},
{
"id": "parentB",
"children": [
{
"name": "name4"
},
{
"name": "name5"
},
{
"name": "name6"
}
]
}];
var out = _.reduce(data, function(result, parent) {
_.reduce(parent.children, function(r, child) {
r[child.name] = parent.id;
return r;
}, result);
return result;
}, {});
document.write(JSON.stringify(out));
<script src="http://underscorejs.org/underscore-min.js"></script>
var a = [{
"id": "parentA",
"children": [{
"name": "name1"
}, {
"name": "name2"
}, {
"name": "name3"
}]
}, {
"id": "parentB",
"children": [{
"name": "name4"
}, {
"name": "name5"
}, {
"name": "name6"
}]
}];
var new_obj = {};
var len = a.length;
for (j = 0; j < len; j++) {
var c = $.extend({}, a[j]);
var children_length = (c.children).length;
for (i = 0; i < children_length; i++) {
var temp = ((a[j].children)[i]).name;
new_obj[temp] = c.id;
}
}
document.write(JSON.stringify(new_obj));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
You'll only need to use underscore if you're supporting browsers without native reduce and forEach array methods, but you can do it like this.
var result = _.reduce(array, function(memo, entry) {
_.each(entry.children, function(child) {
memo[child.name] = entry.id;
});
return memo;
}, {});
function expand(list){
return _.reduce(list,function(a,b) {
_.each(b.children, function(c) {
a[c.name] = b.id;
});
return a;
},{});
}
Check the output for your sample here

Categories

Resources