Related
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.
I have an object I want to filter so that trainers with the most electric-type pokemon are listed first, but trainers without any electric-type pokemon are still present (represented as an empty array)
Here's my object:
obj = {
trainer1: [
{ name: 'pikachu', type: 'electric', id: 25 },
{ name: 'zapdos', type: 'electric', id: 145 },
{ name: 'psyduck', type: 'water', id: 54 },
],
trainer2: [
{ name: 'eevee', type: 'normal', id: 133 },
{ name: 'magmar', type: 'fire', id: 126 }
],
trainer3: [
{ name: 'ditto', type: 'normal', id: 132 },
{ name: 'magnemite', type: 'electric', id: 81 }
]
}
Becomes this object:
obj = {
trainer1: [
{ name: 'pikachu', type: 'electric', id: 25 },
{ name: 'zapdos', type: 'electric', id: 145 }
],
trainer3: [
{ name: 'magnemite', type: 'electric', id: 81 }
]
trainer2: [] // Array still present, but empty
}
I know reduce would come in handy here but I'm not sure how to set it up correctly.
This may be the bruteforce solution and there will be better solution than this but i think you can do it like the following way.
const tempArr = Object.keys(obj).map(key=>{
return {
key:key,
value:obj[key].filter(pokemon=>pokemon.type==='electric')
}
})
let newObj = {}
tempArr.sort((a,b)=>b.value.length-a.value.length)
tempArr.forEach(item=>{
newObj[item.key] = item.value
})
console.log(newObj)
Good afternoon, I had a difficulty. I faced a problem that I can’t solve. I have an array of objects:
[
{
date: "2020-04-20",
groups: ["apple"],
type: ["fruit"],
},
{
date: "2020-04-20",
groups: ["potate"],
type: ["vegetable"],
},
{
date: "2020-04-23",
groups: ["burger"],
type: ["fastfood"],
},
{
date: "2020-04-24",
groups: ["cola"],
type: ["water"],
}
]
I need to write a function that will determine to cross two objects into one by date.
That is, the expected result that the function will return should be like this:
[
{
date: "2020-04-20",
groups: ["apple", "potate"],
type: ["fruit", "vegetable"],
},
{
date: "2020-04-23",
groups: ["burger"],
type: ["fastfood"],
},
{
date: "2020-04-24",
groups: ["cola"],
type: ["water"],
}
]
Thank you in advance for your help!
First, consider what you are trying to accomplish. You want to merge data together. A good tool for this, as well as changing from one data structure to another is Array.prototype.reduce.
Conceptually, I'd suggest creating a map between the date and the entire object. This will help you determine if the entry already exists. Then, you can push the relevant keys to the relevant arrays as you iterate. Finally, you can use Object.values to get only the values of the map you created.
This is a pattern I use frequently for these sorts of situarions.
I would do something like the following:
const input = [
{
date: "2020-04-20",
groups: ["apple"],
type: ["fruit"],
},
{
date: "2020-04-20",
groups: ["potate"],
type: ["vegetable"],
},
{
date: "2020-04-23",
groups: ["burger"],
type: ["fastfood"],
},
{
date: "2020-04-24",
groups: ["cola"],
type: ["water"],
}
];
const combine = (arr) => {
const dateMap = arr.reduce((acc, item) => {
if (!acc[item.date]) {
acc[item.date] = item;
} else {
acc[item.date].groups = [...acc[item.date].groups, ...item.groups];
acc[item.date].type = [...acc[item.date].type, ...item.type];
}
return acc;
}, {});
return Object.values(dateMap);
};
console.log(combine(input));
You could take a standard grouping with an object for collecting the values.
var data = [{ date: "2020-04-20", groups: ["apple"], type: ["fruit"] }, { date: "2020-04-20", groups: ["potate"], type: ["vegetable"] }, { date: "2020-04-23", groups: ["burger"], type: ["fastfood"] }, { date: "2020-04-24", groups: ["cola"], type: ["water"] }],
result = Object.values(data.reduce((r, o) => {
r[o.date] = r[o.date] || { date: o.date, groups: [], type: [] };
r[o.date].groups.push(...o.groups);
r[o.date].type.push(...o.type);
return r;
}, {}));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can also solve this with forEach. Give this a try:
var sortedData=[];
var data = [{ date: "2020-04-20", groups: ["apple"], type: ["fruit"] }, { date: "2020-04-20", groups: ["potate"], type: ["vegetable"] }, { date: "2020-04-23", groups: ["burger"], type: ["fastfood"] }, { date: "2020-04-24", groups: ["cola"], type: ["water"] }];
data.forEach((elem, index, arr)=>{
isDatePresent= sortedData.some((value)=>value.date==elem.date);
if(!isDatePresent){
sortedData.push(elem);
} else {
var index=sortedData.findIndex((value)=> value.date==elem.date);
sortedData[index].groups= [...sortedData[index].groups, ...elem.groups];
sortedData[index].type= [...sortedData[index].type, ...elem.groups];
}
});
console.log(sortedData)
I'm new to ES6 and I have one problem, I have a nested array and I want to filter the data based on checked checkbox input.
const data = [
{ name: 'Airplane', type: 'folder', folderId: 1 },
{
name: 'Car',
type: 'folder',
folderId: 2,
children: [
{
Name: 'BMW',
type: 'folder',
folderId: 3,
children: [{ Name: 'X5', type: 'file', folderParentId: 3 }]
},
{ Name: 'Toyota', type: 'file', folderParentId: 2 },
{ Name: 'Mazda', type: 'file', folderParentId: 2 },
{ Name: 'Ferrari', type: 'file', folderParentId: 2 }
]
}
]
So I need to get:
CASE I: If X5 is checked and it is the only child, I need get the parent's information:
{
Name: 'BMW',
type: 'folder',
folderId: 3
}
CASE II: If Toyota and Ferrari are checked, I need to get the selected children:
{
Name: 'Toyota',
type: 'file',
folderParentId: 2
},
{
Name: 'Ferrari',
type: 'file',
folderParentId: 2
}
CASE III: If all children and all subfolders are checked, I need to get the selected grandparent:
{
name: 'Car',
type: 'folder',
folderId: 2,
}
After function for checkbox I got an array like:
const checked = [
{ name: 'Car', type: 'folder', folderId: 2}
{ name: 'BMW', type: 'folder', folderId: 2}
{ Name: 'X5', type: 'file', folderParentId: 3 }
{ Name: 'Toyota', type: 'file', folderParentId: 2 }
{ Name: 'Ferrari', type: 'file', folderParentId: 2 }
]
I try:
checked.filter(item=>item.folder === folderParentId && item)
You can use below function to get requested object.
function getObject(array, key, value) {
var o;
array.some(function iter(a) {
if (a[key] === value) {
o = a;
return true;
}
return Array.isArray(a.children) && a.children.some(iter);
});
return o;
}
You can test getObject(data,'name','Car');
I want to merge value to array if it have a same id.
I have one array of objects like this.
[
{
id: 'Tony',
type: 'hero',
favorite: 'Rosie',
},
{
id: 'Jane',
type: 'human',
favorite: null,
},
{
id: 'Tony',
type: 'hero',
favorite: 'Lisa',
},
{
id: 'Steve',
type: 'hero',
favorite: 'Jennie',
},
{
id: 'Tony',
type: 'hero',
favorite: 'Jisoo',
},
]
and I want to merge key favorite from string to array.
I want output like this
[
{
id: 'Tony',
type: 'hero',
favorite: ['Rosie', 'Lisa', 'Jisoo'],
},
{
id: 'Jane',
type: 'human',
favorite: null,
},
{
id: 'Steve',
type: 'hero',
favorite: ['Jennie'],
}
and i try to write code like this: (from: Sum similar keys in an array of objects )
var obj = [
{
id: 'Tony',
type: 'hero',
favorite: 'Rosie',
},
{
id: 'Jane',
type: 'human',
favorite: null,
},
{
id: 'Tony',
type: 'hero',
favorite: 'Lisa',
},
{
id: 'Steve',
type: 'hero',
favorite: 'Jennie',
},
{
id: 'Tony',
type: 'hero',
favorite: 'Jisoo',
},
];
var holder = {};
const ar = []
obj.forEach(function (d) {
if (holder.hasOwnProperty(d.id)) {
holder[d.id] = ar.push(holder[d.id] + d.favorite);
} else {
holder[d.id] = d.favorite;
}
});
var obj2 = [];
for (var prop in holder) {
obj2.push({ name: prop, favorite: holder[prop] });
}
console.log(obj2);
but out put is
[ { name: 'Tony', favorite: 2 },
{ name: 'Jane', favorite: null },
{ name: 'Steve', favorite: 'Jennie' } ]
How can i do this ?
You can do this with a single Array.reduce and would most likely be the most simple and performant approach:
var data = [ { id: 'Tony', type: 'hero', favorite: 'Rosie', }, { id: 'Jane', type: 'human', favorite: null, }, { id: 'Tony', type: 'hero', favorite: 'Lisa', }, { id: 'Steve', type: 'hero', favorite: 'Jennie', }, { id: 'Tony', type: 'hero', favorite: 'Jisoo', }, ]
let result = data.reduce((r, {id,type,favorite}) => {
r[id] = r[id] || {id, type, favorite: []}
r[id].favorite.push(favorite)
return r
}, {})
console.log(Object.values(result))
The idea is to "group by" the id and then keep pushing to the favorites array on each iteration.
For ES5 you can do it in similar fashion:
var data = [ { id: 'Tony', type: 'hero', favorite: 'Rosie', }, { id: 'Jane', type: 'human', favorite: null, }, { id: 'Tony', type: 'hero', favorite: 'Lisa', }, { id: 'Steve', type: 'hero', favorite: 'Jennie', }, { id: 'Tony', type: 'hero', favorite: 'Jisoo', }, ]
let result = data.reduce(function(r, c){
r[c.id] = r[c.id] || {id: c.id, type: c.type, favorite: []}
r[c.id].favorite.push(c.favorite)
return r
}, {})
console.log(Object.values(result))
There really is no need for lodash to achieve this.
Group by the id with _.group(), and then merge each group using _.map() and _.mergeWith(), and collect favorite to an array:
const data = [{"id":"Tony","type":"hero","favorite":"Rosie"},{"id":"Jane","type":"human","favorite":null},{"id":"Tony","type":"hero","favorite":"Lisa"},{"id":"Steve","type":"hero","favorite":"Jennie"},{"id":"Tony","type":"hero","favorite":"Jisoo"}]
const result = _(data)
.groupBy('id')
.map(g => _.mergeWith({}, ...g, (o, s, k) => {
if(k !== 'favorite') return // non favorite key are not collected to an array
if(_.isNil(s)) return o // don't add null or undefined to array
return [].concat(o || [], s) // concat other values to array
}))
.value()
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.js"></script>
Use map and Set to get this result.
First we create a new unique Set using the id field.
We seed a new new array from this and call the map method.
const arr = [
{
id: 'Tony',
type: 'hero',
favorite: 'Rosie',
},
{
id: 'Jane',
type: 'human',
favorite: null,
},
{
id: 'Tony',
type: 'hero',
favorite: 'Lisa',
},
{
id: 'Steve',
type: 'hero',
favorite: 'Jennie',
},
{
id: 'Tony',
type: 'hero',
favorite: 'Jisoo',
},
];
const masterArr = [...new Set(arr.map((e) => e.id))].map(a => ({
id: a,
type: arr.find(x => x.id === a).type,
favorites: arr.filter(x => x.id === a).map(y => y.favorite)
}));
console.log(masterArr);
You can use reduce on the array.
const arr = [
{
id: 'Tony',
type: 'hero',
favorite: 'Rosie',
},
{
id: 'Jane',
type: 'human',
favorite: null,
},
{
id: 'Tony',
type: 'hero',
favorite: 'Lisa',
},
{
id: 'Steve',
type: 'hero',
favorite: 'Jennie',
},
{
id: 'Tony',
type: 'hero',
favorite: 'Jisoo',
},
]
const out = arr.reduce((result, el)=> {
const id = result.findIndex(e => e.id ===el.id)
if(id> -1){
result[id] = {...result[id], favorite: [...result[id].favorite, el.favorite]}
} else {result.push({...el, favorite: [el.favorite].filter(x => x) })}
return result
}, [])
console.log(out)
Just loop over the array and construct a new array after converting favorite attribute into an array, then for next elements with the same id, push to favorite array
let users = [{id: 'Tony', type: 'hero', favorite: 'Rosie',}, {id: 'Jane', type: 'human', favorite: null,}, {id: 'Tony', type: 'hero', favorite: 'Lisa',}, {id: 'Steve', type: 'hero', favorite: 'Jennie',}, {id: 'Tony', type: 'hero', favorite: 'Jisoo',},];
// To combine friends of the same user in an array at property friendNames
let merged = {};
for (let user of users) {
if (typeof merged[user.id] === 'undefined') {
user.favorite = [user.favorite];
merged[user.id] = user;
} else {
merged[user.id].favorite.push(user.favorite)
}
}
console.log(Object.values(merged));