Create a nested array recursively in Node.js - javascript

Following is my array
[
{id: 1, title: 'hello', parent: {number:0}},
{id: 2, title: 'hello', parent: {number:0}},
{id: 3, title: 'hello', parent: {number:1}},
{id: 4, title: 'hello', parent: {number:3}},
{id: 5, title: 'hello', parent: {number:4}},
{id: 6, title: 'hello', parent: {number:4}},
{id: 7, title: 'hello', parent: {number:3}},
{id: 8, title: 'hello', parent: {number:2}}
]
and I want to have objects nested like this as output :
[
{id: 1, title: 'hello', parent: 0, children: [
{id: 3, title: 'hello', parent: 1, children: [
{id: 4, title: 'hello', parent: 3, children: [
{id: 5, title: 'hello', parent: 4},
{id: 6, title: 'hello', parent: 4}
]},
{id: 7, title: 'hello', parent: 3}
]}
]},
{id: 2, title: 'hello', parent: 0, children: [
{id: 8, title: 'hello', parent: 2}
]}
]
Please help me with a recursive function to do this in node.js.
Following is the recursive function is what I have tried:
function getNestedChildren(arr, parent) {
var out = []
for(var i in arr) {
if(arr[i].parent.number == parent.number) {
var children = getNestedChildren(arr, arr[i].id)
if(children.length) {
arr[i].children = children
}
out.push(arr[i])
}
}
return out
}
Please help me to solve this. I am a newbie in this.

Renaming some variables helped me solve this.
getNestedChildren returns an array of children, so rename out to children.
the children returned by the recursive call are the grandchildren of the parent being processed in the call. So call the result of the recursive call grandchildren.
The problem that caused the code to not work:
line 4 of the posted code uses the id property of the parent parameter. So either ensure that every call to getNestedChidren provides an object with such a property (as below), or change the second argument to parentNumber and just supply the numeric value of the number property. Your choice.
Lastly, avoid using for ... in loops to iterate an array - please do a web search for more information and discussion.
var array = [
{id: 1, title: 'hello', parent: {number:0}},
{id: 2, title: 'hello', parent: {number:0}},
{id: 3, title: 'hello', parent: {number:1}},
{id: 4, title: 'hello', parent: {number:3}},
{id: 5, title: 'hello', parent: {number:4}},
{id: 6, title: 'hello', parent: {number:4}},
{id: 7, title: 'hello', parent: {number:3}},
{id: 8, title: 'hello', parent: {number:2}}
]
function getNestedChildren(arr, parent) {
var children = [];
for(var i =0; i < arr.length; ++i) {
if(arr[i].parent.number == parent.number) {
var grandChildren = getNestedChildren(arr, {number: arr[i].id})
if(grandChildren.length) {
arr[i].children = grandChildren;
}
children.push( arr[i]);
}
}
return children;
}
var nest = getNestedChildren(array,{number: 0});
console.log( nest);

In case anyone wants to know the solution with a flattened initial array, as per user992731's request:
var array = [
{id: 1, title: 'hello', parent: 0},
{id: 2, title: 'hello', parent: 0},
{id: 3, title: 'hello', parent: 1},
{id: 4, title: 'hello', parent: 3},
{id: 5, title: 'hello', parent: 4},
{id: 6, title: 'hello', parent: 4}
];
function getNestedChildren(arr, parent) {
var children = [];
for(var i =0; i < arr.length; ++i) {
if(arr[i].parent == parent) {
var grandChildren = getNestedChildren(arr, arr[i].id);
if(grandChildren.length) {
arr[i].children = grandChildren;
}
children.push(arr[i]);
}
}
return children;
}
var nest = getNestedChildren(array,0);
console.log(nest);

Related

array grouping conversion key start_section to end_section

Let's say I have below array :
[{id: 1, name: "header"},{id: 2, name: "start_section"},
{id: 3, name: "input"}, {id: 5, name: "image"},
{id: 6, name: "end_section"}, {id: 7, name: "header"},
{id: 8, name: "start_section"}, {id: 9, name: "input"},
{id: 10, name: "date"}, {id: 11, name: "end_section"},
]
I want this :
[{
id: 1,
name: "header"
}, {
id: 2,
name: "section",
child: [{
{
id: 3,
name: "input"
},
{
id: 5,
name: "image"
},
}],
}, {
id: 7,
name: "header"
}, {
id: 8,
name: "section",
child: [{
{
id: 9,
name: "input"
},
{
id: 10,
name: "date"
},
}]
}]
if I find start_section and end_section then it will form a new object , How do I change the array by grouping by the key specified in the example above in javascript?
If I get it right, you want something like this? It's simple approach with for loop and some flags:
const arr = [{id: 1, name: "header"},{id: 2, name: "start_section"},
{id: 3, name: "input"}, {id: 5, name: "image"},
{id: 6, name: "end_section"}, {id: 7, name: "header"},
{id: 8, name: "start_section"}, {id: 9, name: "input"},
{id: 10, name: "date"}, {id: 11, name: "end_section"},
];
// Set final array
let finalArray = [];
// Set sub object for groups (Childs)
let subObj = {};
// Flag for sub section stuff
let inSubSection = false;
// Loop array
for(let i = 0; i < arr.length; i++) {
if(arr[i].name === "end_section") {
// If we have end_section
// Set flag off
inSubSection = false;
// Push sub object to final array
finalArray.push(subObj);
} else if(arr[i].name === "start_section") {
// If we get start_section
// Set flag on
inSubSection = true;
// Set new sub object, set childs array in it
subObj = {
id: arr[i].id,
name: "section",
child: []
};
} else if(inSubSection) {
// If we have active flag (true)
// Push child to section array
subObj.child.push({
id: arr[i].id,
name: arr[i].name
});
} else {
// Everything else push straight to final array
finalArray.push(arr[i]);
}
}
// Log
console.log(finalArray);
you can Array.reduce function
let array = [{id: 1, name: "header"},{id: 2, name: "start_section"},
{id: 3, name: "input"}, {id: 5, name: "image"},
{id: 6, name: "end_section"}, {id: 7, name: "header"},
{id: 8, name: "start_section"}, {id: 9, name: "input"},
{id: 10, name: "date"}, {id: 11, name: "end_section"},
]
let outPut = array.reduce( (acc, cur, i, arr) => {
if (cur.name == "start_section") {
//find the end element
let endIndex = arr.slice(i).findIndex( e => e.name == "end_section") + i ;
//splice the child elements from base array
let child = arr.splice(i + 1, endIndex - 1 );
//remove last element that has "end_section"
child.splice(-1);
//append child
cur.child = child;
//sert the name as "section"
cur.name = "section";
}
//add to accumulator
acc.push(cur);
return acc;
}, []);
console.log(outPut);

Recursive function to find top level parent given an id

Given the following data set:
const accounts = [
{id: 2, children: [1,22,69], parentId: null},
{id: 3, children: [140, 122, 580], parentId: null},
{id: 1, children: [4,5,6], parentId: 2},
{id: 22, children: [8,9,2], parentId: 2},
{id: 4, children: [45,54,61], parentId: 1},
{id: 6, children: [40,89,20], parentId: 1},
{id: 40, children: [], parentId: 6},
....
]
I need to create a function that takes and id as argument and returns a tree, starting with the top most level parent and it's children (and siblings).
In the above example, there are only 2 top level "accounts", id:2 and id:3. So the function call might look like findTree(89) , it should return the tree starting with the account id 2, and it's children, but will obviously leave out account id 3 and it's children, since that top level account has nothing to do with top level account of id 2, so the ideal response would be:
{
id: 2,
children: [
{ id: 1, children: [{id: 540, children: [{ id: 78},{}], parentId:1], parentId: 2},
.....
],
parentId: null
}
What would be the best way to go about it ? I've tried a recursive function but I'm not getting anywhere near to a solution.
EDIT: Here part of the code:
(groupArray is an array containing all items in a flat list, without hierarchy)
const makeTreeById = itemId => {
const startNode = _.find(groupArray, {id: itemId}) // grab the actual item, not the id
findTopParent(startNode)
}
and then the findTopParent fn
const findTop = item => {
let top = item;
if(top.parentId) {
top = _.find(groupArray, {id: top.parentId}
return findTop(top)
}
return top;
}
I was creating that function to simply have it return the top level account and from there I was planning on constructing the actual tree, the problem is that top does get me the top level but at some point it get reassigned with the immediate parent.
SECOND EDIT: Sorry about all the confusion guys, as you can see, I'm really new.
I have an array that includes all items I would need. So it kinda looks like this:
// children here are only ids, not the actual elements, the element are part of // the list, but the children array for each element is just a reference.
data = [
{id: 1, children: [4,5,6], parentId: null},
{id: 2, children: [7,8,9], parentId: null},
{id: 3, children: [10,11,12], parentId: null},
{id: 4, children: [13,14,15], parentId: 1},
{id: 10, children: [20,21,22], parentId: 3}
{id: 14, children: [], parentId: 4}
....
]
You can find the desired results with function topParent. Just look for parent being null in each iteration.
const accounts = [
{id: 2, children: [1,22,69], parentId: null},
{id: 3, children: [140, 122, 580], parentId: null},
{id: 1, children: [4,5,6], parentId: 2},
{id: 22, children: [8,9,2], parentId: 2},
{id: 4, children: [45,54,61], parentId: 1},
{id: 6, children: [40,89,20], parentId: 1},
{id: 40, children: [], parentId: 6}
];
function topParent(id) {
var obj = accounts.find((v) => v.id == id);
return obj.parentId == null ? obj : topParent(obj.parentId)
}
console.log(topParent(6));
actually they are many way to achieve the expected tree. In performance manner you should determine if you will have complexity (in term of iteration) on the deep of your tree or | and on how many items in total you will have.
I have assume the complexity will be more on how many items in total you will have.
exemple : big amount of accounts with only small amount of nested childrens.
Introduction : Following you have type and sample array.
interface IEntity {
id: number;
children: number[];
parentId?: number;
}
interface IEntityNested {
id: number;
children: IEntityNested[];
parentId?: number;
}
const accounts: IEntity[] = [
{id: 1, children: [3], parentId: null},
{id: 2, children: [], parentId: null},
{id: 3, children: [4], parentId: 1},
{id: 4, children: [], parentId: 3},
];
For that i prupose you to start by searching for any particular id what is the top of you tree. The element which doesn't have any other top element.
const findTopParent = (id: number): IEntity => {
let account = accounts.find(acc => acc.id === id);
if(account.parentId !== null) {
account = findTopParent(account.parentId);
}
return account;
};
For id 4 it should return account id 1
const topParent = findTopParent(4);
console.log(topParent.id); // Print 1.
then from your topParent you can build the nested tree from the top to the bottom.
const buildTreeFromSpecificAccount = (account: IEntity): IEntityNested => {
const nestedAccount = {...account,children: []};
account.children.forEach(childId => {
nestedAccount.children.push(
buildTreeFromSpecificAccount(
accounts.find(acc => acc.id === childId)
)
);
})
return nestedAccount;
}
// Build the tree from the top parent.
const tree = buildTreeFromSpecificAccount(topParent);
And voilĂ  !
Side note :
You can way more improve the performance by changing your data array by indexed object like following :
const accountOrdered: {[id: number]: IEntity} = {
1: {id: 1, children: [3], parentId: null},
2: {id: 2, children: [], parentId: null},
3: {id: 3, children: [4], parentId: 1},
4: {id: 4, children: [], parentId: 3},
};
Like this instead of doing accounts.find(acc => acc.id === childId) looping on your array to find entry by id. you can do accountOrdered[childId]
live sample

How to convert a list of object to a tree [duplicate]

This question already has answers here:
JavaScript format array of objects into nested children
(3 answers)
Create nested array data from an array of objects
(5 answers)
Convert array of objects with parent / child relationships to array of nested objects
(1 answer)
Closed 3 years ago.
I have a data like this:
[
{
ID: 1,
Title: 't1',
parentID: null,
},
{
ID: 2,
Title: 't2',
parentID: null,
},
{
ID: 50,
Title: 'tt',
parentID: 2,
},
{
ID: 30,
Title: 'ct',
parentID: 2,
},
{
ID: 6,
Title: 'cyt',
parentID: 50,
}
]
I want to convert the list above to a tree like structure, which each object has a children and this child-parent relationship is defined in the list using a parentID, if the parentID is null it means the object doesn't have any parent
[
{
ID: 1,
Title: 't1',
parentID: null,
},
{
ID: 2,
Title: 't2',
parentID: null,
children: [
{
ID: 50,
Title: 'tt',
parentID: 2,
children: [
{
ID: 6,
Title: 'cyt',
parentID: 50,
}
]
},
{
ID: 30,
Title: 'ct',
parentID: 2,
},
]
}
]
I am looking for a good way to do this which is both optimal and doesn't require lots of looping, since the objects might grow in number.
You can first create a mapping of IDs to objects, and then in one pass assign all children based on this mapping. Runtime complexity O(n).
var data = [
{
ID: 1,
Title: 't1',
parentID: null,
},
{
ID: 2,
Title: 't2',
parentID: null,
},
{
ID: 50,
Title: 'tt',
parentID: 2,
},
{
ID: 30,
Title: 'ct',
parentID: 2,
},
{
ID: 6,
Title: 'cyt',
parentID: 50,
}
] ;
const map = {};
for (let i= 0; i < data.length; i++) {
map[data[i].ID] = data[i];
}
for (let i= 0; i < data.length; i++) {
const parent = map[data[i].parentID];
if (parent) {
if (parent.children == null) {
parent.children = [data[i]];
} else {
parent.children.push(data[i]);
}
}
}
console.log(data.filter(e => e.parentID == null));

How to convert objects containing array into object of objects

My code:
function convert(arr, parent) {
var out = [];
for(var i in arr) {
if(arr[i].parent == parent) {
var children = convert(arr, arr[i].id)
if(children.length) {
arr[i].children = children
}
out.push(arr[i])
}
}
return out; //return Object.assign({}, out);tried this, but i lose parents childrens arrays
};
arras = [
{id: 1, name: "parent1", parent: null},
{id: 2, name: "children1", parent: 1},
{id: 3, name: "children2", parent: 1},
{id: 4, name: "parent2", parent: null},
{id: 5, name: "children3", parent: 4},
{id: 6, name: "children4", parent: 4}
]
console.log(convert(arras, null));
How final result should look
{
parent1: [
{name: "children1"},
{name: "children2"}
],
parent2: [
{name: "children3},
{name: "children4"}
]
}
What my output looks so far:
[
{id: 1, name: "parent1", parent: null}: [
{id: 2, name: "children1", parent: 1},
{id: 3, name: "children2", parent: 1},
],
{id: 4, name: "parent2", parent: null}: [
{id: 5, name: "children3", parent: 4},
{id: 6, name: "children4", parent: 4}
]
]
So firstly, what I have to do is convert main array to object, when I tend to do that, I lose both parent object arrays...Also need to change the way console displays objects, any help is appreciated.
You could build a tree with check if parent is a root node or not.
var data = [{ id: 1, name: "parent1", parent: null }, { id: 2, name: "children1", parent: 1 }, { id: 3, name: "children2", parent: 1 }, { id: 4, name: "parent2", parent: null }, { id: 5, name: "children3", parent: 4 }, { id: 6, name: "children4", parent: 4 }],
tree = function (data, root) {
var r = {},
o = {};
data.forEach(function (a) {
if (a.parent === root) {
r[a.name] = [];
o[a.id] = r[a.name];
} else {
o[a.parent] = o[a.parent] || [];
o[a.parent].push({ name: a.name });
}
});
return r;
}(data, null);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Try this.
function convert(arr) {
var parents = {};
for (var i in arr) {
if (arr[i].parent === null) {
parents[arr[i].id] = arr[i].name
}
}
var out = {}
for (i in arr) {
if (arr[i].parent !== null) {
var parentName = parents[arr[i].parent];
if (out.hasOwnProperty(parentName)) {
out[parentName].push(arr[i].name)
} else {
out[parentName] = [arr[i].name]
}
}
}
return out;
};
arras = [{
id: 1,
name: "parent1",
parent: null
},
{
id: 2,
name: "children1",
parent: 1
},
{
id: 3,
name: "children2",
parent: 1
},
{
id: 4,
name: "parent2",
parent: null
},
{
id: 5,
name: "children3",
parent: 4
},
{
id: 6,
name: "children4",
parent: 4
}
]
//console.log(convert(arras, null));
alert(JSON.stringify(convert(arras)));
But notice for multilevel it doesn't work correctly. If your need it, your must save map for all possible parent list
arras.forEach(function(el){
if(el.parent){
el.parent=arras.find(e=>e.id==el.parent)||(console.error("no parent:"+el.parent),undefined);
}
});
//resolved parent/childs....
var newmodel = arras.reduce(function(obj,el){
if(el.parent){
//child
obj[el.parent.name]=obj[el.parent.name]||[];//create new parent if neccessary
obj[el.parent.name].push({name:el.name});
}else{
//parent
obj[el.name]=obj[el.name]||[];
}
return obj;
},{});
http://jsbin.com/renicijufi/edit?console
Another way:
var arrays = [
{id: 1, name: 'parent1', parent: null},
{id: 2, name: 'children1', parent: 1},
{id: 3, name: 'children2', parent: 1},
{id: 4, name: 'parent2', parent: null},
{id: 5, name: 'children3', parent: 4},
{id: 6, name: 'children4', parent: 4}
];
// First, reduce the input arrays to id based map
// This step help easy to select any element by id.
arrays = arrays.reduce(function (map, el) {
map[el.id] = el;
return map;
}, {});
var result = Object.values(arrays).reduce(function (result, el) {
if (!el.parent) {
result[el.name] = [];
} else {
result[arrays[el.parent].name].push(el.name);
}
return result;
}, {});
console.log(result);
I think this meets your requirement
Obj = new Object();
for( i in arras){
person = arras[i];
if(person.parent != null){
if(!Obj.hasOwnProperty(person.parent)){
// here instead of the index you can use Obj["parent"+person.parent] get the exact thing. If you are using that use tha in rest of the code
Obj[person.parent] = new Array();
}
Obj[person.parent].push(person);
}
else{
if(!Obj.hasOwnProperty(person.id)){
// Some parents might have kids not in the list. If you want to ignore, just remove from the else.
Obj[person.id] = new Array()
}
}
}
Edit :
Obj = new Object();
for( i in arras){
person = arras[i];
if(person.parent != null){
if(!Obj.hasOwnProperty(person.parent)){
// here instead of the index you can use Obj["parent"+person.parent] get the exact thing. If you are using that use tha in rest of the code
Obj[person.parent] = new Array();
}
Obj[person.parent].push({name : person.name});
}
else{
if(!Obj.hasOwnProperty(person.id)){
// Some parents might have kids not in the list. If you want to ignore, just remove from the else.
Obj[person.id] = new Array()
}
}
}
Hope this helps. :)

Get all values in an object array with specific key (set::extract cakephp equivalent)

is there an equivalent of cakePHPs set::extract functionality (http://book.cakephp.org/2.0/en/core-utility-libraries/set.html#Set::extract)?
What i've got is something like that:
var arr = [{name: "test", children: [id: 1, title: "title1"]},
{name: "test2", children: [id: 2, title: "title2"]},
{name: "lipsum", children: [id: 1, title: "title1", id: 2, title: "title2"]},
{name: "lipsum2", children: [id: 3, title: "title3"]}]
What i want to do is:
var objs = filter({arr.children.id:1});
The result of 'objs' should be:
[{name: "test", children: [id: 1, title: "title1"]},
{name: "lipsum", children: [id: 1, title: "title1", id: 2, title: "title2"]}]
Thanks in advance.
You have an invalid object structure... that children array should actually be an object. Then you can do:
var filtered = arr.filter(function(item) {
return item.children.filter(function(child) {
return child.id === 1;
}).length
});
Correct data format:
[{
name: "test",
children: [{
id: 1,
title: "title1"
}]
}]
Demo: http://jsfiddle.net/588o5p69/
The correct structure for your array in JS would have to be this:
var arr = [{name: "test", children: [ {id: 1, title: "title1" } ]},
{name: "test2", children: [ { id: 2, title: "title2" } ]},
{name: "lipsum", children: [ {id: 1, title: "title1"}, {id: 2, title: "title2"}]},
{name: "lipsum2", children: [ {id: 3, title: "title3"} ]}];
On that structure, you can simply use underscore's _.filter:
var resArr= _.filter(arr, function(elem){ return elem.children.id === 1; });
If IE < 9 is not relevant, you can also go with ECMA Script 5's native filter function:
var resArr= arr.filter(function(elem){ return elem.children.id === 1; });

Categories