patternjavascriptMinor
Calculate average of array of objects per key value using reduce
Viewed 0 times
perobjectsarrayaveragevaluereducecalculateusingkey
Problem
I want to find average of an array of objects based on their key values using the new functional programming style. I found my way around array
Please take a look at my code and see if this is the way to use reduce for my purpose.
Let's say I have an array of objects as follows:
I want to create another array containing the averages of each of the items in my data array. What I have, and is working, is below:
Running the code will give me my expected results:
But is this the best way to solve my problem using new
reduce and solved my problem, but not sure if this is the best way to do it. Please take a look at my code and see if this is the way to use reduce for my purpose.
Let's say I have an array of objects as follows:
private data = [
{tv: 1, radio:5,fridge:4},
{tv: 2, radio:2,fridge:null},
{tv: 3, radio:6,fridge:5}
];I want to create another array containing the averages of each of the items in my data array. What I have, and is working, is below:
function summary(){
var keys= Object.keys(data[0]);
var sums = {};
var averages = Object.keys(this.data.reduce((previous, element) => {
keys.forEach(el => {
if(element[el] !== null){
if (previous.hasOwnProperty(el)) {
previous[el].value += element[el];
previous[el].count += 1;
} else {
previous[el] = {
value: element[el],
count: 1
};
}
}
});
return previous;
}, sums)).map(name => {
return {
name: name,
average: sums[name].value / sums[name].count
};
});
console.log(averages);
}Running the code will give me my expected results:
average = [
{ "name": "tv", "average": 2 },
{ "name": "radio", "average": 4.333333333333333 },
{ "name": "fridge", "average": 4.5 }
]But is this the best way to solve my problem using new
reduce functions?Solution
Here is possibly an even more functional programming style solution, which makes use of a temporary ES6
Instead of immediately summing up the values, this code first collects the different values into an array per property, in a
Alternative output structure
Personally I find it more logical to produce output that has the same structure as the input objects, so I provide this very similar alternative. Only the final
Performance improvement
As you asked in comments about performance, I tried to improve on it, without giving up on functional programming.
I took my first code version (which will be more performant than the second), and changed the first half of the algorithm: the numbers are now summed up immediately, keeping a count next to it. For this I introduced an immediately invoked (arrow) function:
This stays within the functional programming rules, but I expect better performance than the first two versions I posted.
Map object. This has the advantage over a plain object: you can turn it into an array of pairs, and chain on that to get the final result:var data = [
{tv: 1, radio:5, fridge:4},
{tv: 2, radio:2, fridge:null},
{tv: 3, radio:6, fridge:5}
];
var avg = Array.from(data.reduce(
(acc, obj) => Object.keys(obj).reduce(
(acc, key) => typeof obj[key] == "number"
? acc.set(key, (acc.get(key) || []).concat(obj[key]))
: acc,
acc),
new Map()),
([name, values]) =>
({ name, average: values.reduce( (a,b) => a+b ) / values.length })
);
console.log(avg);
Instead of immediately summing up the values, this code first collects the different values into an array per property, in a
Map, then it calculates the averages from those arrays, turning it into the desired target structure.Alternative output structure
Personally I find it more logical to produce output that has the same structure as the input objects, so I provide this very similar alternative. Only the final
map is replaced by a reduce:var data = [
{tv: 1, radio:5, fridge:4},
{tv: 2, radio:2, fridge:null},
{tv: 3, radio:6, fridge:5}
];
var avg = Array.from(data.reduce(
(acc, obj) => Object.keys(obj).reduce(
(acc, key) => typeof obj[key] == "number"
? acc.set(key, (acc.get(key) || []).concat(obj[key]))
: acc,
acc),
new Map())).reduce(
(acc, [name, values]) =>
Object.assign(acc, { [name]: values.reduce( (a,b) => a+b ) / values.length }),
{}
);
console.log(avg);
Performance improvement
As you asked in comments about performance, I tried to improve on it, without giving up on functional programming.
I took my first code version (which will be more performant than the second), and changed the first half of the algorithm: the numbers are now summed up immediately, keeping a count next to it. For this I introduced an immediately invoked (arrow) function:
var data = [
{tv: 1, radio:5, fridge:4},
{tv: 2, radio:2, fridge:null},
{tv: 3, radio:6, fridge:5}
];
var avg = Array.from(data.reduce(
(acc, obj) => Object.keys(obj).reduce(
(acc, key) => typeof obj[key] == "number"
? acc.set(key, ( // immediately invoked function:
([sum, count]) => [sum+obj[key], count+1]
)(acc.get(key) || [0, 0])) // pass previous value
: acc,
acc),
new Map()),
([name, [sum, count]]) => ({ name, average: sum/count })
);
console.log(avg);
This stays within the functional programming rules, but I expect better performance than the first two versions I posted.
Context
StackExchange Code Review Q#141530, answer score: 5
Revisions (0)
No revisions yet.