Group and sum with underscore - javascript

Input:
const data = [
{
id: 'RS11',
name: 'Road 1',
quantity: {
lengthVal: 50
}
},
{
id: 'RS11',
name: 'Road 1',
quantity: {
lengthVal: 100
}
},
{
id: 'RS11',
name: 'Road 2',
quantity: {
lengthVal: 20
}
}
]
Each property except quantity should be grouped. quantity.lengthVal should be summarized. A count property should also be added.
Expected output:
const expected = [
{
id: 'RS11',
name: 'Road 1',
count: 2,
summarizedLength: 150
},
{
id: 'RS11',
name: 'Road 2',
count: 1,
summarizedLength: 20
}
]
This is what i tried:
const groupAndSum = (arr) => {
return _.chain(arr)
.groupBy((obj) => {
// Some reduce here?
return _.values(_.without(obj), 'quantity').join('|')
})
.map((val) => {
val[0].count = val.length;
delete val[0].quantity;
return val[0];
})
.value();
}
Jsbin: https://jsbin.com/totujopume/edit?html,js,console
Dataset is quite big so performance is important.

Can do this with a fairly simple native reduce() that only makes one iteration through the array for whole procedure
const res = Object.values(
data.reduce((a, c) => {
const key = c.id + '|' + c.name;
const o = a[key] = a[key] || c;
o.count = (o.count || 0) +1;
o.summarizedLength = (o.summarizedLength || 0) + c.quantity.lengthVal;
delete c.quantity;
return a;
},{})
);
console.log(res)
.as-console-wrapper { max-height: 100%!important;}
<script>
const data = [{
id: 'RS11',
name: 'Road 1',
quantity: {
lengthVal: 50
}
},
{
id: 'RS11',
name: 'Road 1',
quantity: {
lengthVal: 100
}
},
{
id: 'RS11',
name: 'Road 2',
quantity: {
lengthVal: 20
}
}
]
</script>

Related

How to get the total amount of the orders? (ReactJS)

order history
I would like to get the total amounts per order, however, I cannot get the value of the quantity in an array
Quantity in an array
I would like to know how to get the value of the quantity in the array so that I can multiply it by to order amount? Or is there another way to get the total amount of the orders?
It's not very clear what you want to do with the quantity, nor how you will get the price of the product, but I try to give some help anyway.
Let's say you have an orders array, if you just need to extract all the quantities you can just do like this:
orders.forEach(order => {
const products = order.products;
products.forEach(product => {
const quantity = product.quantity;
console.log(quantity);
});
});
If you had a function that can give you the price of the single item, for example something like getProductValue() you can compute the total like this:
orders.forEach(order => {
const products = order.products;
products.forEach(product => {
const quantity = product.quantity;
const value = getProductValue(product.id);
const total = quantity * value;
console.log(total);
});
});
You can also create an array with all the amounts like this:
const allAmounts = orders.reduce((valuesList, order) => {
const products = order.products;
const values = products.map(product => {
const quantity = product.quantity;
const value = getProductValue(product.id);
const total = quantity * value;
return total;
});
valuesList = valuesList.concat(values);
return valuesList;
}, []);
Or you can even sum all the values like this:
const totalSum = orders.reduce((totalOrderValue, order) => {
const products = order.products;
const productSum = products.reduce((totalProductValue, product) => {
const quantity = product.quantity;
const value = getProductValue(product.id);
const total = quantity * value;
return totalProductValue += total;
}, 0);
totalOrderValue = totalOrderValue + productSum;
return totalOrderValue;
}, 0);
Here is a complete snippet with a fake orders array and a fake getProductValue function and the totalSum computation:
const orders = [
{
_id: 'xxx',
products: [
{
title: 'ptitle',
id: 'dfsdsge',
quantity: 4,
},
{
title: 'ptitle',
id: 'sdfrer',
quantity: 2,
}
]
},
{
_id: 'dddd',
products: [
{
title: 'ptitle',
id: 'dfsdsge',
quantity: 2,
},
{
title: 'ptitle',
id: 'sdfrer',
quantity: 23,
}
]
},
{
_id: 'eee',
products: [
{
title: 'ptitle',
id: 'dfsdsge',
quantity: 7,
},
{
title: 'ptitle',
id: 'sdfrer',
quantity: 2,
}
]
},
{
_id: 'zzzz',
products: [
{
title: 'ptitle',
id: 'dfsdsge',
quantity: 2,
},
{
title: 'ptitle',
id: 'sdfrer',
quantity: 1,
}
]
},
{
_id: 'hhh',
products: [
{
title: 'ptitle',
id: 'dfsdsge',
quantity: 1,
},
{
title: 'ptitle',
id: 'sdfrer',
quantity: 1,
}
]
},
{
_id: 'wedsd',
products: [
{
title: 'ptitle',
id: 'dfsdsge',
quantity: 78,
},
{
title: 'ptitle',
id: 'sdfrer',
quantity: 8,
}
]
},
{
_id: 'wedsd',
products: [
{
title: 'ptitle',
id: 'dfsdsge',
quantity: 6,
},
{
title: 'ptitle',
id: 'sdfrer',
quantity: 1,
}
]
}
];
const getProductValue = id => {
return 5;
}
const totalSum = orders.reduce((totalOrderValue, order) => {
const products = order.products;
const productSum = products.reduce((totalProductValue, product) => {
const quantity = product.quantity;
const value = getProductValue(product.id);
const total = quantity * value;
return totalProductValue += total;
}, 0);
totalOrderValue = totalOrderValue + productSum;
return totalOrderValue;
}, 0);
console.log(totalSum);
Something along the lines:
let sumOfQuantity = 0;
ORDERS[3].products.forEach(x => {
sumOfQuantity += x.quantity;
})
console.log("Total: " + (ORDERS[3].amount * sumOfQuantity))
If you need to do this for every order just do ORDERS.forEach in the same mannor

How to merge and return new array from object in es6

Suppose there are two objects.
const a = [
{ id: '1-1-1', name: 'a111' },
{ id: '1-1-2', name: 'a112' },
{ id: '1-2-1', name: 'a121' },
{ id: '1-2-2', name: 'a122' },
{ id: '2-1-1', name: 'a211' },
{ id: '2-1-2', name: 'a212' }
]
const b = ['1-1', '1-2', '2-1']
and the result
{
'1-1':[
{ id: '1-1-1', name: 'a111' },
{ id: '1-1-2', name: 'a112' },
],
'1-2':[
{ id: '1-2-1', name: 'a121' },
{ id: '1-2-2', name: 'a122' },
],
'2-1':[
{ id: '2-1-1', name: 'a211' },
{ id: '2-1-2', name: 'a212' },
]
}
Basically, I want to group the data.
I use includes to check if the item from b to match the id from a. Then construct the new array.
This is my attempt(fiddle):
return b.map(item => a.map(jtem => {
if(jtem.id.includes(item)){
return {
[item]: jtem
}
}
}))
For somehow, it doesn't work.
and, is there a clever way to avoid the nested for loop or map function?
You can do that in following steps:
Apply reduce() on the array b
During each iteration use filter() on the the array a
Get all the items from a which starts with item of b using String.prototype.startsWith()
At last set it as property of the ac and return ac
const a = [
{ id: '1-1-1', name: 'a111' },
{ id: '1-1-2', name: 'a112' },
{ id: '1-2-1', name: 'a121' },
{ id: '1-2-2', name: 'a122' },
{ id: '2-1-1', name: 'a211' },
{ id: '2-1-2', name: 'a212' }
]
const b = ['1-1', '1-2', '2-1']
let res = b.reduce((ac,b) => {
ac[b] = a.filter(x => x.id.startsWith(b));
return ac;
},{})
console.log(res)
As suggested by #Falco is the comments that It would be better to scan over the a once as its large. So here is that version.Actually its better regarding performance
const a = [
{ id: '1-1-1', name: 'a111' },
{ id: '1-1-2', name: 'a112' },
{ id: '1-2-1', name: 'a121' },
{ id: '1-2-2', name: 'a122' },
{ id: '2-1-1', name: 'a211' },
{ id: '2-1-2', name: 'a212' }
]
const b = ['1-1', '1-2', '2-1']
let res = a.reduce((ac,x) => {
let temp = b.find(y => x.id.startsWith(y))
if(!ac[temp]) ac[temp] = [];
ac[temp].push(x);
return ac;
},{})
console.log(res)
Note: startsWith is not supported by I.E. So you can create polyfill using indexOf
if(!String.prototype.startWith){
String.prototype.startsWith = function(str){
return this.indexOf(str) === 0
}
}

Array of objects value with some weight age value not comes in proper sequence

I have an array of object like this:
let messageScoreData = {
messagescore: [
{
userid: "5bacc8c6563a882a1ca7756a",
score: 2605.4
},
{
userid: "5bacc98431481e0520856df8",
score: 1013.2
},
{
userid: "5bc6d0bb26f1bb1b44a790c6",
score: 41
},
{
userid: "5bc6d0bb26f1bb1b44a790c9",
score: 29
}
],
messagescorebefore: [
{
userid: "5bacc8c6563a882a1ca7756a",
score: 3754
},
{
userid: "5bacc98431481e0520856df8",
score: 1259.8
},
{
userid: "5bc6d0bb26f1bb1b44a790c6",
score: 98
},
{
userid: "5bced078d62b321d08f012af",
score: 22
},
{
userid: "5bcec1ad11302529f452b31e",
score: 6
},
{
userid: "5c10afec8c587d2fac8c356e",
score: 6
},
{
userid: "5c07b7f199848528e86e9359",
score: 3
},
{
userid: "5bed1373f94b611de4425259",
score: 2
},
{
userid: "5c21ccff833a5006fc5a98af",
score: 2
},
{
userid: "5c21ccff82e32c05c4043410",
score: 1
}
]
};
Now we will provide the weight-age value i.e messagescorebefore array have 0.4 value and messagescore have 0.6 value;
For that I have the algorithm which sequentialize the value with weight-age value. i.e
var result = messageScoreData;
var columns = [
{
name: "messagescorebefore",
value: 0.4
},
{
name: "messagescore",
value: 0.6
}
];
var total = {};
for (let column of columns) {
for (let userid of result[column.name]) {
var alphabet = userid.userid;
if (total[alphabet]) {
total[alphabet] += column.value;
} else {
total[alphabet] = column.value;
}
}
}
const valueholder = Object.keys(total)
.map(key => ({ name: key, value: total[key] }))
.sort((f, s) => s.value - f.value);
console.log(valueholder);
By this Algo output is :
[ { name: '5bacc8c6563a882a1ca7756a', value: 1 },
{ name: '5bc6d0bb26f1bb1b44a790c6', value: 1 },
{ name: '5bacc98431481e0520856df8', value: 1 },
{ name: '5bc6d0bb26f1bb1b44a790c9', value: 0.6 },
{ name: '5bcec1ad11302529f452b31e', value: 0.4 },
{ name: '5bced078d62b321d08f012af', value: 0.4 },
{ name: '5c07b7f199848528e86e9359', value: 0.4 },
{ name: '5bed1373f94b611de4425259', value: 0.4 },
{ name: '5c21ccff833a5006fc5a98af', value: 0.4 },
{ name: '5c21ccff82e32c05c4043410', value: 0.4 },
{ name: '5c10afec8c587d2fac8c356e', value: 0.4 } ]
Problem is userid: "5bacc98431481e0520856df8" will come on second position on both array but after final calculation this will come under 3rd position which is wrong.
expected output will be like this:
[ { name: '5bacc8c6563a882a1ca7756a', value: 1 },
{ name: '5bacc98431481e0520856df8', value: 1 },
{ name: '5bc6d0bb26f1bb1b44a790c6', value: 1 },
{ name: '5bc6d0bb26f1bb1b44a790c9', value: 0.6 },
{ name: '5bced078d62b321d08f012af', value: 0.4 },
{ name: '5bcec1ad11302529f452b31e', value: 0.4 },
{ name: '5c10afec8c587d2fac8c356e', value: 0.4 },
{ name: '5c07b7f199848528e86e9359', value: 0.4 },
{ name: '5bed1373f94b611de4425259', value: 0.4 },
{ name: '5c21ccff833a5006fc5a98af', value: 0.4 },
]
Any help is really appreciated for this. Thanks in advance
Actually, you want to preserve relative order of elements. normal sort function is not guaranteed to preserve relative order. so we need some tricks to keep relative order like below.
let messageScoreData = {
messagescore: [
{
userid: "5bacc8c6563a882a1ca7756a",
score: 2605.4
},
{
userid: "5bacc98431481e0520856df8",
score: 1013.2
},
{
userid: "5bc6d0bb26f1bb1b44a790c6",
score: 41
},
{
userid: "5bc6d0bb26f1bb1b44a790c9",
score: 29
}
],
messagescorebefore: [
{
userid: "5bacc8c6563a882a1ca7756a",
score: 3754
},
{
userid: "5bacc98431481e0520856df8",
score: 1259.8
},
{
userid: "5bc6d0bb26f1bb1b44a790c6",
score: 98
},
{
userid: "5bced078d62b321d08f012af",
score: 22
},
{
userid: "5bcec1ad11302529f452b31e",
score: 6
},
{
userid: "5c10afec8c587d2fac8c356e",
score: 6
},
{
userid: "5c07b7f199848528e86e9359",
score: 3
},
{
userid: "5bed1373f94b611de4425259",
score: 2
},
{
userid: "5c21ccff833a5006fc5a98af",
score: 2
},
{
userid: "5c21ccff82e32c05c4043410",
score: 1
}
]
};
var result = messageScoreData;
var columns = [
{
name: "messagescorebefore",
value: 0.4
},
{
name: "messagescore",
value: 0.6
}
];
var total = [];
for (let column of columns) {
for (let userid of result[column.name]) {
var alphabet = userid.userid;
if (total[alphabet]) {
total[alphabet] += column.value;
} else {
total[alphabet] = column.value;
}
}
}
let res = Object.keys(total).map((k, idx) => {
return {
name: k,
value: total[k],
index: idx
}
})
var output = res.sort((f, s) => {
if (s.value < f.value) return -1;
if (s.value > f.value) return 1;
return f.index - s.index
})
console.log("output : ", output)
The observed behaviour is expected since you are sorting the values in a descending way: .sort((f, s) => s.value - f.value);. From your example it seems that you want to sort the entries lexicographically on the names. In that case you should sort according to the names:
const valueholder = Object.keys(total)
.map(key => ({ name: key, value: total[key] }))
.sort((f, s) => f.name.localeCompare(s.name));
If you want to sort them primarily on the values (descending) and secondarily on the names (ascending) then do:
const valueholder = Object.keys(total)
.map(key => ({ name: key, value: total[key] }))
.sort((f, s) => s.value - f.value || f.name.localeCompare(s.name));
In this case, if two entries have the same value the difference s.value - f.value will be 0. Since this is a falsy value, f.name.localeCompare(s.name) will be evaluated, effectively sorting the values lexicographically on their name.
If you want to sort the entries based on their values but retain the original order for entries with the same value you can do the following:
const entries = Object.keys(total)
.map(key => ({ name: key, value: total[key] }))
const valueholder = entries.sort((f, s) => s.value - f.value || arr.indexOf(f) - arr.indexOf(s));
The reason we need to explicitly sort on their original order is because the built-in sorting algorithm is not (guaranteed to be) stable. Note that the above sorting is not very efficient since we use indexOf. I leave it as an exercise to first loop through the array and accumulate all indexes in a map that maps names to indexes. As such, when sorting you can look up the indexes rather than computing them.
If you're looking for stable sort, i.e. preserving the original order of elements of the array with equal value, you have to add a comparison of the indexes of the key array (assuming that this has the proper ordering):
const keys = Object.keys(total);
const valueholder = keys
.map(key => ({ name: key, value: total[key] }))
.sort((f, s) => s.value - f.value || keys.indexOf(f.name) < keys.indexOf(s.name));

Reduce array by its items key in ES6

I've got array of fruits:
const fruits = [
{ name: 'apple', count: 2 },
{ name: 'apple', count: 4 },
{ name: 'banana', count: 5 },
{ name: 'apple', count: 3 },
{ name: 'apple', count: 4 },
{ name: 'apple', count: 1 },
{ name: 'banana', count: 3 },
{ name: 'apple', count: 1 },
{ name: 'orange', count: 2 }
];
What I try to achieve is to reduce this array so it look like this:
let fruitsReduces = [
{ name: 'apple', count: 15 },
{ name: 'banana', count: 8 },
{ name: 'orange', count: 2 }
];
I've done this using very ugly for loop:
for (let i = 0; i < fruits.length; i++) {
fruitItem = {};
fruitItem.name = fruits[i].name;
fruitItem.count = fruits[i].count;
const fruitAlreadyInTheArray = fruitsReduced.find(fruit => fruit.name === fruitItem.name);
if (!fruitAlreadyInTheArray) {
fruitsReduced.push(fruitItem);
} else {
fruitAlreadyInTheArray.count += fruitItem.count;
}
}
But I'm sure that same thing can be done somehow by using ES6 array.reduce. Can you help me?
I looked at JS (ES6): Reduce array based on object attribute but I can't figure it out.
You can us reduce() the array into an object. Use Object.values() to convert the object into an array.
const fruits = [{"name":"apple","count":2},{"name":"apple","count":4},{"name":"banana","count":5},{"name":"apple","count":3},{"name":"apple","count":4},{"name":"apple","count":1},{"name":"banana","count":3},{"name":"apple","count":1},{"name":"orange","count":2}];
var result = Object.values(fruits.reduce((c, {name,count}) => {
c[name] = c[name] || {name,count: 0};
c[name].count += count;
return c;
}, {}));
console.log(result);
You could use an array as accumulator and seach for an inserted object with a wanted name for adding the actual count. If not found add a new object.
const
fruits = [{ name: 'apple', count: 2 }, { name: 'apple', count: 4 }, { name: 'banana', count: 5 }, { name: 'apple', count: 3 }, { name: 'apple', count: 4 }, { name: 'apple', count: 1 }, { name: 'banana', count: 3 }, { name: 'apple', count: 1 }, { name: 'orange', count: 2 }],
result = fruits.reduce((r, { name, count }) => {
var item = r.find(o => o.name === name);
if (!item) {
item = { name, count: 0 };
r.push(item);
}
item.count += count;
return r;
}, []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
A different approach by collecting all counts first with a Map and render the wanted array with Array.from and a mapping function which builds new objects.
const
fruits = [{ name: 'apple', count: 2 }, { name: 'apple', count: 4 }, { name: 'banana', count: 5 }, { name: 'apple', count: 3 }, { name: 'apple', count: 4 }, { name: 'apple', count: 1 }, { name: 'banana', count: 3 }, { name: 'apple', count: 1 }, { name: 'orange', count: 2 }],
result = Array.from(
fruits.reduce(
(m, { name, count }) => m.set(name, (m.get(name) || 0) + count),
new Map
),
([name, count]) => ({ name, count })
);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can try to reduce() like
const fruits = [
{ name: 'apple', count: 2 },
{ name: 'apple', count: 4 },
{ name: 'banana', count: 5 },
{ name: 'apple', count: 3 },
{ name: 'apple', count: 4 },
{ name: 'apple', count: 1 },
{ name: 'banana', count: 3 },
{ name: 'apple', count: 1 },
{ name: 'orange', count: 2 }
];
function getReduced(total, {name,count}) {
total[name] = total[name] || {name,count: 0};
total[name].count += count;
return total;
}
let fruitsReduces = Object.values(fruits.reduce(getReduced,{}));
console.log(fruitsReduces);
Yet another (slightly different) solution
const fruits = [
{ name: 'apple', count: 2 },
{ name: 'apple', count: 4 },
{ name: 'banana', count: 5 },
{ name: 'apple', count: 3 },
{ name: 'apple', count: 4 },
{ name: 'apple', count: 1 },
{ name: 'banana', count: 3 },
{ name: 'apple', count: 1 },
{ name: 'orange', count: 2 } ];
const reduced = Object.entries (
fruits.reduce( (p, o) =>
p[o.name] ? (p[o.name] += o.count, p) : {...p, ...{[o.name]: o.count}}, {} ) )
.map(([key, value]) => ({name: key, count: value}));
console.log(reduced);
You might use a hashtable to build up the resultig array:
const result = [], hash = {};
for(const {name, count} of fruits) {
if(hash[name]) {
hash[name].count += count;
} else {
result.push(hash[name] = {name, count});
}
}
You can use a simple forEach method like this:
const res = {};
fruits.forEach((e) => res[e.name] = (res[e.name] || 0) + e.count);
res will contain your expected result.

How to filter and calculate same object array value in Javascript

For example i have this object array
const purchases = [
{
name: 'a',
price: 500
},
{
name: 'a',
price: 1000
},
{
name: 'b',
price: 500
},
{
name: 'b',
price: 500
}
]
How can i effectively filter and calculate by purchases.name, create the output can be like this
let filteredPurchases = [
{
name: 'a',
price: 1500
},
{
name: 'b',
price: 1000
}
]
You can use array#reduce with Object.values(), group your data based on name value and add price for same name in an object and then extract out all the values.
const purchases = [ { name: 'a', price: 500 }, { name: 'a', price: 1000 }, { name: 'b', price: 500 }, { name: 'b', price: 500 } ],
result = Object.values(purchases.reduce((r,{name, price}) => {
r[name] = r[name] || {name, price : 0};
r[name].price += price;
return r;
},{}));
console.log(result);
You can do something simple with Array#reduce and an object reference for keeping the index.
const purchases = [{
name: 'a',
price: 500
},
{
name: 'a',
price: 1000
},
{
name: 'b',
price: 500
},
{
name: 'b',
price: 500
}
];
// object for keeping index
const ref = Object.create(null);
let res = purchases
// iterate over the array
.reduce((arr, o) => {
// check index already defined, if defined add price value
if (o.name in ref) arr[ref[o.name]].price += o.price;
// if not then define index and insert a new object with all property of object
else arr[ref[o.name] = arr.length] = Object.assign({}, o);
// return the array reference
return arr;
// set initial value as an array for result
}, [])
console.log(res);
const purchases = [
{
name: 'a',
price: 500
},
{
name: 'a',
price: 1000
},
{
name: 'b',
price: 500
},
{
name: 'b',
price: 500
}
];
var result = []
function findSum(array, key) {
let sum = 0;
array.forEach(function(element) {
if(key == element.name) {
sum = sum + element.price;
}
});
return sum;
}
purchases.forEach(function(element) {
let sum = findSum(purchases, element.name);
result[element.name] =({name:element.name,price:sum});
});
console.log("Result obj")
console.log(Object.values(result));
You can create and intermediate map to sum the price of the items, and then iterate through it to create the desired result:
const purchases = [{
name: 'a',
price: 500
},
{
name: 'a',
price: 1000
},
{
name: 'b',
price: 500
},
{
name: 'b',
price: 500
}
];
const mapped = purchases.reduce((result, item) => {
if (!result[item.name]) {
result[item.name] = item.price;
} else {
result[item.name] = result[item.name] + item.price;
}
return result;
}, {});
const result = Object.keys(mapped).map(key => {
return {
name: key,
price: mapped[key]
};
});
console.log(result);

Categories