6

I have an array like so:

var array = [
     {
       name: "a",
       value: 1 
     },
     {
       name: "a",
       value: 2 
     },
     {
       name: "a",
       value: 3 
     },
     {
       name: "b",
       value: 0 
     },
     {
       name: "b",
       value: 1 
     }
 ];

And I need an array like this:

var newarray = [
     {
       name: "a",
       value: 2
     },
     {
       name: "b",
       value: 0.5
     }
 ]

Where the new array has each unique name as an object with the average value.

Is there an easy way to accomplish this?

3
  • Do you only want to know about a and b? Commented Feb 17, 2014 at 2:43
  • a and b are just examples. My array will have more names than just a and b. How are my values incorrect? a has values, 1, 2 and 3. 1+2+3 = 6, there are 3 values for a, so 6/3 = 2. b has values 0 and 1. 0 + 1 = 1, there are 2 values for b, so 1/2 = 0.5 . Commented Feb 17, 2014 at 2:52
  • @user3317337: sorry, that was my mistake -- I though the third one was for "b", not "a". Commented Feb 17, 2014 at 4:39

6 Answers 6

7

You'll have to loop through the array, computing the sum and counts for each object. Here's a quick implementation:

function average(arr) {
    var sums = {}, counts = {}, results = [], name;
    for (var i = 0; i < arr.length; i++) {
        name = arr[i].name;
        if (!(name in sums)) {
            sums[name] = 0;
            counts[name] = 0;
        }
        sums[name] += arr[i].value;
        counts[name]++;
    }

    for(name in sums) {
        results.push({ name: name, value: sums[name] / counts[name] });
    }
    return results;
}

Demonstration

Note, this kind of thing can be made much easier if you use a library like Underscore.js:

var averages = _.chain(array)
                .groupBy('name')
                .map(function(g, k) {
                    return { 
                        name: k, 
                        value: _.chain(g)
                                .pluck('value')
                                .reduce(function(x, y) { return x + y })
                                .value() / g.length
                    };
                })
                .value();

Demonstration

Sign up to request clarification or add additional context in comments.

2 Comments

I don't know that the underscore version is "much easier". It requires understanding chain, groupBy, map, pluck, value and reduce plus requires a suitable function for reduce. I'll bet it's also a lot slower than the POJS version (which can be more concise). ;-)
@RobG Fair point. It's easier if you prefer functional programming strategies. I mostly wanted to offer an alternative.
2
var array = [
     {
       name: "a",
       value: 1 
     },
     {
       name: "a",
       value: 2 
     },
     {
       name: "a",
       value: 3 
     },
     {
       name: "b",
       value: 0 
     },
     {
       name: "b",
       value: 1 
     }
 ];
var sum = {};
for(var i = 0; i < array.length; i++) {
    var ele = array[i];
    if (!sum[ele.name]) {
        sum[ele.name] = {};
        sum[ele.name]["sum"] = 0;
        sum[ele.name]["count"] = 0;
    }
    sum[ele.name]["sum"] += ele.value;
    sum[ele.name]["count"]++;
}
var result = [];
for (var name in sum) {
    result.push({name: name, value: sum[name]["sum"] / sum[name]["count"]});
}
console.log(result);

Comments

1

You can do it with Alasql library with one line of code:

var newArray = alasql('SELECT name, AVG([value]) AS [value] FROM ? GROUP BY name',
                       [array]);

Here I put "value" in square brackets, because VALUE is a keyword in SQL.

Try this example at jsFiddle

Comments

1

Here is a ES2015 version, using reduce

 let arr = [
  { a: 1, b: 1 },
  { a: 2, b: 3 },
  { a: 6, b: 4 },
  { a: 2, b: 1 },
  { a: 8, b: 2 },
  { a: 0, b: 2 },
  { a: 4, b: 3 }
]

arr.reduce((a, b, index, self) => {
 const keys = Object.keys(a)
 let c = {} 

 keys.map((key) => {
  c[key] = a[key] + b[key]

  if (index + 1 === self.length) {
    c[key] = c[key] / self.length
  }
 })

 return c
})

Comments

0

And a possible solution using ECMA5 (as we seem to be missing one)

var sums = {},
    averages = Object.keys(array.reduce(function (previous, element) {
        if (previous.hasOwnProperty(element.name)) {
            previous[element.name].value += element.value;
            previous[element.name].count += 1;
        } else {
            previous[element.name] = {
                value: element.value,
                count: 1
            };
        }

        return previous;
    }, sums)).map(function (name) {
        return {
            name: name,
            average: this[name].value / this[name].count
        };
    }, sums);

On jsFiddle

Comments

0

October 2020, I think this is the shortest way (ES6+)

const getAveragesByGroup = (arr, key, val) => {
  const average = (a, b, i, self) => a + b[val] / self.length;
  return Object.values(
    arr.reduce((acc, elem, i, self) => (
        (acc[elem[key]] = acc[elem[key]] || {
          [key]: elem[key],
          [val]: self.filter((x) => x[key] === elem[key]).reduce(average, 0),
        }),acc),{})
  );
};

console.log(getAveragesByGroup(array, 'name', 'value'))

Try by yourself :)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.