Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use javascript reduce function to calculate average of items meeting a specific condition?

So assume I have the following array of objects:

var arr = [
  {"name": "John", "score": "8.8"},
  {"name": "John", "score": "8.6"},
  {"name": "John", "score": "9.0"},
  {"name": "John", "score": "8.3"},
  {"name": "Tom",  "score": "7.9"}
];

var count = 0;
var avgScore = arr.reduce(function (sum,person) {
  if (person.name == "John") {
    count+=1;
    return sum + parseFloat(person.score);
  }
  return sum;
},0)/count);

Question: Is there a way way to calculate the average score for "John" without creating a global count variable. Ideally, the count would be internal to the anonymous function in the arr.reduce.

like image 435
Randell D Avatar asked Feb 09 '17 20:02

Randell D


People also ask

Can reduce be used on objects JavaScript?

reduce will return the only value (object or otherwise) if the list object only has one item, without calling iterator function.

How do you use reduce on array of objects in JavaScript?

The reduce() method executes the function for each value of the array (non-empty array) from left to right. The reduce() method has the following syntax: let arr = [];arr. reduce(callback(acc, curVal, index, src), initVal);


2 Answers

You could return an object with the average in it, calculated on every loop with an update.

var arr = [{ name: "John", score: "8.8" }, { name: "John", score: "8.6" }, { name: "John", score: "9.0" }, { name: "John", score: "8.3" }, { name: "Tom", score: "7.9" }],
    avgScore = arr.reduce(function (r, person) {
        if (person.name === "John") {
            r.sum += +person.score;
            r.avg = r.sum / ++r.count;
        }
        return r;
    }, { sum: 0, count: 0, avg: 0 }).avg;

console.log(avgScore);

A version with a closure and a direct return of the average.

var arr = [{ name: "John", score: "8.8" }, { name: "John", score: "8.6" }, { name: "John", score: "9.0" }, { name: "John", score: "8.3" }, { name: "Tom", score: "7.9" }],
    avgScore = arr.reduce(function (sum, count) {
        return function (avg, person) {
            if (person.name === "John") {
                sum += +person.score;
                return sum / ++count;
            }
            return avg;
        };
    }(0, 0), 0);

console.log(avgScore);

Above as ES6

var arr = [{ name: "John", score: "8.8" }, { name: "John", score: "8.6" }, { name: "John", score: "9.0" }, { name: "John", score: "8.3" }, { name: "Tom", score: "7.9" }],
    avgScore = arr.reduce(((sum, count) => (avg, person) => person.name === "John" ? (sum += +person.score) / ++count : avg)(0, 0), 0);

console.log(avgScore);
like image 105
Nina Scholz Avatar answered Oct 08 '22 04:10

Nina Scholz


Here is yet another ES6 variant, which (ab)uses the third argument of reduce as temporary storage, and calls reduce again for a chained calculation of the average from the sum and count:

const arr = [
  {"name": "John", "score": "8.8"},
  {"name": "John", "score": "8.6"},
  {"name": "John", "score": "9.0"},
  {"name": "John", "score": "8.3"},
  {"name": "Tom",  "score": "7.9"}
];

const avg = arr.reduce( ([sum, count], {name, score}, i) =>
                        (i = name == 'John', [sum + i * score, count + i]), [0, 0] )
               .reduce( (sum, count) => sum/count );

console.log(avg);
like image 40
trincot Avatar answered Oct 08 '22 06:10

trincot