Using Reduce to rearrange an Object of Arrays - javascript

I am trying to get the below output from the array using reduce, however, I can't wrap my head about some parts on how reduce behave , appreciate some explanation so I can fully grasp it.
final goal is after reducing the result into a single array, removing duplicate objects based on the vch_number
Reduce function
const result = car.reduce((acc,vch)=>{
const temp = {...acc,[vch.name]:vch.Vehciles}
for (const [key, value] of Object.entries(temp)){
const fillterd = value.map(item => {
item.status = key
return item
})
}
return temp
}
,{})
console.log(result)
// final desired output vs current output
current = { available:
[ { make: 'bwm',
model: 'i8',
year: 2000,
vch_number: 51511,
status: 'available' },
{ make: 'bwm',
model: 'i8',
year: 2020,
vch_number: 51541,
status: 'available' } ],
parked:
[ { make: 'bwm',
model: 'i8',
year: 2000,
vch_number: 51510,
status: 'parked' } ],
service:
[ { make: 'bwm',
model: 'i8',
year: 2000,
vch_number: 51510,
status: 'service' } ] }
desired = [
{ make: 'bwm',
model: 'i8',
year: 2000,
vch_number: 51511,
status: 'available' },
{ make: 'bwm',
model: 'i8',
year: 2020,
vch_number: 51541,
status: 'available' },
{ make: 'bwm',
model: 'i8',
year: 2000,
vch_number: 51510,
status: 'parked' } ,
{ make: 'bwm',
model: 'i8',
year: 2000,
vch_number: 51510,
status: 'service' }
]
// Original API array
const car = [
{
"name": "available",
"Vehciles": [
{
"make": "bwm",
"model": "i8",
"year": 2000,
"vch_number": 51511,
},
{
"make": "bwm",
"model": "i8",
"year": 2020,
"vch_number": 51541,
}
]
},
{
"name": "parked",
"Vehciles": [
{
"make": "bwm",
"model": "i8",
"year": 2000,
"vch_number": 51510,
}
]
},
{
"name": "service",
"Vehciles": [
{
"make": "bwm",
"model": "i8",
"year": 2000,
"vch_number": 51510,
}
]
}
]

First problem is your using a {} as the second argument (the so called accumulator) in the .reduce() function. You'll want to pass an empty array [].
Second off all you have the Vehciles array inside those objects so you have to perform one more transformation inside.
more about reducers:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
I think the key is to wrap your head around the initialValue and the accumulator and how it relates to the currentValue when it comes to Array.reduce()
I've whipped up this code:
const car = [
{
"name": "available",
"Vehciles": [
{
"make": "bwm",
"model": "i8",
"year": 2000,
"vch_number": 51511,
},
{
"make": "bwm",
"model": "i8",
"year": 2020,
"vch_number": 51541,
}
]
},
{
"name": "parked",
"Vehciles": [
{
"make": "bwm",
"model": "i8",
"year": 2000,
"vch_number": 51510,
}
]
},
{
"name": "service",
"Vehciles": [
{
"make": "bwm",
"model": "i8",
"year": 2000,
"vch_number": 51510,
}
]
}
];
const result = car.reduce((acc,vch)=>{
const cars = vch.Vehciles.map(vehicle => {
const temp = {
status: vch.name,
...vehicle
};
return temp;
}).reduce((carAcc, car) => {
carAcc.push(car);
return carAcc;
}, acc);
return acc;
}, []);
console.log(result)

You could map the objects and check with a Set.
const
data = [{ name: "available", Vehciles: [{ make: "bwm", model: "i8", year: 2000, vch_number: 51511 }, { make: "bwm", model: "i8", year: 2020, vch_number: 51541 }] }, { name: "parked", Vehciles: [{ make: "bwm", model: "i8", year: 2000, vch_number: 51510 }] }, { name: "service", Vehciles: [{ make: "bwm", model: "i8", year: 2000, vch_number: 51510 }] }],
result = data.flatMap(
(seen => ({ name: status, Vehciles }) => Vehciles.flatMap(o => seen.has(o.vch_number)
? []
: (seen.add(o.vch_number), { ...o, status })
))
(new Set)
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related

Having some issue when trying to group objects from nested array of objects

I am trying to group some nested array of objects. On my below data, there is a 'products' key property. I am trying to group the products if the vendor.email is the same. After grouping the products I also want to put the paymentDetails key property on each group.
This is my code. I am able to group the products but am not able to set the paymentDetails key and value on each group.
const data= [
{
_id: "622d70a49bd88b1599026318",
products: [
{
_id: "6223186e2278d4e502f5264a",
title: "Product number 1",
price: 600,
cartQuantity: 1,
vendor: {email: "vendor1#gmail.com"}
},
{
_id: "622d4e9f9bd88b1599026317",
title: "asdas",
price: 100,
cartQuantity: 5,
vendor: {
email: "vendor2#gmail.com"
}
},
{
_id: "622d4e9f9bd88b1599026317",
title: "asdas",
price: 100,
cartQuantity: 5,
vendor: {
email: "vendor2#gmail.com"
}
},
],
paymentDetails: {
createdId: 1647145079,
date: "Sun Mar 13 2022",
amount: 700,
email: "user#gmail.com",
last4: "4242",
transaction: "p"
},
status: "Pending",
billing: {
country: "BD",
name: "Md. Fathe Karim",
phone: "+88010000000",
line1: "Madhabdi",
city: "Narshingdi",
postal_code: "1604",
state: "Bandarban"
}
}]
const mapped = {}; // MY function
data[0].products.forEach(item => {
if (item.vendor.email in mapped) return mapped[item.vendor.email].push(item);
mapped[item.vendor.email] = [item];
});
const expectedFormat = Object.keys(mapped).map(key => {
const o = {};
o["orders"] = mapped[key];
return o;
});
console.log(expectedFormat)
My expected result is:
[
{
"orders": [
{
"_id": "6223186e2278d4e502f5264a",
"title": "Product number 1",
"price": 600,
"vendor": {
"email": "vendor1#gmail.com"
}
}
],
"paymentDetails": {
createdId: 1647145079,
amount: 700,
email: "user#gmail.com",
}
},
{
"orders": [
{
"_id": "622d4e9f9bd88b1599026317",
"title": "Product number 2",
"price": 100,
"vendor": {
"email": "vendor2#gmail.com"
}
},
{
"_id": "622d4e9f9bd88b1599026317",
"title": "Product number 3",
"price": 100,
"vendor": {
"email": "vendor2#gmail.com"
}
}
],
"paymentDetails": {
createdId: 1647145079,
amount: 700,
email: "user#gmail.com",
}
}
]
Does expectedFormat.forEach(orders => orders.paymentDetails = data[0].paymentDetails); work? I might be misunderstanding.
If you want to add more than just the paymentDetails key then add brackets after the arrow.
expectedFormat.forEach(orders => {
orders.paymentDetails = data[0].paymentDetails;
orders.status = data[0].status;
orders.billing = data[0].billing;
});

What is the most performant way to convert an Array of Object to an Object with unique keys

I am trying to figure out the most performant Javascript way to convert an array of objects, into an object with unique keys and an array full of objects as the value.
For Example:
const array = [
{ "name": "greg", "year": "2000" },
{ "name": "john", "year": "2002" },
{ "name": "bob", "year": "2005" },
{ "name": "ned", "year": "2000" },
{ "name": "pam", "year": "2000" },
];
I would like this converted to:
{
"2000": [
{ "name": "greg", "year": "2000" },
{ "name": "ned", "year": "2000" },
{ "name": "pam", "year": "2000" }
],
"2002": [ { "name": "john", "year": "2002" } ],
"2005": [ { "name": "bob", "year": "2005" } ],
}
As of now, this is what I've done so far:
let yearsObj = {};
for (let i=0; i<array.length; i++) {
if (!yearsObj[array[i].year]) {
yearsObj[array[i].year] = [];
}
yearsObj[array[i].year].push(array[i]);
}
you can use a more elegant way to do it by using array's reduce function
// # impl
const group = key => array =>
array.reduce(
(objectsByKeyValue, obj) => ({
...objectsByKeyValue,
[obj[key]]: (objectsByKeyValue[obj[key]] || []).concat(obj)
}),
{}
);
// # usage
console.log(
JSON.stringify({
byYear: group(array),
}, null, 1)
);
// output
VM278:1 {
"carsByBrand": {
"2000": [
{
"name": "greg",
"year": "2000"
},
{
"name": "ned",
"year": "2000"
},
{
"name": "pam",
"year": "2000"
}
],
"2002": [
{
"name": "john",
"year": "2002"
}
],
"2005": [
{
"name": "bob",
"year": "2005"
}
]
}
}
It could be as simple as that Object.fromEntries(array.map(obj => [obj.year,obj])) even it is not exactly what you need, but talking about performance it is way slower than all proposed, so i'm giving it as an bad example of showing how the short statement is not always the fastest.
Your way seems to be the fastest taking about performance.
Run the snippet below to see the actual timing.
// common
let array = [
{ "name": "greg", "year": "2000" },
{ "name": "john", "year": "2002" },
{ "name": "bob", "year": "2005" },
{ "name": "ned", "year": "2000" },
{ "name": "pam", "year": "2000" },
];
// simple as a statement way
console.time();
console.log(Object.fromEntries(array.map(obj => [obj.year,obj])));
console.timeEnd();
// using .reduce way
console.time();
const result = array.reduce((prev, curr) => {
const { year } = curr;
if (prev[year]) {
prev[year].push(curr);
} else {
prev[year] = [curr];
}
return prev;
}, {});
console.log(result);
console.timeEnd();
// your way
console.time();
let yearsObj = {};
for (let i=0; i<array.length; i++) {
if (!yearsObj[array[i].year]) {
yearsObj[array[i].year] = [];
}
yearsObj[array[i].year].push(array[i]);
}
console.log(yearsObj);
console.timeEnd();
A for loop (imperative style) like you have is likely to be the fastest in most situations. However, in this case you are not likely to see much of a difference. One thing you could do to improve the code in your example is to get the array length before the for loop and assign it to the variable, so that it's not calculated every iteration of the loop.
const yearsObj = {};
const arrayLength = array.length; // Only calculate array length once
for (let i=0; i<arrayLength; i++) {
if (!yearsObj[array[i].year]) {
yearsObj[array[i].year] = [];
}
yearsObj[array[i].year].push(array[i]);
}
In this situation, my preference would be to use Array.reduce(). It is more readable and the performance difference will be negligible.
const arr = [
{ name: 'greg', year: '2000' },
{ name: 'john', year: '2002' },
{ name: 'bob', year: '2005' },
{ name: 'ned', year: '2000' },
{ name: 'pam', year: '2000' },
];
const result = arr.reduce((prev, curr) => {
const { year } = curr;
if (prev[year]) {
prev[year].push(curr);
} else {
prev[year] = [curr];
}
return prev;
}, {});
/* Result:
{ '2000':
[ { name: 'greg', year: '2000' },
{ name: 'ned', year: '2000' },
{ name: 'pam', year: '2000' } ],
'2002': [ { name: 'john', year: '2002' } ],
'2005': [ { name: 'bob', year: '2005' } ] }
*/

Grouping Elements in angularjs or javascript

I want to use month as the column, location as the row header and then the center item is as value. but unable to make it.
I want to make a matrix form table, like below:
my Json is as below:
//Output is as below....{ "QATAR": 464785, "UAE": 223428, "SAUDI ARABIA": 355212 }
$scope.resArray = [{
"Month": "January-2016",
"Year": "2016",
"Value": 26000,
"Location": "QATAR"
},
{
"Month": "January-2016",
"Year": "2016",
"Value": 0,
"Location": "QATAR"
},
{
"Month": "January-2016",
"Year": "2016",
"Value": 8700,
"Location": "UAE"
},
{
"Month": "January-2016",
"Year": "2016",
"Value": 311912,
"Location": "SAUDI ARABIA"
},{"Month": "January-2016","Year": "2016","Value": 15300,"Location":SAUDI ARABIA"},{"Month": "January-2016","Year": "2016","Value": 3000,"Location": "QATAR"},{"Month": "January-2016","Year": "2016","Value": 2500,"Location": "QATAR"},{"Month": "January-2016","Year": "2016","Value": 2300,"Location": "UAE"
}]var groupedData = {};$scope.resArray.forEach(function(item) {var Location = item.Location;var value = item.Value;if (groupedData.hasOwnProperty(Location)) {groupedData[Location] += value;
} else {groupedData[Location] = value;}});
**Please see the plunker PlunkerLink
First of all I suggest to use more than one object for grouping, one for grouping by Month and Location and another one for collection groups specific totals, like for the before mentioned groups and a total value. This is necessary to calculate the percent values, later.
In the result table, you could address the grouped values by taking row and column name for the result.
var $scope = { resArray: [{ Month: "January-2016", Year: "2016", Value: 26000, Location: "QATAR" }, { Month: "January-2016", Year: "2016", Value: 0, Location: "QATAR" }, { Month: "January-2016", Year: "2016", Value: 8700, Location: "UAE" }, { Month: "January-2016", Year: "2016", Value: 311912, Location: "SAUDI ARABIA" }, { Month: "January-2016", Year: "2016", Value: 15300, Location: "SAUDI ARABIA" }, { Month: "January-2016", Year: "2016", Value: 3000, Location: "QATAR" }, { Month: "January-2016", Year: "2016", Value: 2500, Location: "QATAR" }, { Month: "January-2016", Year: "2016", Value: 2300, Location: "UAE" }] },
grouped = {},
total = { total: 0 };
$scope.resArray.forEach(function (item) {
grouped[item.Month] = grouped[item.Month] || {};
grouped[item.Month][item.Location] = (grouped[item.Month][item.Location] || 0) + item.Value;
['Month', 'Location'].forEach(function (key) {
total[key] = total[key] || {};
total[key][item[key]] = (total[key][item[key]] || 0) + item.Value;
});
total.total += item.Value;
});
console.log(grouped);
console.log(total);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Grouping array of objects by multiple keys

I feel embarrassed for asking this question as I should know how to figure it out, but I'm spinning my wheels on grouping an array of objects by multiple keys.
Here's the data:
[
{
"car": "audi",
"type": "A6",
"style": "Avant",
"year": "1996"
},
{
"car": "audi",
"type": "A4",
"style": "2",
"year": "2006"
},
{
"car": "audi",
"type": "A4",
"style": "L W12",
"year": "2006"
},
{
"car": "audi",
"type": "80",
"style": "GLE",
"year": "1975"
},
{
"car": "audi",
"type": "A6",
"style": "Avant L",
"year": "1996"
},
{
"car": "audi",
"type": "A6",
"style": "3.2 Multitronic",
"year": "2006"
},
]
What I've been trying to generate with little success is the following:
[{
"audi": [{
"1996": {
"A6": ["Avant, Avant L"]
}
}, {
"2006": }
"A6": ["3.2 Multitronic"],
"A4": ["L W12", "2"]
}
}
....
}]
The schema is:
{
"car1": [{
"year1": {
"style1": ["trim1", "trim2"],
"style2": ["trim1", "trim2"]
},
"year1": {
"style1": ["trim1", "trim2"],
"style2": ["trim1", "trim2"]
}
}],
"car2": [{
"year1": {
"style1": ["trim1", "trim2"],
"style2": ["trim1", "trim2"]
},
"year2": {
"style1": ["trim1", "trim2"],
"style2": ["trim1", "trim2"]
}
}]
}
I've tried the following with lodash
let result = _.chain(carData)
.groupBy('car')
.toPairs()
.map(function(curr) {
return _.zipObject(['car', 'year'], curr);
})
.value();
This gets me part of the way, but I end up with incomplete data when it comes to the styles and types for each year of the car.
You could use a hash object and a nested approach for the given properties.
var data = [{ car: "audi", type: "A6", style: "Avant", year: 1996 }, { car: "audi", type: "A4", style: 2, year: 2006 }, { car: "audi", type: "A4", style: "L W12", year: 2006 }, { car: "audi", type: 80, style: "GLE", year: 1975 }, { car: "audi", type: "A6", style: "Avant L", year: 1996 }, { car: "audi", type: "A6", style: "3.2 Multitronic", year: 2006 }],
keys = ['car', 'year', 'type'],
result = [];
data.forEach(function (a) {
keys.reduce(function (r, k) {
var o = {};
if (!r[a[k]]) {
r[a[k]] = { _: [] };
o[a[k]] = r[a[k]]._;
r._.push(o);
}
return r[a[k]];
}, this)._.push(a.style);
}, { _: result });
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Here's a (slightly verbose) solution that generates exactly the JSON object shape you wanted and groups by unlimited keys:
var cars = [{
"car": "audi",
"type": "A6",
"style": "Avant",
"year": "1996"
}, {
"car": "audi",
"type": "A4",
"style": "2",
"year": "2006"
}, {
"car": "audi",
"type": "A4",
"style": "L W12",
"year": "2006"
}, {
"car": "audi",
"type": "80",
"style": "GLE",
"year": "1975"
}, {
"car": "audi",
"type": "A6",
"style": "Avant L",
"year": "1996"
}, {
"car": "audi",
"type": "A6",
"style": "3.2 Multitronic",
"year": "2006"
}, ];
function groupBy(list, prop) {
return list.reduce((groupped, item) => {
var key = item[prop];
delete item[prop];
if (groupped.hasOwnProperty(key)) {
groupped[key].push(item);
} else {
groupped[key] = [item];
}
return groupped
}, {});
}
function groupSubKeys(obj, properties, propIndex) {
var grouppedObj = groupBy(obj, properties[propIndex]);
Object.keys(grouppedObj).forEach((key) => {
if (propIndex < properties.length - 2) {
grouppedObj[key] = groupSubKeys(grouppedObj[key], properties, propIndex + 1);
} else {
grouppedObj[key] = grouppedObj[key].map(item => item[properties[propIndex + 1]])
}
});
return grouppedObj;
}
function groupByProperties(list, properties) {
return groupSubKeys(list, properties, 0);
}
console.log(groupByProperties(cars, ['car', 'year', 'type', 'style']));
Here's a running example:
http://codepen.io/rarmatei/pen/evmBOo
const groupBy = function groupBy(list, properties, propertyIndex) {
// current property index
let i = propertyIndex === undefined ? 0 : propertyIndex;
// group by
let grouppedObj = list.reduce((acc, obj) => {
let groupedValue = obj[properties[i]];
if (!groupedValue) {
return acc;
}
if (!acc[groupedValue]) {
acc[groupedValue] = [];
}
acc[groupedValue].push({ ...obj, groupBy: properties.join(",") });
return acc;
}, {});
// group by nested
const keys = Object.keys(grouppedObj);
if (i === properties.length - 1) {
return grouppedObj;
}
keys.forEach((key) => {
grouppedObj[key] = groupBy(grouppedObj[key], properties, i + 1);
});
return grouppedObj;
};
const data =[
{
"year": "2021",
"cabin": "1",
"months": ["1", "2"]
},
{
"year": "2021",
"cabin": "1",
"months": ["4"]
},
{
"year": "2021",
"cabin": "2",
"months": ["1", "2"]
},
{
"year": "2022",
"cabin": "1",
"months": ["1", "2"]
},
{
"year": "2022",
"cabin": "1",
"months": ["4"]
},
{
"year": "2022",
"cabin": "2",
"months": ["1", "2"]
}
];
const results=groupBy(data, ["year", "cabin"]);
console.log(results);

Multiple Groupings for MongoDB Aggregate

Given the following data set of Event objects:
[
{
"_id": ObjectId("4fda05cb322b1c95b531ac26",
"title": "BUTTON CLICKED",
"createdAt": ISODate("2017-01-12T01:00:00+01:00")
},
{
"_id": ObjectId("1235h1k235h1kl325h1v31gv",
"title": "BUTTON CLICKED",
"createdAt": ISODate("2017-01-14T01:00:00+01:00")
},
{
"_id": ObjectId("c2n890904cn897qnxp23hjk1",
"title": "PAGE VIEWED",
"createdAt": ISODate("2017-01-12T02:00:00+01:00")
}
]
How would I group them by date then by name?
The desired result would look like this:
[
{
_id: { year: 2017, month: 1, day: 11 },
events: [ {
title: "BUTTON PRESSED",
count: 3
}, {
title: "PAGE VIEWED",
count: 2
}
]
},
{
_id: { year: 2017, month: 1, day: 24 },
events: [ {
title: "BUTTON PRESSED",
count: 1
}
]
}
]
Any help on this issue would be greatly appreciated, so thank you!
you can try this query
db.collectionName.aggregate([
$group: {
_id : {
year : { $year : "$createdAt" },
month : { $month : "$createdAt" },
day : { $dayOfMonth : "$createdAt" },
title: "$title"
},
count:{$sum:1}
}
},
{
$group:{
_id:{
year: "$_id.year",
month: "$_id.month",
day: "$_id.day"
},
data:{
$push: {
name:"$_id.title",
count:"$count"
}
}
}
}
])

Categories