Recursive function to find top level parent given an id - javascript

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

Related

Create a hierarchy of arrays from a flat array

Consider I have an array like this
const ar = [
{id: 1, name: "A", parent: null},
{id: 2, name: "B", parent: 1},
{id: 11, name: "AA", parent: 1},
{id: 12, name: "AB", parent: 1},
{id: 111, name: "AAA", parent: 11},
{id: 41, name: "CC", parent: 4},
{id: 4, name: "C", parent: 1},
];
How do I create a hierarchy of just one object like this
{
id: 1,
name: "A",
parent: null,
children: [
{
id: 11,
name: "AA",
parent: 1,
children: [
{id: 111, name: "AAA", parent: 11}],
},
{id: 2, name: "B", parent: 1, children: []},
{
id: 4,
name: "C",
parent: 1,
children: [{id: 41, name: "CC", parent: 4, children: []}],
},
],
}
The id is actually not a number in my actual app. It's a random string BTW.
I could do it recursively by drilling through the children array but it is not the most effective way. Can somebody help please?
const ar = [
{id: 1, name: "A", parent: null},
{id: 2, name: "B", parent: 1},
{id: 11, name: "AA", parent: 1},
{id: 12, name: "AB", parent: 1},
{id: 111, name: "AAA", parent: 11},
{id: 41, name: "CC", parent: 4},
{id: 4, name: "C", parent: 1},
];
const hierarchy = (arr) => {
const map = {};
let root;
for (const ele of arr) {
map[ele.id] = ele;
ele.children = [];
}
for (const ele of arr) {
if (map[ele.parent] != undefined)
map[ele.parent].children.push(ele);
else
root = ele;
}
return root;
}
console.log(hierarchy(ar));
First step is to map the items by the id so you have an easy look up so you are not looping over the array multiple times. After that you just need to loop over and add a children array to the parent and add the reference.
const ar = [
{id: 1, name: "A", parent: null},
{id: 2, name: "B", parent: 1},
{id: 11, name: "AA", parent: 1},
{id: 12, name: "AB", parent: 1},
{id: 111, name: "AAA", parent: 11},
{id: 41, name: "CC", parent: 4},
{id: 4, name: "C", parent: 1},
];
// make a look up by the id
const mapped = ar.reduce((acc, item) => {
acc[item.id] = item;
return acc;
}, {});
// loop over
const result = ar.reduce((acc, item) => {
// if there there is no parent, we know it is the first so return it
const parentId = item.parent;
if (!parentId) return item;
// if we have a parent, see if we found this yet, if not add the array
mapped[parentId].children = mapped[parentId].children || [];
// set the item as a child
mapped[parentId].children.push(item);
return acc;
}, null);
console.log(result)
You can iterate through the array and push the elem to the right place each time.
To get the root, you can then retrieve the element without parent.
const arr = [{id: 1, name: "A", parent: null},
{id: 2, name: "B", parent: 1},
{id: 11, name: "AA", parent: 1},
{id: 12, name: "AB", parent: 1},
{id: 111, name: "AAA", parent: 11},
{id: 41, name: "CC", parent: 4},
{id: 4, name: "C", parent: 1}]
arr.forEach(elem => elem.children = [])
arr.forEach(elem => {
if(elem.parent){
const parent = arr.find(x => x.id === elem.parent)
if(parent)parent.children.push(elem)
}
})
console.log(arr.find(x => !x.parent))
Note : If you want to optimize a little more, you can add the children array in the second forEach

Turn array of objects into structured object? [duplicate]

I am working on a Nodejs project. I have to create a function which takes an object (a child category) like:
{
id: 65,
name: 'Outdoor',
parent_id: 2
}
Now I want my function to check for the parent category by using parent_id from database and return an array/object like this:
{
id: 2,
name: 'Furniture',
parent: {
id: 1,
name: 'Residential',
parent: {
id: ...,
name: ...,
parent: {
and so on..
}
}
}
}
This is what I have done so far:
* _get_category_parents(category, _array) {
if(_array === undefined) _array = []
if( category.parent_id !== 0 ) {
const c_parent = yield this.Database.from('categories').where('id', '=', category.parent_id)
_array.push({id: c_parent[0].id, name: c_parent[0].name})
yield this._get_category_parents(c_parent[0], _array)
}
return _array
}
And calling this function like this:
const parents = yield this._get_category_parents(category)
This returns me an array of parents like this:
[
{
"id": 2,
"name": "Furniture"
},
{
"id": 1,
"name": "Residential"
}
]
I want Residential object to be appended in Furniture's parent node.
I have spent too much time on this but not getting what I want. Any help would be deeply appreciated.
What you want to think about is a recursive solution.
Since you're calling a database, it's probably unlikely, but if the lookup by id is synchronous, you might do it with code something like the following (note that I'm faking a db here):
const getHierarchy = (lookup, child) => {
const {id, name, parent_id} = lookup(child) ||
{id: null, name: null, parent_id: 0}
return parent_id == 0
? {id, name, parent_id}
: {...{id, name}, ...{parent: getHierarchy(lookup, {parent_id})}}
}
const items = [
{id: 1, name: 'Residential', parent_id: 5},
{id: 2, name: 'Furniture', parent_id: 1},
{id: 3, name: 'Other', parent_id: 0},
{id: 4, name: 'FooBar', parent_id: 3},
{id: 5, name: 'Stuff', parent_id: 0}
]
const lookup = child => items.find(item => item.id == child.parent_id)
const item = {id: 65, name: 'Outdoor', parent_id: 2}
console.log(getHierarchy(lookup, item))
You would have to write an appropriate lookup function, presumably using this.Database.from(...). You might also want to simplified version that bakes in your lookup function, in which case, you might write
const getAncestry = (item) => getHierarchy(lookup, item)
If, as seems more likely, your lookup is asynchronous, then that will affect getHierarchy and how you call it. Here's one possibility:
const getHierarchy = async (lookup, child) => {
const {id, name, parent_id} = await lookup(child) ||
{id: null, name: null, parent_id: 0}
return parent_id == 0
? {id, name, parent_id}
: {...{id, name}, ...{parent: await getHierarchy(lookup, {parent_id})}}
}
const items = [
{id: 1, name: 'Residential', parent_id: 5},
{id: 2, name: 'Furniture', parent_id: 1},
{id: 3, name: 'Other', parent_id: 0},
{id: 4, name: 'FooBar', parent_id: 3},
{id: 5, name: 'Stuff', parent_id: 0}
]
const lookup = async child => new Promise(
(resolve, reject) => setTimeout(
() => resolve(items.find(item => item.id == child.parent_id)),
1000
)
)
const getAncestry = async item => getHierarchy(lookup, item)
const item = {id: 65, name: 'Outdoor', parent_id: 2}
getAncestry(item).then(console.log)
Note the change in how you call the function. You need to call .then() on the resulting promise to get any useful behavior.

How to compute the distance between a leaf and the root node in a tree structure

I'm trying to create a tree structure. But I don't know how to use recursive methods.
I'm loading an array that contains info and children.
What I would like to know is, how far down is a node from the root?
For exmaple:
element with id 1 is 0 steps from the root
element with id 12 is 1 steps from the root
element with id 122 is 2 steps from the root
element with id 13 is 1 steps from the root
const data = [
{id: 1, title: 'foo', children: [
{id: 11, parentId: 1, title: 'bar',},
{id: 12, parentId: 1, title: 'baz', children: [
{id: 121, parentId: 12, title: 'qux'},
{id: 122, parentId: 12, title: 'quz'}
]},
{id: 13, parentId: 1, title: 'corge'}
]}
];
You can write a recursive method for that:
const data = [
{id: 1, title: 'foo', children: [
{id: 11, parentId: 1, title: 'bar'},
{id: 12, parentId: 1, title: 'baz', children: [
{id: 121, parentId: 12, title: 'qux'},
{id: 122, parentId: 12, title: 'quz'}
]},
{id: 13, parentId: 1, title: 'corge'}
]}
];
function findDistance(data, id) {
for(const elem of data){
if(elem.id === id) return 0
if(!elem.children) continue
const value = findDistance(elem.children, id)
if(!Number.isNaN(value)) return value + 1
}
//Not found, return NaN
return NaN
}
console.log(findDistance(data, 1)) //0
console.log(findDistance(data, 12)) //1
console.log(findDistance(data, 122)) //2
console.log(findDistance(data, 13)) //1
console.log(findDistance(data, 0)) //Not found, NaN

Sorting an array with child - parent dependencies

I am given the following array of objects:
let data = [{id: 3, name: ''}, {id: 4, name: ''}, {id: 5, name: '', parent: 3}, {id: 6, name: '', parent: 5}, {id: 7, name: '', parent: 4}, {id: 8, name: '', parent: 8}];
How do I create a nested list, where an object with corresponding parent element is appended to a new <li> element? So to make it clear it would look something like this:
<li>
object id:3 (parent)
<li>object id:5</li>(child)
</li>`
Any clues? :)
You could do this with two functions one to create nested tree structure and another one to create a nested html structure based on previously create tree structure.
let data = [{id: 3, name: ''}, {id: 4, name: ''}, {id: 5, name: '', parent: 3}, {id: 6, name: '', parent: 5}, {id: 7, name: '', parent: 4}, {id: 8, name: '', parent: 8}];
const toTree = (data, pid = undefined) => {
return data.reduce((r, e) => {
if (pid == e.parent) {
const obj = { ...e }
const children = toTree(data, e.id);
if (children) obj.children = children
r.push(obj)
}
return r
}, [])
}
const toHtml = (data) => {
const ul = document.createElement('ul')
data.forEach(e => {
const li = document.createElement('li');
const text = document.createElement('span')
text.textContent = `object id: ${e.id}`;
li.appendChild(text)
const children = toHtml(e.children);
if (children) li.appendChild(children)
ul.appendChild(li)
})
return ul
}
const tree = toTree(data)
const html = toHtml(tree)
document.body.appendChild(html)

Create a nested array recursively in Node.js

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);

Categories