Create a hierarchy of arrays from a flat array - javascript

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

Related

How to group an array of objects by value for the same item?

I have a question, how can I map and reduce an array like this:
[
{
id: 1,
Price: 50,
Item: {id: 1, Name: "A"},
Date: {id: 1, Start: "202001"}
},
{
id: 2,
Price: 100,
Item: {id: 1, Name: "A"},
Date: {id: 2, Start: "202002"}
},
{
id: 3,
Price: 200,
Item: {id: 2, Name: "B"},
Date: {id: 1, Start: "202001"}
}
]
I'm writing an app in React and I want to show those values grouped in a table.
It should look something like this:
ITEM
202001
202002
A
50
100
B
-
200
I would like to be able to do this with the array:
[
{
id: 1,
Item: {id: 1, Name: "A"},
Date: [{id: 1, Start: "202001",Price: "50"},{id: 2, Start: "202002",Price: "100"}]
},
{
id: 2,
Item: {id: 2, Name: "B"},
Date: {id: 1, Start: "202001",Price: "200"}
}
]
Any suggestions to get to what I need?
You can use Array.prototype.reduce() and then use Object.values like so:
const arr = [
{
id: 1,
Price: 50,
Item: {id: 1, Name: "A"},
Date: {id: 1, Start: "202001"}
},
{
id: 2,
Price: 100,
Item: {id: 1, Name: "A"},
Date: {id: 2, Start: "202002"}
},
{
id: 3,
Price: 200,
Item: {id: 2, Name: "B"},
Date: {id: 1, Start: "202001"}
}
]
const res = Object.values(arr.reduce((acc, {Item, Date, Price}) => {
if(!acc[Item.id]) {
acc[Item.id] = {
id: Item.id,
Item,
Date: [{...Date, Price}]
};
} else {
acc[Item.id].Date = [...acc[Item.id].Date, {...Date, Price}];
}
return acc;
}, {}));
console.log(res);
You can use groupBy method of lodash to group your dataset according to Item.Name.
First get the package:
npm i lodash.groupby
Then use it in your code as
import groupBy from 'lodash.groupby'
const tempData = [
{
id: 1,
Price: 50,
Item: {id: 1, Name: "A"},
Date: {id: 1, Start: "202001"}
},
{
id: 2,
Price: 100,
Item: {id: 1, Name: "A"},
Date: {id: 2, Start: "202002"}
},
{
id: 3,
Price: 200,
Item: {id: 2, Name: "B"},
Date: {id: 1, Start: "202001"}
}
]
groupBy(tempData, 'Item.Name')
/*
Will result as below
{
A: [
//objects with 'Item.Name' === 'A'
],
B: [
//objects with 'Item.Name' === 'B'
]
}
*/
Then, you need to populate your table with the keys inside the response from groupBy

How to create dynamic nested accordion in React

How i can create an accordion like this
-parent
-subparent1
-subparent2
...
-subparentN
- child
Here is my data.
//parents
{id: 1, name: "", parent_id: null}
{id: 2, name: "", parent_id: null }
{id: 3, name: "", parent_id: null }
{id: 4, name: "", parent_id: null}
//children
{id: 5, name: "", parent_id: 1}
{id: 6, name: "", parent_id: 1}
{id: 7, name: "", parent_id: 5}
{id: 8, name: "", parent_id: 5}
{id: 9, name: "", parent_id: 6}
{id: 10, name: "", parent_id: 6}
{id: 11, name: "", parent_id: 6}
{id: 12, name: "", parent_id: 6}
{id: 13,name: "", parent_id: 6}
{id: 14, name: "", parent_id: 6}
Basically the ones who have parent_id:null are parents, and when I click on them I want their potential children to be displayed if they have any, now this isn't that hard, but what I can't understand is how to display subparent's children
You can loop over the list of all your items and add each sub item to their parent. Afterwards you just have to loop over all the items in the array and create their respective html.
const items = [
//parents
{id: 1, name: "1", parent_id: null},
{id: 2, name: "2", parent_id: null },
{id: 3, name: "3", parent_id: null },
{id: 4, name: "4", parent_id: null},
//children
{id: 5, name: "5", parent_id: 1},
{id: 6, name: "6", parent_id: 1},
{id: 7, name: "7", parent_id: 5},
{id: 8, name: "8", parent_id: 5},
{id: 9, name: "9", parent_id: 6},
{id: 10, name: "10", parent_id: 6},
{id: 11, name: "11", parent_id: 6},
{id: 12, name: "12", parent_id: 6},
{id: 13,name: "13", parent_id: 6},
{id: 14, name: "14", parent_id: 6},
];
for(const item of items) {
// Find the parent object
const parent = items.find(({ id }) => id === item.parent_id);
// If the parent is found add the object to its children array
if(parent) {
parent.children = parent.children ? [...parent.children, item] : [item]
}
};
// Only keep root elements (parents) in the main array
const list = items.filter(({ parent_id }) => !parent_id);
// console.log(list);
// Show tree (vanillaJS, no REACT)
for(const item of list) {
// Create a new branch for each item
const ul = createBranch(item);
// Append branch to the document
document.body.appendChild(ul);
}
function createBranch(item) {
// Create ul item for each branch
const ul = document.createElement("ul");
// Add current item as li to the branch
const li = document.createElement("li");
li.textContent = item.name;
ul.appendChild(li);
// Check if there are children
if(item.children) {
// Create a new branch for each child
for(const child of item.children) {
const subUl = createBranch(child);
// Append child branch to current branch
ul.appendChild(subUl);
}
}
return ul;
}
ul {
margin: 0;
padding-left: 2rem;
}
I think your data structure should be a nested object similarly to how you see your menu working, i.e.
[
{id: 1, name:"", children: [
{id: 5, name: "", children: []},
{id: 6, name: "", children: [
{id: 7, name: "", children: []},
{id: 8, name: "", children: []},
]},
]},
{id: 2, name:"", children:[]}
]
Then you would need a function to output each item:
const returnMenuItem = (item, i) =>{
let menuItem;
if (item.children.length===0) {
menuItem = <div key={i}>{item.label}</div>;
}
else {
let menuItemChildren = item.children.map((item,i)=>{
let menuItem = returnMenuItem(item,i);
return menuItem;
});
menuItem = <div key={i}>
<div>{item.label}</div>
<div>
{menuItemChildren}
</div>
</div>;
}
return menuItem;
}
And you would invoke this function by going through your items:
let menuItems = data.map((item,i)=>{
let menuItem = returnMenuItem(item,i);
return menuItem;
});
A complete component would look something like the following:
import React, { useState, useEffect } from "react";
import { UncontrolledCollapse } from "reactstrap";
const Menu = (props) => {
const [loading, setLoading] = useState(true);
const [items, setItems] = useState([]);
useEffect(() => {
const menuData = [
{
id: 1,
name: "test 1",
children: [
{ id: 5, name: "test 5", children: [] },
{
id: 6,
name: "test 6",
children: [
{ id: 7, name: "test 7", children: [] },
{ id: 8, name: "test 8", children: [] }
]
}
]
},
{ id: 2, name: "test 2", children: [] }
];
const returnMenuItem = (item, i) => {
let menuItem;
if (item.children.length === 0) {
menuItem = (
<div className="item" key={i}>
{item.name}
</div>
);
} else {
let menuItemChildren = item.children.map((item, i) => {
let menuItem = returnMenuItem(item, i);
return menuItem;
});
menuItem = (
<div key={i} className="item">
<div className="toggler" id={`toggle-menu-item-${item.id}`}>
{item.name}
</div>
<UncontrolledCollapse
className="children"
toggler={`#toggle-menu-item-${item.id}`}
>
{menuItemChildren}
</UncontrolledCollapse>
</div>
);
}
return menuItem;
};
const load = async () => {
setLoading(false);
let menuItems = menuData.map((item, i) => {
let menuItem = returnMenuItem(item, i);
return menuItem;
});
setItems(menuItems);
};
if (loading) {
load();
}
}, [loading]);
return <div className="items">{items}</div>;
};
export default Menu;
And a minimum css:
.item {
display: block;
}
.item > .children {
padding: 0 0 0 40px;
}
.item > .toggler {
display: inline-block;
}
.item::before {
content: "-";
padding: 0 5px 0 0;
}
You can find a working code sandbox here sandbox
I think that your data structure has a flaw. Beside child to parent relation, you should keep track of parent to child relationship. Now you will be able to easily iterate through the data and render sub parent's children.
{id: 1, parent_id: null, children: [
{id: 2, parent_id: 1, children: []},
{id: 3, parent_id: 1, children: [
{id: 4, parent_id: 3, children: []}
]}
]}
If you need to keep all objects inline, you can structure your data like:
{id: 1, parent_id: null, children: [2, 3]}
{id: 2, parent_id: 1, children: []},
{id: 3, parent_id: 1, children: [4]},
{id: 4, parent_id: 3, children: []}

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

In JavaScirpt, how do I filter the elements of the same field in two arrays and return a new two-dimensional array

This is an example:
I want to regroup arry2 according to the fields in arry1
var arry1 = [
{id: 1, parentId: 0, name: "phone"},
{id: 2, parentId: 1, name: "nick"}
];
var arry2 = [
{id: 7, parentId: 0, name: "phone_item1"},
{id: 8, parentId: 1, name: "phone_item2"},
{id: 9, parentId: 0, name: "nick_item1"},
{id: 10, parentId: 1, name: "nick_item2"}
];
let newArrys = arry1.filter((item)=>{
return leve_two.indexOf(arry2.parentId) == -1
})
I want to return a two-dimensional array:
[[
{id: 7, parentId: 0, name: "phone_item1"},
{id: 9, parentId: 0, name: "nick_item1"}
],[
{id: 8, parentId: 1, name: "phone_item2"},
{id: 10, parentId: 1, name: "nick_item2"}
]]
I tried Array.filter and so on.
Can you help me?
You can use filter() method along-with Object.values() to get the desired output:
const arr1 = [
{id: 1, parentId: 0, name: "phone", level: 0, productCount: 0},
{id: 2, parentId: 1, name: "nick", level: 0, productCount: 0}
];
const arr2 = [
{id: 7, parentId: 0, name: "phone_item1", level: 1, productCount: 0},
{id: 8, parentId: 1, name: "phone_item2", level: 1, productCount: 0},
{id: 9, parentId: 0, name: "nick_item1", level: 1, productCount: 0},
{id: 10, parentId: 1, name: "nick_item2", level: 1, productCount: 0}
];
const filterIds = arr1.map(({ parentId }) => parentId);
const arr3 = Object.values(arr2.reduce((r, c) => {
r[c.parentId] = r[c.parentId] || [];
r[c.parentId].push(c);
return r;
}, {}));
console.log(arr3);
.as-console-wrapper { max-height: 100% !important; top: 0; }
It looks just like grouping arry2 by parentId and arry1 looks useless 🤔
Use some lib for this. For example Ramda way:
const result = R.pipe(
R.groupBy(R.prop("parentId")),
R.toPairs,
R.map(R.last)
)(arry2)

How can I convert normal array of objects to multilevel array in javascript? [duplicate]

This question already has answers here:
Build tree array from flat array in javascript
(34 answers)
Closed 4 years ago.
My normal array object like this :
var b = [
{id: 1, name: 'England',parent_id: null},
{id: 2, name: 'Spain',parent_id: null},
{id: 3, name: 'Chelsea',parent_id: 1},
{id: 4, name: 'Manchester United',parent_id: 1},
{id: 5, name: 'Real Madrid',parent_id: 2},
{id: 6, name: 'Barcelona',parent_id: 2},
{id: 7, name: 'Hazard',parent_id: 3},
{id: 8, name: 'Morata',parent_id: 3},
{id: 9, name: 'Pogba',parent_id: 4},
{id: 10, name: 'Lukaku',parent_id: 4},
{id: 11, name: 'Ronaldo',parent_id: 5},
{id: 12, name: 'Bale',parent_id: 5},
{id: 13, name: 'Messi',parent_id: 6},
{id: 14, name: 'Suarez',parent_id: 6},
];
I want to convert the object array to be like this :
var b = [
{
name: 'England',
children: [
{
name: 'Chelsea',
children: [
{name: 'Hazard'},
{name: 'Morata'}
]
},
{
name: 'Manchester United',
children: [
{name: 'Pogba'},
{name: 'Lukaku'}
]
}
]
},
{
name: 'Spain',
children: [
{
name: 'Real Madrid',
children: [
{name: 'Ronaldo'},
{name: 'Bale'}
]
},
{
name: 'Barcelona',
children: [
{name: 'Messi'},
{name: 'Suarez'}
]
},
]
}
];
It seems it will be separated using key parent_id
But i'm still confused to implement it
How can I convert the array object like that?
Please help me guys
.filter() the b so it contains only items with parent_id: null
.map() remaining items, assigning children to them
.map() children for each of the root level parents to return them without parent_id field (optional, not in the example)
var b = [
{id: 1, name: 'England',parent_id: null},
{id: 2, name: 'Spain',parent_id: null},
{id: 3, name: 'Chelsea',parent_id: 1},
{id: 4, name: 'Manchester United',parent_id: 1},
{id: 5, name: 'Real Madrid',parent_id: 2},
{id: 6, name: 'Barcelona',parent_id: 2},
{id: 7, name: 'Hazard',parent_id: 3},
{id: 8, name: 'Morata',parent_id: 3},
{id: 9, name: 'Pogba',parent_id: 4},
{id: 10, name: 'Lukaku',parent_id: 4},
{id: 11, name: 'Ronaldo',parent_id: 5},
{id: 12, name: 'Bale',parent_id: 5},
{id: 13, name: 'Messi',parent_id: 6},
{id: 14, name: 'Suarez',parent_id: 6},
];
const done = b.filter(person => !person.parent_id).map(person => {
return {
id : person.id,
name : person.name,
children: b.filter(child => child.parent_id == person.id)
}
});
console.log(done);

Categories