Afi - 1 year ago 195
Javascript Question

# Calculate average of array of objects per key value using reduce

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

`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 = [
];
``````

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?

Here is possibly an even more functional programming style solution, which makes use of a temporary ES6 `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 = [
];

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 = [
];

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 = [
];

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.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download