How to flatten an array of objects? [duplicate] - javascript

This question already has answers here:
Group array items using object
(19 answers)
Flatten objects in array
(6 answers)
Closed 1 year ago.
I have some data in a non desirable format and I would like to flatten it.
Data:
[
{
team: "Team 1",
name: "John"
},
{
team: "Team 1",
name: "Stacy"
},
{
team: "Team 1",
name: "Jason"
},
{
team: "Team 2",
name: "Tim"
},
{
team: "Team 2",
name: "Andrew"
},
{
team: "Team 2",
name: "Steve"
}
,
{
team: "Team 3",
name: "Eric"
},
{
team: "Team 3",
name: "Frank"
},
{
team: "Team 3",
name: "Cory"
}
]
The desired result is:
[
{
team: "Team 1",
name: ["John", "Stacy", "Jason"],
count: 3
},
{
team: "Team 2",
name: ["Tim", "Andrew", "Steve"],
count: 3
},
{
team: "Team 3",
name: ["Eric", "Frank", "Cory"],
count: 3
}
]
I've tried looping through it and using Object.assing but that seemed the be the incorrect approach. Any suggestions on a good approach to flatted this data? Thanks

You can use Array.reduce.
In the reducer function, check whether the accumulator contains an item with the same team property. If so, increment its's count property and push the current item's name property to it's name property.
const arr=[{team:"Team 1",name:"John"},{team:"Team 1",name:"Stacy"},{team:"Team 1",name:"Jason"},{team:"Team 2",name:"Tim"},{team:"Team 2",name:"Andrew"},{team:"Team 2",name:"Steve"},{team:"Team 3",name:"Eric"},{team:"Team 3",name:"Frank"},{team:"Team 3",name:"Cory"}];
const res = arr.reduce((a,b) => {
let itm = a.find(e => e.team == b.team);
if(itm){
itm.count++;
itm.name.push(b.name);
}else{
a.push({team: b.team, name: [b.name], count:1})
}
return a;
}, [])
console.log(res)
A more efficient solution:
const arr=[{team:"Team 1",name:"John"},{team:"Team 1",name:"Stacy"},{team:"Team 1",name:"Jason"},{team:"Team 2",name:"Tim"},{team:"Team 2",name:"Andrew"},{team:"Team 2",name:"Steve"},{team:"Team 3",name:"Eric"},{team:"Team 3",name:"Frank"},{team:"Team 3",name:"Cory"}];
const obj = arr.reduce((a,b) => {
let itm = a[b.team]
if(itm){
itm.count++;
itm.name.push(b.name);
}else{
a[b.team] = {team: b.team, name: [b.name], count: 0}
}
return a;
}, {})
const res = Object.keys(obj).map(e => obj[e])
console.log(res)

Related

How to loop through an array of objects and find objects with same value [duplicate]

This question already has answers here:
Sum similar keys in an array of objects
(15 answers)
Closed 4 months ago.
I have this array of objects and I want to loop through in a way that all the objects that have same value for name property, like first, third and last object has same value (person 1) for name property and total its amount.
like the result should be like:- person 1 - 434 (total amount)
const arr = [
{ name: "person 1", amount: 154 },
{ name: "person 2", amount: 240 },
{ name: "person 1", amount: 100 },
{ name: "person 2", amount: 160 },
{ name: "person 2", amount: 140 },
{ name: "person 1", amount: 180 },
];
By far the easiest approach is using a simple for/of loop.
Create an output object, and then iterate over the array. On each iteration of the array of objects: if output doesn't already have a property key that matches the name of the current object create one by assigning it zero. Then increment that property's value with the current amount.
Note: I've used logical nullish assignment (??=) here but output[name] = output[name] || 0 would also work if your system doesn't currently support it.
const arr = [
{ name: "person 1", amount: 154 },
{ name: "person 2", amount: 240 },
{ name: "person 1", amount: 100 },
{ name: "person 2", amount: 160 },
{ name: "person 2", amount: 140 },
{ name: "person 1", amount: 180 },
];
// Create an empty object
const output = {};
// Iterate over the objects in the array
// I've destructured the name and amount from
// the object (documentation below)
for (const { name, amount } of arr) {
// If the output object doesn't have a property
// key that matches the current object's name
// create it by assigning it zero
output[name] ??= 0;
// Then increase its amount
output[name] += amount;
}
console.log(output);
Additional documentation
Destructuring assignment
I will not write your code for you; however, I am more than happy to point you in the right direction. Use a for loop to iterate through all the objects. Keep a global counter variable for each person/group.
In the for loop: Using the name property, figure out which counter to increment. Increment the counter variable by the number in the amount property.
After the for loop terminates, the counter variables will have your results. Make sure your counter variables are declared outside the for loop; otherwise your variables will not be available in the global scope.
This is the solution you might think of initially,
const arr = [
{ name: "person 1", amount: 154 },
{ name: "person 2", amount: 240 },
{ name: "person 1", amount: 100 },
{ name: "person 2", amount: 160 },
{ name: "person 2", amount: 140 },
{ name: "person 1", amount: 180 },
];
let first = [];
arr.forEach((e)=>{
first.push(e.name);
})
first.sort(); let person = [];
person.push(first[0]);
for(let i =1 ;i<first.length ; i++){
if(first[i]!==first[i-1]){
person.push(first[i]);
}
}
let sum =0; let sumarr = [];
person.forEach(element => {
arr.forEach(element2 => {
if(element===element2.name){
sum+=element2.amount;
}
});
sumarr.push(sum); sum=0;
});
for(let i =0 ; i<sumarr.length ; i++){
console.log(person[i] , sumarr[I]);
}
You could reduce the array into an object that is keyed by name.
const arr = [
{ name: "person 1", amount: 154 },
{ name: "person 2", amount: 240 },
{ name: "person 1", amount: 100 },
{ name: "person 2", amount: 160 },
{ name: "person 2", amount: 140 },
{ name: "person 1", amount: 180 },
];
const groupedByName = arr.reduce((acc, { name, amount }) =>
({ ...acc, [name]: (acc[name] ?? 0) + amount }), {});
console.log(groupedByName);
If you want to be more performant, as suggested in your linked article, you can use a for...of loop.
const arr = [
{ name: "person 1", amount: 154 },
{ name: "person 2", amount: 240 },
{ name: "person 1", amount: 100 },
{ name: "person 2", amount: 160 },
{ name: "person 2", amount: 140 },
{ name: "person 1", amount: 180 },
];
const result = {};
for (const { name, amount } of arr) {
result[name] = (result[name] ?? 0) + amount;
}
console.log(result);
If you knew about these patterns already, why did you ask the question in the first place? If you had an algorithm already and wanted to improve it, you can visit Code Review.

how can I filter a parent object based on the inner (child) object?

As you see in the data below, we have a list of restaurants,
each restaurant has a list of different menus,
and each menu has a list of products
my question is: how can I filter a restaurant based on the product object ?
so as you see the second product object (Typical salad) is missing the price so I want to remove the whole restaurant (Resturant 1) object from data, how can I do it?
const data = [
{
name: "Resturant 1",
phone: "0000555823",
multiple_menus: [
{
name: "salads",
products: [
{
name: "French salad",
price: 15.5
},
{
name: "Typical salad",
}
]
},
{
name: "burgers",
products: [
{
name: "cheese burger",
price: 15.5
},
{
name: "American burger",
price: 10
}
]
},
]
},
{
name: "Resturant 2",
phone: "0000555823",
multiple_menus: [
{
name: "salads",
products: [
{
name: "French salad",
price: 15.5
},
{
name: "Typical salad",
price: 5.5
}
]
}
]
},
]
As a more readable answer, this is a solution
const filteredData = data.filter((restaurant)=>{
const everyMenuProductHasPrice = restaurant.multiple_menus.every((menu)=>{
const everyProductHasPrice = menu.products.every((product)=>!!product.price)
return everyProductHasPrice
})
return everyMenuProductHasPrice
})

In array of objects count grouped values by certain key

I have a following data:
const data2 = [
{
App: "testa.com",
Name: "TEST A",
Category: "HR",
Employees: 7
},
{
App: "testd.com",
Name: "TEST D",
Category: "DevOps",
Employees: 7
},
{
App: "teste.com",
Name: "TEST E",
Category: "DevOps",
Employees: 7
},
{
App: "testf.com",
Name: "TEST F",
Category: "Business",
Employees: 7
}
]
I want to get the count of distinct categories: Right now I am getting the list of all distinct categories but I'm unable to compute their count.
Following snippet give me the Distinct Category:
let uniqueCategory = [];
for(let i = 0; i < result.data.length; i++){
if(uniqueCategory.indexOf(result.data[i].Category) === -1){
uniqueCategory.push(result.data[i].Category);
}
}
What changes should I make to get the Counts of those Categories in the uniqueCategory - something like following:
uniqueCategory = [
{Category: "DevOps", count: 5},
{Category: "Business", count: 4},
....
{}
]
Your approach implies looping your source array (with .indexOf()) every iteration of for(..-loop. That will slow down unnecessarily look up process.
Instead, you may employ Array.prototype.reduce() to traverse your source array and build up the Map, having Category as a key and object of desired format as a value, then extract Map.prototype.values() into resulting array.
That will perform much faster and scale better.
const src = [{App:"testa.com",Name:"TEST A",Category:"HR",Employees:7},{App:"testd.com",Name:"TEST D",Category:"DevOps",Employees:7},{App:"teste.com",Name:"TEST E",Category:"DevOps",Employees:7},{App:"testf.com",Name:"TEST F",Category:"Business",Employees:7}],
result = [...src
.reduce((r, {Category}) => {
const cat = r.get(Category)
cat ? cat.count ++ : r.set(Category, {Category, count: 1})
return r
}, new Map)
.values()
]
console.log(result)
.as-console-wrapper{min-height:100%;}
The easiest way to do it is to use Array.prototype.reduce
const arr = [ ... ];
const output = arr.reduce((result, obj) => {
if (!result[obj.category]) {
result[obj.category] = 0;
}
result[obj.category]++;
return result;
}, {});
console.log(output); // this should log the similar output you want
Here's another alternative using .map and Set:
const src = [
{
App: "testa.com",
Name: "TEST A",
Category: "HR",
Employees: 7
},
{
App: "testd.com",
Name: "TEST D",
Category: "DevOps",
Employees: 7
},
{
App: "teste.com",
Name: "TEST E",
Category: "DevOps",
Employees: 7
},
{
App: "testf.com",
Name: "TEST F",
Category: "Business",
Employees: 7
}
];
const categories = src.map(obj => obj.Category);
const distinctCategories = [...new Set(categories)];
console.log(distinctCategories.length);

Sort Array to group matching elements but maintain original order

I need to sort an array of objects so items with similar categories are grouped but not losing original index of categories
I want to sort/group by the category key but keep the order the categories appeared in in the array. There isn't a specific order for the categories.
arr = [
{
name: "Name 1",
category: "D Category"
},
{
name: "Name 2",
category: "A Category"
},
{
name: "Name 3",
category: "D Category"
},
{
name: "Name 4",
category: "G Category"
},
{
name: "Name 5",
category: "A Category"
}
];
attempt doesnt quite work,
it ends up putting items with same categories together but alphabetizes them, which i dont want
arr.sort((first, second) => {
const firstId = first.category;
const secondId = second.category;
if (firstId < secondId) {
return -1;
}
if (firstId > secondId) {
return 1;
}
return 0;
});
expected result:
expectedResult = [
{
name: "Name 1",
category: "D Category"
},
{
name: "Name 3",
category: "D Category"
},
{
name: "Name 2",
category: "A Category"
},
{
name: "Name 5",
category: "A Category"
},
{
name: "Name 4",
category: "G Category"
}
];
Doing a .sort with comparison partially works where similar categories are together but they end up getting alphabetized which I don't want
Here's an approach that might work for you. Basically, it does what you're attempting, but introduces an intermediary step of creating a priority map. This map will contain the unique category values (from the original data) as keys, with the order they appear as the value. You can then simply base the sort logic off of that. My code maybe be more clear than my explaination:
//Original data
const arr = [{
name: "Name 1",
category: "D Category",
}, {
name: "Name 2",
category: "A Category"
}, {
name: "Name 3",
category: "D Category"
}, {
name: "Name 4",
category: "G Category"
}, {
name: "Name 5",
category: "A Category"
}];
// A priority map so we have a basis for sorting
const orderPriority = arr
.map(o => o.category)
.reduce((map, category, idx) => {
if (map[category] == null) {
map[category] = idx;
}
return map;
}, {});
// Now implement the sort based on the priorty map we created:
arr.sort((a, b) => orderPriority[a.category] - orderPriority[b.category]);
console.log(arr);
You could first use reduce method to group them by category and also save each category index and then sort by index and use flatMap to get values.
const arr = [{"name":"Name 1","category":"D Category"},{"name":"Name 2","category":"A Category"},{"name":"Name 3","category":"D Category"},{"name":"Name 4","category":"G Category"},{"name":"Name 5","category":"A Category"}]
const grouped = arr.reduce((r, e, index) => {
if(!r[e.category]) r[e.category] = {index, values: [e]}
else r[e.category].values.push(e)
return r;
}, {})
const result = Object.values(grouped)
.sort((a, b) => a.index - b.index)
.flatMap(e => e.values)
console.log(result)
You need to iterate the array in advance and collect the groups and get them an order value.
var array = [{ name: "Name 1", category: "D Category" }, { name: "Name 2", category: "A Category" }, { name: "Name 3", category: "D Category" }, { name: "Name 4", category: "G Category" }, { name: "Name 5", category: "A Category" }],
order = 0,
hash = array.reduce((o, { category }) => (o[category] = o[category] || ++order, o), {});
array.sort(({ category: a }, { category: b }) => hash[a] - hash[b]);
console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Another approach by using a Map and flat the values of it.
var array = [{ name: "Name 1", category: "D Category" }, { name: "Name 2", category: "A Category" }, { name: "Name 3", category: "D Category" }, { name: "Name 4", category: "G Category" }, { name: "Name 5", category: "A Category" }];
array = Array
.from(array
.reduce((m, o) => m.set(o.category, [...(m.get(o.category) || []), o]), new Map)
.values()
)
.flat();
console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }
This solution populates a Map, and then flattens the result into an Array. The reason I use a Map and not an object, is that objects in JavaScript should not really be thought of as ordered.
const arr = [{ name: "Name 1", category: "D Category", other: {} },
{ name: "Name 2", category: "A Category", other: {} },
{ name: "Name 3", category: "D Category", other: {} },
{ name: "Name 4", category: "G Category", other: {} },
{ name: "Name 5", category: "A Category", other: {} }]
const collate = ({arr, field}) =>
[...arr.reduce((acc,o) =>
(!acc.has(o[field])
? acc.set(o[field], [o])
: acc.set(o[field], [...acc.get(o[field]), o]),
acc), new Map).values()].flat()
console.log(collate({arr, field: 'category'}))

Find duplicate value object JS [duplicate]

This question already has answers here:
merge partially duplicated arrays in javascript (lodash)
(4 answers)
Group array items using object
(19 answers)
Closed 3 years ago.
I have a problem with javascript code
I have an array of the type
var array = [{
name: "Name 1",
id: 1
},
{
name: "Name 2",
id: 2
},
{
name: "Name 3",
id: 3
},
{
name: "Name 1",
id: 4
},
{
name: "Name 1",
id: 5
},
{
name: "Name 2",
id: 6
},
];
I'd like to get this:
var newArray = [{
name: "Name 1",
id: [1, 4, 5]
},
{
name: "Name 2",
id: [2, 6]
},
{
name: "Name 3",
id: 3
},
];
But I can't
I tried with a .find() but it doesn't work
Can you give me some leads please
You can use .reduce() for that. This example produces exactly the same output you expect:
var array = [
{
name: "Name 1",
id: 1
},
{
name: "Name 2",
id: 2
},
{
name: "Name 3",
id: 3
},
{
name: "Name 1",
id: 4
},
{
name: "Name 1",
id: 5
},
{
name: "Name 2",
id: 6
}
];
var output = array.reduce(function(res, curr) {
var existing = res.find(function(el) { return el.name === curr.name; });
if (existing) {
if (Array.isArray(existing.id))
existing.id.push(curr.id);
else
existing.id = [existing.id, curr.id]
} else {
res.push(curr);
}
return res;
}, []);
console.log(output);
You can use reduce() to create an object and then use Object.values to get an array. You can use map() after this to remove the array for those elements which have only a single id.
var array = [{
name: "Name 1",
id: 1
},
{
name: "Name 2",
id: 2
},
{
name: "Name 3",
id: 3
},
{
name: "Name 1",
id: 4
},
{
name: "Name 1",
id: 5
},
{
name: "Name 2",
id: 6
},
];
const res = Object.values(array.reduce((ac, a) => {
if(!ac[a.name]){
ac[a.name] = {...a, id: []}
}
ac[a.name].id.push(a.id);
return ac;
},{})).map(x => ({...x, id: x.id.length === 1 ? x.id[0] : x.id}))
console.log(res)
You can use reduce with a Map, for O(n) performance, as there are no nested loops:
const array = [
{ name: "Name 1", id: 1 },
{ name: "Name 2", id: 2 },
{ name: "Name 3", id: 3 },
{ name: "Name 1", id: 4 },
{ name: "Name 1", id: 5 },
{ name: "Name 2", id: 6 }
]
const out = [...array.reduce((a, o) => {
const e = a.get(o.name)
return (a.set(o.name, e ? Array.isArray(e) ? e.concat(o.id): [e, o.id] : o.id), a)
}, new Map())].map(([k, v]) => ({ name: k, id: v }))
console.log(out)
Try with Array.reduce() like this :
var array = [{
name: "Name 1",
id: 1
},
{
name: "Name 2",
id: 2
},
{
name: "Name 3",
id: 3
},
{
name: "Name 1",
id: 4
},
{
name: "Name 1",
id: 5
},
{
name: "Name 2",
id: 6
},
];
var output = array.reduce((acc, current) => {
if (exist = acc.find(p => p.name == current.name)) {
if(!Array.isArray(exist.id)){
exist.id = [exist.id];
}
exist.id.push(current.id);
} else {
acc.push(current)
}
return acc;
}, []);
console.log(output);

Categories

Resources