0

I have an API response (committers to a git project) where members are repeated due to them having the same name but different email address.

Sample response:

[
  {"id": "122334", "name": "bob", "commits":10, "email": "1@abc.com"},
  {"id": "223411","name": "frank", "commits":4, "email": "frank@whatever.com"},
  {"id": "223411","name": "bob", "commits":19, "email": "bob@aol.com"},
]

So here I want to produce a result like:

[
  {"name": "bob", "commits":29},
  {"name": "frank", "commits":4},
]

It feels like there is need for both a reduce and a loop.... but perhaps someone can suggest a simpler way as this feels like a common everyday kind of thing!

I looked a little in underscore.js and it's groupBy function but it feels like overkill for a single usage and I couldn't get that working either :)

Seer
  • 524
  • 1
  • 8
  • 19
  • what does not work with your code? – Nina Scholz Mar 10 '20 at 21:12
  • 2
    Does this answer your question? [Most efficient method to groupby on an array of objects](https://stackoverflow.com/questions/14446511/most-efficient-method-to-groupby-on-an-array-of-objects) – Klaycon Mar 10 '20 at 21:14
  • My brain mostly :) I am lonnnng out of practice in js. I can picture a way to probably achieve something in a laborious way as mentioned with loops and reduces and all kinds of vars. But I am thinking that there is probably a better aggregation technique I can use? – Seer Mar 10 '20 at 21:14
  • 1
    Great link @Klaycon thankyou! (I wrote the question in draft as many ways as possible and no suggestions were coming up!) – Seer Mar 10 '20 at 21:21

5 Answers5

4

You don't need the additional for loop. You can do this simply with a Array.reduce function. Additionally, I would avoid importing a library such as underscore to do something that is already possible with native JavaScript. You would essentially be importing thousands of lines of code to do a simple thing. Not the best approach to anything. You should always avoid using a library unless you're going to use multiple pieces of it or if there is no other option.

Example:

const data = [
  {"id": "122334", "name": "bob", "commits":10, "email": "1@abc.com"},
  {"id": "223411","name": "frank", "commits":4, "email": "frank@whatever.com"},
  {"id": "223411","name": "bob", "commits":19, "email": "bob@aol.com"},
];

const results = data.reduce( (acc, curr) => {
  acc[curr.name] = acc[curr.name] ? acc[curr.name] + curr.commits : curr.commits;
  return acc;  
}, {});
console.log(results);
mwilson
  • 12,295
  • 7
  • 55
  • 95
  • Awesome - yes that was kind of the point of my question in that to import a monster that would get used once to handle a quick presentation of a 10 item array seemed insane! Great solution! – Seer Mar 10 '20 at 21:23
1

You can use Objects like a map, so you can do the following:

const array = [
  {"id": "122334", "name": "bob", "commits":10, "email": "1@abc.com"},
  {"id": "223411","name": "frank", "commits":4, "email": "frank@whatever.com"},
  {"id": "223411","name": "bob", "commits":19, "email": "bob@aol.com"},
]; // the response
const map = {};

for (const item of array) {
    const newCommits = item.commits;
    const oldCommits = 
        (typeof map[item.name] === 'undefined') ? 0 : map[item.name];
    map[item.name] = newCommits + oldCommits;
}

const result = [];

// Now iterate over all keys
for (const key in map) {
    result.push({name: key, commits: map[key]});
}

console.log(result);
Max
  • 965
  • 1
  • 10
  • 28
  • 1
    I like this approach too - especially if I need to start aggregating more than one stat from the same array I feel like it is going to be more readable and more of a one-shot approach. Thankyou! – Seer Mar 10 '20 at 21:46
1

You could take a hash table for collecting the values and map new objects form the entries.

var data = [{ id: "122334", "name": "bob", commits: 10, email: "1@abc.com" }, { id: "223411","name": "frank", commits: 4, email: "bob@aol.com" }, { id: "223411","name": "bob", commits: 19, email: "bob@aol.com" }],
    result = Object
        .entries(data.reduce((r, { name, commits }) => {
            r[name] = (r[name] || 0) + commits;
            return r;
        }, {}))
        .map(([name, commits]) => ({ name, commits }));

console.log(result);
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
1

You can use the function reduce for grouping and the function Object.values for extracting the grouped values.

let arr = [  {"id": "122334", "name": "bob", "commits":10, "email": "1@abc.com"},  {"id": "223411","name": "frank", "commits":4, "email": "frank@whatever.com"},  {"id": "223411","name": "bob", "commits":19, "email": "bob@aol.com"}],
    result = Object.values(arr.reduce((a, {name, commits}) => {
      (a[name] || (a[name] = {name, commits: 0})).commits += commits;
      return a;
    }, {}));

console.log(result);
Ele
  • 33,468
  • 7
  • 37
  • 75
0

Try this I used javascript map data structure

  const map = new Map();
  arr.forEach(item => {
    if (map.has(item.name)) {
      const i = map.get(item.name);
      i.commits += item.commits;
      map.set(item.name, i);
    } else {
      map.set(item.name, item);
    }
  });
  console.log(Array.from(map.values()));
Ahmed Kesha
  • 810
  • 5
  • 11