Javascript - Group by data from object arrays - javascript

I have the object arrays like below:
const obj = {
top: [
{ id: 1, name: "Spider-man", rating: 1 },
{ id: 2, name: "Iron man", rating: 3 },
{ id: 3, name: "Hulk", rating: 5 }
],
middle: [
{ id: 1, name: "Spider-man", rating: 4.5 },
{ id: 2, name: "Iron man", rating: 3.5 },
{ id: 3, name: "Hulk", rating: 1.5 }
],
bottom: [
{ id: 1, name: "Spider-man", rating: 5 },
{ id: 2, name: "Iron man", rating: 2 },
{ id: 3, name: "Hulk", rating: 4 }
]
};
And what I want is to group by id, name and list out rating belonging its like below
const users = [
{
id: 1,
name: "Spider-man",
rating: {top: 1, middle: 4.5, bottom: 5}
},
{
id: 2,
name: "Iron man",
rating: {top: 3, middle: 3.5, bottom: 2}
},
{
id: 3,
name: "Hulk",
rating: {top: 5, middle: 1.5, bottom: 4}
}
];
I've tried this approach but seems it can achieve in more ways such as .reduce, for...of with more elegant, right?
const obj = {
top: [
{ id: 1, name: "Spider-man", rating: 1 },
{ id: 2, name: "Iron man", rating: 3 },
{ id: 3, name: "Hulk", rating: 5 }
],
middle: [
{ id: 1, name: "Spider-man", rating: 4.5 },
{ id: 2, name: "Iron man", rating: 3.5 },
{ id: 3, name: "Hulk", rating: 1.5 }
],
bottom: [
{ id: 1, name: "Spider-man", rating: 5 },
{ id: 2, name: "Iron man", rating: 2 },
{ id: 3, name: "Hulk", rating: 4 }
]
};
var result = obj.top.map(x => ({
id: x.id,
name: x.name,
rating: {
top: obj.top.find(t => t.id === x.id).rating,
middle: obj.middle.find(t => t.id === x.id).rating,
bottom: obj.bottom.find(t => t.id === x.id).rating,
}
}));
console.log(result);
Any other ways to achieve it? Thanks in advance.

You need to map one of the subarrays to find each character's rating, so I think your current approach is pretty reasonable. You can make it a bit less repetitive by making an array of the properties (top, middle, bot) beforehand, then iterating over them instead of listing each different one:
const obj = {
top: [
{ id: 1, name: "Spider-man", rating: 1 },
{ id: 2, name: "Iron man", rating: 3 },
{ id: 3, name: "Hulk", rating: 5 }
],
middle: [
{ id: 1, name: "Spider-man", rating: 4.5 },
{ id: 2, name: "Iron man", rating: 3.5 },
{ id: 3, name: "Hulk", rating: 1.5 }
],
bottom: [
{ id: 1, name: "Spider-man", rating: 5 },
{ id: 2, name: "Iron man", rating: 2 },
{ id: 3, name: "Hulk", rating: 4 }
]
};
const props = ['top', 'middle', 'bottom'];
var result = obj.top.map(x => ({
id: x.id,
name: x.name,
rating: Object.fromEntries(
props.map(prop =>
[prop, obj[prop].find(t => t.id === x.id).rating]
)
)
}));
console.log(result);
Another approach that's less computationally complex:
const obj = {
top: [
{ id: 1, name: "Spider-man", rating: 1 },
{ id: 2, name: "Iron man", rating: 3 },
{ id: 3, name: "Hulk", rating: 5 }
],
middle: [
{ id: 1, name: "Spider-man", rating: 4.5 },
{ id: 2, name: "Iron man", rating: 3.5 },
{ id: 3, name: "Hulk", rating: 1.5 }
],
bottom: [
{ id: 1, name: "Spider-man", rating: 5 },
{ id: 2, name: "Iron man", rating: 2 },
{ id: 3, name: "Hulk", rating: 4 }
]
};
const byName = {};
for (const [prop, arr] of Object.entries(obj)) {
for (const item of arr) {
byName[item.name] ??= { ...item, rating: {} };
byName[item.name].rating[prop] = item.rating;
}
}
console.log(Object.values(byName));

You could do it in a one-liner way
const obj = {
top: [
{ id: 1, name: "Spider-man", rating: 1 },
{ id: 2, name: "Iron man", rating: 3 },
{ id: 3, name: "Hulk", rating: 5 },
],
middle: [
{ id: 1, name: "Spider-man", rating: 4.5 },
{ id: 2, name: "Iron man", rating: 3.5 },
{ id: 3, name: "Hulk", rating: 1.5 },
],
bottom: [
{ id: 1, name: "Spider-man", rating: 5 },
{ id: 2, name: "Iron man", rating: 2 },
{ id: 3, name: "Hulk", rating: 4 },
],
};
const res = Array.from(
Object.entries(obj)
.flatMap(([rater, ratee]) =>
ratee.map(({ id, name, rating }) => ({
id,
name,
[rater]: rating,
}))
)
.reduce(
(acc, { id, ...restInfo }) =>
acc.set(id, { ...(acc.get(id) || {}), ...restInfo }),
new Map()
)
).map(([id, { name, ...rating }]) => ({ id, name, rating }));
console.log(res);

Using Dictionary along with Logical nullish assignment (??=)
The main idea includes 2 steps:
Loop all [key, values] of the object.
Inner each the values of the object, we loop to determine which the user's rating along with key belongs by user.id.
const obj = {
top: [
{ id: 1, name: "Spider-man", rating: 1 },
{ id: 2, name: "Iron man", rating: 3 },
{ id: 3, name: "Hulk", rating: 5 }
],
middle: [
{ id: 1, name: "Spider-man", rating: 4.5 },
{ id: 2, name: "Iron man", rating: 3.5 },
{ id: 3, name: "Hulk", rating: 1.5 }
],
bottom: [
{ id: 1, name: "Spider-man", rating: 5 },
{ id: 2, name: "Iron man", rating: 2 },
{ id: 3, name: "Hulk", rating: 4 }
]
};
// Refactor code: using Dictionary along with `Logical nullish assignment (??=)` .
var result = Object.entries(obj).reduce((acc, [key, values]) => {
values.forEach(v => {
acc[v.id] ??= {...v, rating: {}};
acc[v.id].rating[key] = v.rating;
});
return acc;
}, {});
console.log(Object.values(result));
/* Old versions: using Array approach
var result = Object.entries(obj).reduce((acc, [key, values]) => {
values.forEach(v => {
var x = acc.find(r => r.id === v.id);
if(x !== undefined){
x.rating[key] = v.rating;
}else{
x = { id: v.id, name: v.name, rating: {[key]: v.rating} };
acc.push(x);
}
});
return acc;
}, []);
*/
More detailed explanation:
With the dictionary approach instead of array, you can achieve it with the highest performance both time and space complexity (as #CertainPerformance's comment).
From MDN Web docs said that:
The logical nullish assignment (x ??= y) operator only assigns if x is nullish (null or undefined).

const obj = {
top: [
{ id: 1, name: "Spider-man", rating: 1 },
{ id: 2, name: "Iron man", rating: 3 },
{ id: 3, name: "Hulk", rating: 5 }
],
middle: [
{ id: 1, name: "Spider-man", rating: 4.5 },
{ id: 2, name: "Iron man", rating: 3.5 },
{ id: 3, name: "Hulk", rating: 1.5 }
],
bottom: [
{ id: 1, name: "Spider-man", rating: 5 },
{ id: 2, name: "Iron man", rating: 2 },
{ id: 3, name: "Hulk", rating: 4 }
]
};
const tempArr1 = Object.keys(obj).reduce((arr, key) => {
obj[key].forEach((item) => {
arr = [...arr, { ...item, rating: { [key]: item.rating } }];
});
return arr;
}, []);
const result = tempArr1.reduce((arr, el) => {
let tempObj = { ...el };
const index = arr.findIndex((tempItem) => tempItem.id === tempObj.id);
if (~index) {
arr[index] = {
...tempObj,
rating: {
...arr[index].rating,
...tempObj.rating
}
};
} else {
arr = [...arr, tempObj];
}
return arr;
}, []);
console.log(result);

const obj = {
top: [
{ id: 1, name: "Spider-man", rating: 1 },
{ id: 2, name: "Iron man", rating: 3 },
{ id: 3, name: "Hulk", rating: 5 }
],
middle: [
{ id: 1, name: "Spider-man", rating: 4.5 },
{ id: 2, name: "Iron man", rating: 3.5 },
{ id: 3, name: "Hulk", rating: 1.5 }
],
bottom: [
{ id: 1, name: "Spider-man", rating: 5 },
{ id: 2, name: "Iron man", rating: 2 },
{ id: 3, name: "Hulk", rating: 4 }
]
};
var result = [];
for(let [key, values] of Object.entries(obj))
for(let item of values){
let x = result.find(r => r.id === item.id);
if(x !== undefined){
x.rating[key] = item.rating;
}else{
x = { id: item.id, name: item.name, rating: {[key]: item.rating} };
result.push(x);
}
}
console.log(result);

Related

How can I get the result from arr1 and arr2, When the ID matches I need to copy the content from arr1

How can I get the result from arr1 and arr2, When the ID matches I need to copy the content from arr1
const arr1 = [
{ id: 1, name: "omar" },
{ id: 2, name: "laith" },
{ id: 3, name: "aref" },
]
const arr2 = [
{ id: 1, rating: "good" },
{ id: 2, rating: "very good" },
{ id: 2, rating: "very good" },
{ id: 3, rating: "Excellence" },
{ id: 3, rating: "Excellence" },
]
//expected output
const result = [
{ id: 1, rating: "good", name: "omar" },
{ id: 1, rating: "good", name: "omar" },
{ id: 2, rating: "very good", name: "laith" },
{ id: 3, rating: "Excellence", name: "aref" },
{ id: 3, rating: "Excellence", name: "aref" },
]
use reduce with filter
const arr1 = [ { id: 1, name: "omar" }, { id: 2, name: "laith" }, { id: 3, name: "aref" }, ];
const arr2 = [ { id: 1, rating: "good" }, { id: 2, rating: "very good" }, { id: 2, rating: "very good" }, { id: 3, rating: "Excellence" }, { id: 3, rating: "Excellence" }, ];
const result = arr1.reduce((acc,item) => {
const list = arr2.filter(i => i.id === item.id)
return [...acc, ...list.map(i => ({id: i.id,rating:i.rating, name: item.name}))]
}, [])
console.log(result)
Basically with a loop. Actually 2. Using a temporary object (result) as dictionary (or map) we can make it efficient searching for a match to each id. This is of complexity O(n) basically.
const arr1 = [ { id: 1, name: "omar" }, { id: 2, name: "laith" }, { id: 3, name: "aref" }, ];
const arr2 = [ { id: 1, rating: "good" }, { id: 2, rating: "very good" }, { id: 2, rating: "very good" }, { id: 3, rating: "Excellence" }, { id: 3, rating: "Excellence" }, ];
var result = {}
arr1.forEach(function(item1) {
result[item1.id] = item1;
});
arr2.forEach(function(item2) {
result[item2.id] = (result[item2.id] || item2)
result[item2.id]['rating'] = item2.rating
})
result = Object.values(result)
console.log(result)

How to reduce an array and add a count as a new field?

I have an array of objects, however i need the array to add a count onto each object, and also remove any duplicates. Is there a simple way to achieve this?
CURRENT
[
{ id: 2, name: 'Adventure' },
{ id: 6, name: 'Crime' },
{ id: 2, name: 'Adventure' },
{ id: 3, name: 'Beautiful' },
{ id: 7, name: 'Drama' },
{ id: 2, name: 'Adventure' }
]
EXPECTED
[
{ id: 2, name: 'Adventure', count: 3 },
{ id: 6, name: 'Crime', count: 1 },
{ id: 3, name: 'Beautiful', count: 1 },
{ id: 7, name: 'Drama', count: 1 }
]
let current = [
{ id: 2, name: 'Adventure' },
{ id: 6, name: 'Crime' },
{ id: 2, name: 'Adventure' },
{ id: 3, name: 'Beautiful' },
{ id: 7, name: 'Drama' },
{ id: 2, name: 'Adventure' }
]
let expected = current.reduce((acc, cur) => {
let curFind = acc.find(item => item.id === cur.id)
if (curFind) {
curFind.count++
return acc
} else {
return [...acc, {
...cur,
count: 1
}]
}
}, [])
console.log('expected:', expected)

Build nested object array from recursive function

How would i utilize my getChildren() function to create a larger function which takes my two main arrays objs and objRefs and outputs a single array of objs demonstrating their parent/child relationship.
here are the two main data arrays
const objs = [
{ name: "Kevin", age: 5, id: 1 },
{ name: "Matt", age: 53, id: 5 },
{ name: "Marry", age: 30, id: 2 },
{ name: "Leslie", age: 21, id: 3 },
{ name: "Sarah", age: 46, id: 4 },
{ name: "Heather", age: 37, id: 6 },
{ name: "Cory", age: 19, id: 7 },
]
const objRefs = [
{ parent_id: 5, obj_id: 7 }, // cory child of matt
{ parent_id: null, obj_id: 6 }, // matt root
{ parent_id: null, obj_id: 4 }, // sarah root
{ parent_id: null, obj_id: 5 }, // heather root
{ parent_id: 5, obj_id: 3 }, // leslie child of matt
{ parent_id: 4, obj_id: 2 }, // mary child of sarah
{ parent_id: 3, obj_id: 1 }, // kevin child of leslie
]
My goal is to run a function called getFamilyTree() which would return me this...
const tree = [
{
id: 5,
name: "Matt",
age: 53,
children:[
{
id: 3,
name: "Leslie",
age: 21,
children:[
{
id: 1,
name: "Kevin",
age: 5,
children:[ ]
}
]
},
{
id: 7,
name: "Cory",
age: 19,
children:[ ]
}
]
},
{
id: 6,
name: "Heather",
age: 37,
children:[ ]
},
{
id: 4,
name: "Sarah",
age: 46,
children:[
{
id: 2,
name: "Marry",
age: 30,
children:[ ]
}
]
}
]
I have a function that returns me all the children for the given parent node id, but im not sure how structure a function to return me the entire tree like my example.
function getChildren(parent_id) {
let children = []
for (var i = 0; i < objRefs.length; i++) {
const ref = objRefs[i]
if (ref.parent_id === parent_id) {
const obj = objs.find(obj => {
return obj.id === ref.obj_id
})
children.push(obj)
}
}
return children
}
function getFamilyTree() {
let result = []
... // build recursive family tree
return result
}
You don't need a recursive function to construct that.
To get a reasonable time complexity, store all the objs to a Map or something (if the ids are sequential, even an array will work) keyed by id. Then, just iterate over objRefs and construct the relations appropriately:
const objs = [
{ name: "Kevin", age: 5, id: 1 },
{ name: "Matt", age: 53, id: 5 },
{ name: "Marry", age: 30, id: 2 },
{ name: "Leslie", age: 21, id: 3 },
{ name: "Sarah", age: 46, id: 4 },
{ name: "Heather", age: 37, id: 6 },
{ name: "Cory", age: 19, id: 7
},
]
const objRefs = [
{ parent_id: 5, obj_id: 7 }, // cory child of matt
{ parent_id: null, obj_id: 6 }, // matt root
{ parent_id: null, obj_id: 4 }, // sarah root
{ parent_id: null, obj_id: 5 }, // heather root
{ parent_id: 5, obj_id: 3 }, // leslie child of matt
{ parent_id: 4, obj_id: 2 }, // mary child of sarah
{ parent_id: 3, obj_id: 1 }, // kevin child of leslie
]
function getFamilyTree(objs, objRefs){
const tree = []
const map = new Map(
objs.map(e => [e.id, { ...e, children: [] }])
)
for(const {parent_id, obj_id} of objRefs){
if(parent_id === null){
tree.push(map.get(obj_id))
}else{
map.get(parent_id).children.push(map.get(obj_id))
}
}
return tree
}
const tree = getFamilyTree(objs, objRefs)
console.log(tree)
I don't think you even need the getChildren function to actually build your tree. Using Maps instead could be useful:
const objs = [
{ name: "Kevin", age: 5, id: 1 },
{ name: "Matt", age: 53, id: 5 },
{ name: "Marry", age: 30, id: 2 },
{ name: "Leslie", age: 21, id: 3 },
{ name: "Sarah", age: 46, id: 4 },
{ name: "Heather", age: 37, id: 6 },
{ name: "Cory", age: 19, id: 7 },
]
const objRefs = [
{ parent_id: 5, obj_id: 7 }, // cory child of matt
{ parent_id: null, obj_id: 6 }, // matt root
{ parent_id: null, obj_id: 4 }, // sarah root
{ parent_id: null, obj_id: 5 }, // heather root
{ parent_id: 5, obj_id: 3 }, // leslie child of matt
{ parent_id: 4, obj_id: 2 }, // mary child of sarah
{ parent_id: 3, obj_id: 1 }, // kevin child of leslie
]
function getFamillyTree(){
const nodes = new Map()
// Preparing the data nodes
objs.forEach(elt => nodes.set(elt.id, {...elt, children: [], root: false}))
// Linking the nodes to make the parent <-> children relations
objRefs.filter(rel => !!rel.parent_id).forEach(rel => {
const parent = nodes.get(rel.parent_id)
parent.children.push(nodes.get(rel.obj_id))
})
// Marking the roots
objRefs.filter(rel => rel.parent_id === null).forEach(rel => {
const obj = nodes.get(rel.obj_id)
obj.root = true
})
return Array.from(nodes.values()).filter(obj => obj.root)
}
document.write(JSON.stringify(getFamillyTree(), null, 4))
Edit: This answer can be slightly off, because as Nina stated in a comment on the question, OP seems to ask for an explicitly recursive solution, leaving this here for reference.
You could use some object as reference to the persons and their relations and map the nodes with their children.
const
getChildren = parent => (references[parent] || []).map(id => ({
...nodes[id],
children: getChildren(id)
})),
people = [{ name: "Kevin", age: 5, id: 1 }, { name: "Matt", age: 53, id: 5 }, { name: "Marry", age: 30, id: 2 }, { name: "Leslie", age: 21, id: 3 }, { name: "Sarah", age: 46, id: 4 }, { name: "Heather", age: 37, id: 6 }, { name: "Cory", age: 19, id: 7 }],
children = [{ parent_id: 5, obj_id: 7 }, { parent_id: null, obj_id: 6 }, { parent_id: null, obj_id: 4 }, { parent_id: null, obj_id: 5 }, { parent_id: 5, obj_id: 3 }, { parent_id: 4, obj_id: 2 }, { parent_id: 3, obj_id: 1 }],
nodes = Object.fromEntries(people.map(o => [o.id, o])),
references = children.reduce((r, { parent_id, obj_id }) => ((r[parent_id] ??= []).push(obj_id), r), {}),
tree = getChildren(null);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }
An approach with a single loop of children.
const
getTree = (people, children, root) => {
const
nodes = Object.fromEntries(people.map(o => [o.id, o])),
t = {};
children.forEach(({ parent_id: p, obj_id: id }) =>
((t[p] ??= {}).children ??= []).push(Object.assign(t[id] ??= {}, nodes[id]))
);
return t[root].children;
},
people = [{ name: "Kevin", age: 5, id: 1 }, { name: "Matt", age: 53, id: 5 }, { name: "Marry", age: 30, id: 2 }, { name: "Leslie", age: 21, id: 3 }, { name: "Sarah", age: 46, id: 4 }, { name: "Heather", age: 37, id: 6 }, { name: "Cory", age: 19, id: 7 }],
children = [{ parent_id: 5, obj_id: 7 }, { parent_id: null, obj_id: 6 }, { parent_id: null, obj_id: 4 }, { parent_id: null, obj_id: 5 }, { parent_id: 5, obj_id: 3 }, { parent_id: 4, obj_id: 2 }, { parent_id: 3, obj_id: 1 }],
tree = getTree(people, children, null);
console.log(tree);
.as-console-wrapper { max-height: 100% !important; top: 0; }

How to duplicate an object in an array by given quantity, ES6 and above

I'm trying to convert an array of objects where i return duplicated objects if the object properties quantity is greater than 1.
const objects = [
{ id: 1, name: "Scissor", price: 2, quantity: 3 },
{ id: 2, name: "Hat", price: 6.5, quantity: 1 },
{ id: 3, name: "Socks", price: 0.5, quantity: 5 },
];
// desired return
[
{ id: 1, name: "Scissor", price: 2 }
{ id: 1, name: "Scissor", price: 2 }
{ id: 1, name: "Scissor", price: 2 }
{ id: 2, name: "Hat", price: 6.5}
{ id: 3, name: "Socks", price: 0.5 }
{ id: 3, name: "Socks", price: 0.5 }
{ id: 3, name: "Socks", price: 0.5 }
{ id: 3, name: "Socks", price: 0.5 }
{ id: 3, name: "Socks", price: 0.5 }
]
My code:
const objects = [
{ id: 1, name: "Scissor", price: 2, quantity: 3 },
{ id: 2, name: "Hat", price: 6.5, quantity: 1 },
{ id: 3, name: "Socks", price: 0.5, quantity: 5 },
];
let newObjects= [];
Object.entries(objects).forEach(([key, value]) => {
for (let i=0; i < value.quantity; i++){
newObjects.push({ id: value.id, name: value.name, price: value.price})
}
});
console.log(newObjects);
So my code above does work, does return what i wanted, however i feel like there is a better/smoother and more of ES6 and beyond method. Could anyone please suggest a better way?
You could use .fill() and .flatMap().
const objects = [
{ id: 1, name: "Scissor", price: 2, quantity: 3 },
{ id: 2, name: "Hat", price: 6.5, quantity: 1 },
{ id: 3, name: "Socks", price: 0.5, quantity: 5 },
];
let newObjects = objects.flatMap(e=>
Array(e.quantity).fill({id: e.id, name: e.name, price: e.price})
);
console.log(newObjects);
You can use an array reduce along with an array fill.
The map is required only if you want to have unique references otherwise you can fill using the same object.
const objects = [
{ id: 1, name: "Scissor", price: 2, quantity: 3 },
{ id: 2, name: "Hat", price: 6.5, quantity: 1 },
{ id: 3, name: "Socks", price: 0.5, quantity: 5 },
];
const output = objects.reduce((a, c) => {
return a.concat(Array(c.quantity).fill({}).map(x=>({
id: c.id,
name: c.name,
price: c.price
})))
}, []);
console.log(output)

How to flatten the nested Array?

How do I flatten the nested Array in the Array?
Here is the example input Array,
const input = [
{
id: 1,
name: 'Charles',
otherFields: [{
id: 2,
name: 'Pung',
}, {
id: 3,
name: 'James',
}]
}, {
id: 4,
name: 'Charles',
otherFields: [{
id: 5,
name: 'Pung',
}, {
id: 6,
name: 'James',
}]
}
]
Output Array I want to get.
[{
id: 1,
name: 'Charles'
}, {
id: 2,
name: 'Pung',
}, {
id: 3,
name: 'James',
}, {
id: 4,
name: 'Charles'
}, {
id: 5,
name: 'Pung',
}, {
id: 6,
name: 'James',
}]
I want to somehow get the output in one statement like
input.map((sth) => ({...sth??, sth.field...})); // I'm not sure :(
With flatMap you can take out the otherFields property, and returning an array containing the parent item and the other array:
const input = [{
id: 1,
name: 'Charles',
otherFields: [{
id: 2,
name: 'Pung',
}, {
id: 3,
name: 'James',
}]
}];
console.log(
input.flatMap(({ otherFields, ...item }) => [item, ...otherFields])
);
For more than one level, you could take a recursive approach of flattening.
const
flat = ({ otherFields = [], ...o }) => [o, ...otherFields.flatMap(flat)],
input = [{ id: 1, name: 'Charles', otherFields: [{ id: 2, name: 'Pung' }, { id: 3, name: 'James', otherFields: [{ id: 4, name: 'Jane' }] }] }],
result = input.flatMap(flat);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Categories

Resources