I have an array of objects like below:
const arr_obj = [
{
id: '1',
children: [],
type: 'TYPE1',
},
{
id: '2',
children: [
{
id: '1',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '2',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '3',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
]
type: 'TYPE2',
},
{
id: '3',
children: [
{
id: '4',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '5',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '6',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
]
type: 'TYPE2',
}
]
I have to find out the count of type: 'MAIN'. these 'MAIN' will be within type: "type2"
So the expected count is 6. The outer children array can be empty and sometimes inner children array with type: "type2" is not there at all examples like below:
children: [] //empty array
children: [
{
id: '1',
children: [],
type: 'TYPE1',
},
] //no children with type: 'TYPE2'
Could someone help me with this? New to programming.
EDIT:
what i have tried?
const findAllChildrenOfType = (obj, type) => {
let count = 0;
if (obj.type === type) count++;
if (obj.children) {
obj.children.forEach(child => {
const childCount = findAllChildrenOfType(child,
"MAIN");
count += childCount;
})
}
return count;
}
findAllChildrenOfType(arr_obj, "TYPE2");
But this gives me count always 0.
const arr_obj = [
{
id: '1',
children: [],
type: 'TYPE1',
},
{
id: '2',
children: [
{
id: '1',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '2',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '3',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
],
type: 'TYPE2',
},
{
id: '3',
children: [
{
id: '4',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '5',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
{
id: '6',
children: [
{
//some attributes
}
],
type: 'MAIN',
},
],
type: 'TYPE2',
}
];
let count = 0
const findAllChildrenOfType = (obj, type) => {
if (obj?.type === type) count++;
if(obj?.children?.length) {
obj.children.forEach((child) => {
findAllChildrenOfType(child, type);
});
}
// return count;
};
arr_obj.forEach((child) => {
if(child.type === 'TYPE2') {
findAllChildrenOfType(child, "MAIN");
}
});
console.log(count);
You could create a small function inside your findAllChildrenOfType() function, that will iterate over the array, and increment the count if it encounters an object with given type. Then you can return the count from the parent function, by subtracting the number of objects with initial type ("TYPE2" in this case), since they also get added up, when you increment the count in if(obj.type === type).
const arr_obj=[{id:"1",children:[],type:"TYPE1"},{id:"2",children:[{id:"1",children:[{}],type:"MAIN"},{id:"2",children:[{}],type:"MAIN"},{id:"3",children:[{}],type:"MAIN"}],type:"TYPE2"},{id:"3",children:[{id:"4",children:[{}],type:"MAIN"},{id:"5",children:[{}],type:"MAIN"},{id:"6",children:[{}],type:"MAIN"}],type:"TYPE2"}];
const findAllChildrenOfType = (arr_obj, type) => {
let count = 0;
let findElem = (arr_obj, type) => {
arr_obj.forEach(obj => {
if (obj.type === type) {
count++;
if (obj.children.length) {
return findElem(obj.children, "MAIN");
}
}
});
return count;
}
count = findElem(arr_obj, type);
let fil = arr_obj.filter(obj => obj.type === type).length;
return count - fil;
}
console.log(findAllChildrenOfType(arr_obj, "TYPE2"));
A small recursive loop.
const data=[{id:"1",children:[],type:"TYPE1"},{id:"2",children:[{id:"1",children:[{}],type:"MAIN"},{id:"2",children:[{}],type:"MAIN"},{id:"3",children:[{}],type:"MAIN"}],type:"TYPE2"},{id:"3",children:[{id:"4",children:[{}],type:"MAIN"},{id:"5",children:[{}],type:"MAIN"},{id:"6",children:[{}],type:"MAIN"}],type:"TYPE2"}];
const data2=[{id:"1",children:[],type:"TYPE1"},{id:"2",children:[{id:"1",children:[{}],type:"MAIN"},{id:"2",children:[{}],type:"MAIN"},{id:"3",children:[{}],type:"MAIN"}],type:"TYPE2"},{id:"3",children:[{id:"4",children:[{}],type:"MAIN"},{id:"5",children:[{}],type:"MAIN"},{id:"6",children:[{}],type:"MAIN"},{id:"7",children:[{}],type:"MAIN"},{id:"8",children:[{}],type:"MAIN2"}],type:"TYPE2"}];
function find(arr) {
let count = 0;
function loop(arr) {
for (const obj of arr) {
const { type, children } = obj;
if (type === 'TYPE2') loop(children);
if (type === 'MAIN') ++count;
}
}
loop(arr);
return count;
}
console.log(find(data));
console.log(find(data2));
Instead of use it with all object i use forEach and use function for each object like:
const arr_obj = [{
id: '1',
children: [],
type: 'TYPE1',
},
{
id: '2',
children: [{
id: '1',
children: [{
//some attributes
}],
type: 'MAIN',
},
{
id: '2',
children: [{
//some attributes
}],
type: 'MAIN',
},
{
id: '3',
children: [{
//some attributes
}],
type: 'MAIN',
},
],
type: 'TYPE2',
},
{
id: '3',
children: [{
id: '4',
children: [{
//some attributes
}],
type: 'MAIN',
},
{
id: '5',
children: [{
//some attributes
}],
type: 'MAIN',
},
{
id: '6',
children: [{
//some attributes
}],
type: 'MAIN',
},
],
type: 'TYPE2'
}
];
const findAllChildrenOfType = (obj, type) => {
let count = 0;
if (obj.type === type) {
if (type === 'MAIN') count++;
if (obj.children) {
obj.children.forEach(child => {
const childCount = findAllChildrenOfType(child,
"MAIN");
count += childCount;
})
}
}
return count;
}
let countAll = 0;
arr_obj.forEach(el => {
countAll += findAllChildrenOfType(el, "TYPE2");
});
console.log(countAll)
Related
I have an array of objects as the following
const sample = [
{ id: '1' },
{ id: '1.1' },
{ id: '1.1.1' },
{ id: '1.1.2' },
{ id: '1.2' },
{ id: '1.2.1' },
{ id: '1.2.1.1' },
{ id: '2' },
{ id: '2.1' }
];
I'd like to create a new array to include the children under their parent based on id property as the following
[
{
id: '1',
children: [
{
id: '1.1',
children: [
{ id: '1.1.1' },
{ id: '1.1.2' }
]
},
{
id: '1.2',
children: [
{
id: '1.2.1',
children: [{ id: '1.2.1.1' }]
}
]
}
]
},
{
id: '2',
children: [ { id: '2.1' } ]
}
]
I'm not sure how to do it or from where to start
Use a map to keep track of parents and children, then get the entries that are the roots as your result:
const data = [
{ id: '1' },
{ id: '1.1' },
{ id: '1.1.1' },
{ id: '1.1.2' },
{ id: '1.3' },
{ id: '1.3.1' },
{ id: '1.3.1.1' },
{ id: '2' },
{ id: '2.1' }
];
const map = new Map();
data.forEach(({ id }) => {
// exclude last bit to get parent id
const parent = id.split(".").slice(0, -1).join(".");
// our entry - needs to be like this since
// we want a reference to the same object
const entry = { id, children: [] };
// won't run if this is a root
if (parent)
// add child to parent
map.get(parent).children.push(entry);
// add to map
map.set(id, entry);
});
const result = Array.from(map)
// get roots - keys that only have one part
.filter(([key]) => key.split(".").length === 1)
// map to entry values
.map(([, value]) => value);
console.log(result);
.as-console-wrapper { max-height: 100% !important }
I have an nested array of objects with the following format
const arr1 = [
{
name: "internalcorp.com",
children: [
{
name: "internalcorp.com.child1",
children: [
{
name: "internalcorp.com.grandchild1",
children: []
},
{
name: "internalcorp.com.grandchild2",
children: []
}
]
},
{
name: "internalcorp.com.child2",
children: []
}
]
},
{
name: "internalcorpwebsite.com",
children: [
{
name: "internalcorpwebsite.com.child1",
children: []
}
]
}
];
Need to add an className property to the each array object based on its level. Like for example, parent should have level-0 group and children items should have level-x leaf where x is the level number relative to the main parent.
Output should look like
const result = [
{
name: "internalcorp.com",
className: "level-0 group",
children: [
{
name: "internalcorp.com.child1",
className: "level-1 leaf",
children: [
{
name: "internalcorp.com.grandchild1",
className: "level-2 leaf",
children: []
},
{
name: "internalcorp.com.grandchild2",
className: "level-2 leaf",
children: []
}
]
},
{
name: "internalcorp.com.child2",
className: "level-1 leaf",
children: []
}
]
},
{
name: "internalcorpwebsite.com",
className: "level-0 group",
children: [
{
name: "internalcorpwebsite.com.child1",
className: "level-1 leaf",
children: [],
}
],
}
];
Code that I have tried
const result = arr1.map((item,idx)=> {
if(item.children.length){
return {
...item,
className: `level${idx} leaf`
}
}
})
You can use the power of recursion:
function recursive(item, id) {
let returnedItem = item
if (item.children.length) {
returnedItem = {
...returnedItem,
children: returnedItem.children.map(childItem => recursive(childItem, id + 1))
}
}
if (0 === id) {
return { ...returnedItem, className: `level-0 group` }
}
return { ...returnedItem, className: `level-${id} leaf` }
}
const result = arr1.map(item => recursive(item, 0))
I have an array of objects. Every object has hasPermission property and children property.
children property is also an array of objects and every object has hasPermission property.
My array like this:
const navigationMenus = [
{
hasPermission: false,
name: 'Main',
type: 'section',
children: [
{
hasPermission: true,
name: 'Test',
type: 'item',
link: '/test'
}
]
},
{
hasPermission: true,
name: 'Master',
type: 'section',
children: [
{
hasPermission: true,
name: 'Operator Group',
type: 'item',
link: '/operator-group'
},
{
hasPermission: false,
name: 'Operation Group',
type: 'item',
link: '/operation-group'
}
]
}
];
Based on hasPermission property I want another array which holds only those objects which hasPermission property is true.
I tried with this approach.
const permittedNavigationMenus = []
for (let i = 0; i < navigationMenus.length; i++) {
const section = navigationMenus[i];
if (section.hasPermission) {
const permittedSection = {
name: section.name,
type: section.type,
children: []
}
for (let j = 0; j < section.children.length; j++) {
const item = section.children[j]
if (item.hasPermission) {
permittedSection.children.push(item)
}
}
permittedNavigationMenus.push(permittedSection)
}
}
console.log(JSON.stringify(permittedNavigationMenus, null, 2))
Is there any better solution?
a simple recursive function can do that:
const navigationMenus =
[ { hasPermission: false, name: 'Main', type: 'section', children:
[ { hasPermission: true, name: 'Test', type: 'item', link: '/test' } ]
}
, { hasPermission: true, name: 'Master', type: 'section', children:
[ { hasPermission: true, name: 'Operator Group', type: 'item', link: '/operator-group' }
, { hasPermission: false, name: 'Operation Group', type: 'item', link: '/operation-group' }
] } ]
const navigationMenusTrue = []
function runArray(arr,parent)
{
arr.forEach(({children,...info}) =>
{
if (info.hasPermission)
{
let newRow = {...info}
parent.push(newRow)
if (children)
{
let xChilds = []
newRow.children = xChilds
runArray(children,xChilds)
}
}
})
}
runArray(navigationMenus,navigationMenusTrue)
console.log( navigationMenusTrue )
.as-console-wrapper {max-height: 100%!important;top:0 }
Here's an recursive option using Array#reduce()
const
filterByPermission = (arr) => {
return arr.reduce((a, o) => {
if (o.hasPermission) {
const _o = o?.children?.length
? { ...o, children: filterByPermission(o.children) }
: { ...o };
a.push(_o);
}
return a;
}, [])
},
navigationMenus = [{ hasPermission: false, name: 'Main', type: 'section', children: [{ hasPermission: true, name: 'Test', type: 'item', link: '/test' }] }, { hasPermission: true, name: 'Master', type: 'section', children: [{ hasPermission: true, name: 'Operator Group', type: 'item', link: '/operator-group' }, { hasPermission: false, name: 'Operation Group', type: 'item', link: '/operation-group' }] }],
result = filterByPermission(navigationMenus);
console.log(result)
.as-console-wrapper { max-height: 100% !important; top: 0; }
Need to use recursion
const navigationMenus = [
{
hasPermission: false,
name: 'Main',
type: 'section',
children: [
{
hasPermission: true,
name: 'Test',
type: 'item',
link: '/test'
}
]
},
{
hasPermission: true,
name: 'Master',
type: 'section',
children: [
{
hasPermission: true,
name: 'Operator Group1',
type: 'item',
link: '/operator-group'
},
{
hasPermission: false,
name: 'Operation Group2',
type: 'item',
link: '/operation-group',
children: [
{
hasPermission: true,
name: 'Operator Group3',
type: 'item',
link: '/operator-group'
},
{
hasPermission: false,
name: 'Operation Group4',
type: 'item',
link: '/operation-group'
}
]
}
]
}
];
function filterRec(arr = []) {
return arr.map(el => {
const elem = el.hasPermission && el
const children = filterRec(el.children)
const result = ({ ...elem, ...(children.length && ({children}) || {}) })
return Object.keys(result).length && result
}).filter(Boolean)
}
// or without empty children key
// /function filterRec(arr = []) {
// return arr.reduce((acc, el) => {
// let result = el.hasPermission && el
// const children = filterRec(el.children)
// if (result && children.length) {
// result.children = children
// } else if (!result) {
// result = children
// }
// return acc.concat(result).filter(Boolean)
// }, [])
// }
console.log(filterRec(navigationMenus))
Probably answered Recursively filter array of objects
You can achieve that in a single pass using .reduce():
const permittedNavigationMenus = navigationMenus.reduce(
(acc, cur) => {
if (cur.hasPermission) {
acc.push({
...cur,
children: cur.children.filter(({ hasPermission }) => hasPermission)
})
}
return acc;
},
[]
);
Here is what I have
[
{
typeProgramId: {
name: 'type1',
},
title: 'test1',
},
{
typeProgramId: {
name: 'type1',
},
subTypeProgramId: [{
name: 'sub1',
}],
title: 'test2',
},
{
typeProgramId: {
name: 'type2',
},
title: 'test3',
},
{
typeProgramId: {
name: 'type2',
},
subTypeProgramId: {
name: 'sub2',
},
title: 'test4',
}
]
First I want to group typeProgramId if the title have the same typeProgramId I want to push title into array by each typeProgramId but If the data have typeProgramId and subTypeProgram Id I want to group subtypeProgramId in typeProgramId too. if not empty subtypeProgramId I want to push it in array title inside subtypeProgram Id. I try to use lodash groupBy and many way but it still did not work.
Here is what I want
{
typeProgramId: [{
name: 'type1',
title: [
'test1',
],
subTypeProgramId: {
name: sub1,
title: [
'test2'
]
}
}, {
name: 'type2',
title: [
'test3',
],
subTypeProgramId: [{
name: sub1,
title: [
'test4'
]
}
}]
}
what I do now
let result = _.groupBy(getProgram, function(data) {
return data.typeProgramId
})
result = _.map(result, function(group, data) {
// I think in here I must groupBy subTypeProgramId again
// the data return all string not object after group
return {
typeProgramId: data,
titile: group,
}
})
Please check the below code. I have used reduce function of Array. It produces the expected result.
function updateMem(mem, prgIndex, val){
if(prgIndex < 0) {
mem.typeProgramId.push({});
prgIndex = mem.typeProgramId.length - 1;
}
mem.typeProgramId[prgIndex].name = val.typeProgramId.name;
if(val.subTypeProgramId){
mem.typeProgramId[prgIndex].subTypeProgramId = Object.assign({}, mem.typeProgramId[prgIndex].subTypeProgramId || {}, {"name" : val.subTypeProgramId.name, "title": []});
mem.typeProgramId[prgIndex].subTypeProgramId.title.push(val.title);
} else {
mem.typeProgramId[prgIndex].title = (mem.typeProgramId[prgIndex].title ? mem.typeProgramId[prgIndex].title : []);
mem.typeProgramId[prgIndex].title.push(val.title);
}
};
arr.reduce((mem, val) => {
var prgIndex = mem.typeProgramId.findIndex((p) => p.name === val.typeProgramId.name);
updateMem(mem, prgIndex, val);
return mem;
}, {typeProgramId: []});
Given a flat level array of objects, what's the most efficient and modern way to nest them based on a parent and id property? The top level objects have no parentId, and there's no limit to nest levels.
[{
id: 'OS:MacOS',
type: 'OS',
value: 'MacOS'
}, {
parentId: 'OS:MacOS',
id: 'Version:Catalina',
type: 'Version',
value: 'Catalina'
}, {
parentId: 'Version:Catalina',
id: 'Browser:Chrome',
type: 'Browser',
value: 'Chrome'
}, {
id: 'OS:Windows',
type: 'OS',
value: 'Windows'
}, {
parentId: 'OS:Windows',
id: 'Version:7',
type: 'Version',
value: '7'
}, {
parentId: 'OS:MacOS',
id: 'Version:Mojave',
type: 'Version',
value: 'Mojave'
}, {
parentId: 'Version:Mojave',
id: 'Browser:Chrome',
type: 'Browser',
value: 'Chrome'
}, {
parentId: 'OS:Windows',
id: 'Version:XP',
type: 'Version',
value: 'XP'
}, {
parentId: 'Version:XP',
id: 'Browser:Chrome',
type: 'Browser',
value: 'Chrome'
}]
Where parentId matches up to a corresponding id field. Ideally transforming them to include a children array field along the lines of:
[{
id: 'OS:MacOS',
type: 'OS',
value: 'MacOS',
children: [
{
parentId: 'OS:MacOS',
id: 'Version:Catalina',
type: 'Version',
value: 'Catalina',
children: [
{
parentId: 'Version:Catalina',
id: 'Browser:Chrome',
type: 'Browser',
value: 'Chrome'
}
]
},
{
parentId: 'OS:MacOS',
id: 'Version:Mojave',
type: 'Version',
value: 'Mojave',
children: [
{
parentId: 'Version:Mojave',
id: 'Browser:Chrome',
type: 'Browser',
value: 'Chrome'
}
]
}
]
}, {
id: 'OS:Windows',
type: 'OS',
value: 'Windows',
children: [
{
parentId: 'OS:Windows',
id: 'Version:7',
type: 'Version',
value: '7'
},
{
parentId: 'OS:Windows',
id: 'Version:XP',
type: 'Version',
value: 'XP',
children: [
{
parentId: 'Version:XP',
id: 'Browser:Chrome',
type: 'Browser',
value: 'Chrome'
}
]
}
]
}]
Thoughts appreciated!
You could use reduce in recursive function that will pass down the current element id and compare it with parent id in nested calls.
const data = [{"id":"OS:MacOS","type":"OS","value":"MacOS"},{"parentId":"OS:MacOS","id":"Version:Catalina","type":"Version","value":"Catalina"},{"parentId":"Version:Catalina","id":"Browser:Chrome","type":"Browser","value":"Chrome"},{"id":"OS:Windows","type":"OS","value":"Windows"},{"parentId":"OS:Windows","id":"Version:7","type":"Version","value":"7"},{"parentId":"OS:MacOS","id":"Version:Mojave","type":"Version","value":"Mojave"},{"parentId":"Version:Mojave","id":"Browser:Chrome","type":"Browser","value":"Chrome"},{"parentId":"OS:Windows","id":"Version:XP","type":"Version","value":"XP"},{"parentId":"Version:XP","id":"Browser:Chrome","type":"Browser","value":"Chrome"}]
function nested(data, pid = undefined) {
return data.reduce((r, e) => {
if (e.parentId == pid) {
const obj = { ...e }
const children = nested(data, e.id);
if (children.length) obj.children = children;
r.push(obj)
}
return r;
}, [])
}
const result = nested(data);
console.log(result)
The reducer approach by Nenad works, but is pretty inefficient as it iterates through the data list n^2 times. Here is an O(n) solution:
function buildTree(data) {
const store = new Map(); // stores data indexed by it's id
const rels = new Map(); // stores array of children associated with id
const roots = []; // stores root nodes
data.forEach(d => {
store.set(d.id, d);
!rels.get(d.id) ? rels.set(d.id, []) : undefined; // noOp.;
if (!d.parentId) {
roots.push(d.id)
return;
}
const parent = rels.get(d.parentId) || [];
parent.push(d.id);
rels.set(d.parentId, parent);
});
function build(id) {
const data = store.get(id);
const children = rels.get(id);
if (children.length === 0) {
return {...data}
}
return {...data, children: children.map(c => build(c)) };
}
return roots.map(r => build(r));
}
const data = [{"id":"OS:MacOS","type":"OS","value":"MacOS"},{"parentId":"OS:MacOS","id":"Version:Catalina","type":"Version","value":"Catalina"},{"parentId":"Version:Catalina","id":"Browser:Chrome","type":"Browser","value":"Chrome"},{"id":"OS:Windows","type":"OS","value":"Windows"},{"parentId":"OS:Windows","id":"Version:7","type":"Version","value":"7"},{"parentId":"OS:MacOS","id":"Version:Mojave","type":"Version","value":"Mojave"},{"parentId":"Version:Mojave","id":"Browser:Chrome","type":"Browser","value":"Chrome"},{"parentId":"OS:Windows","id":"Version:XP","type":"Version","value":"XP"},{"parentId":"Version:XP","id":"Browser:Chrome","type":"Browser","value":"Chrome"}]
console.log(JSON.stringify(buildTree(data), null, 2))
Edit Note:
Earlier answer was class based. Removed that for simplicity. You can further optimize the space storage by changing store to be index based.