Sam Sverko
Sam Sverko

Reputation: 1590

How to group and sum values from array of objects?

I have an array of objects (responses), which can contain any number of objects (i.e. indeterminate number of rounds and questions), in any order. The objects inside will be similar:

responses [
  {
    name: "personA"
    uniqueId: "abcd"
    roundNumber: 0
    questionNumber: 0
    score: 1
  },
  {
    name: "personA"
    uniqueId: "abcd"
    roundNumber: 0
    questionNumber: 1
    score: 1
  },
  {
    name: "personA"
    uniqueId: "abcd"
    roundNumber: 1
    questionNumber: 0
    score: 0
  },
    name: "personB"
    uniqueId: "efgh"
    roundNumber: 0
    questionNumber: 0
    score: 1
  },
  {
    name: "personB"
    uniqueId: "efgh"
    roundNumber: 0
    questionNumber: 1
    score: 0
  },
  {
    name: "personB"
    uniqueId: "efgh"
    roundNumber: 1
    questionNumber: 0
    score: 1
  }
]

How would I group and sum the scores depending on the values of the objects? More specifically I would like to take all the objects for personA (using their name and uniqueId fields), then sum up the scores by roundNumber. Then repeat the process for every player.

Example of flow:

The resulting array could be something like:

sorted = [
  {
    name: "personA",
    uniqueId: "abcd",
    scores: [2, 0]
  },
  {
    name: "personB",
    uniqueId: "efgh",
    scores: [1, 1]
  }
]

I've looked at for loops, indexOf, map, filter, reduce, and I'm struggling hard with this one because the amount of players, rounds, or questions could be any amount.

Upvotes: 1

Views: 86

Answers (3)

Nina Scholz
Nina Scholz

Reputation: 386519

You could take a standard approach for grouping and increment the value of a certain index.

var data = [{ name: "personA", uniqueId: "abcd", roundNumber: 0, questionNumber: 0, score: 1 }, { name: "personA", uniqueId: "abcd", roundNumber: 0, questionNumber: 1, score: 1 }, { name: "personA", uniqueId: "abcd", roundNumber: 1, questionNumber: 0, score: 0 }, { name: "personB", uniqueId: "efgh", roundNumber: 0, questionNumber: 0, score: 1 }, { name: "personB", uniqueId: "efgh", roundNumber: 0, questionNumber: 1, score: 0 }, { name: "personB", uniqueId: "efgh", roundNumber: 1, questionNumber: 0, score: 1 }],
    grouped = Object.values(data.reduce((r, { name, uniqueId, roundNumber, score }) => {
        r[uniqueId] = r[uniqueId] || { name, uniqueId, scores: [] };
        r[uniqueId].scores[roundNumber] = (r[uniqueId].scores[roundNumber] || 0) + score;
        return r;
    }, {}));

console.log(grouped);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Upvotes: 2

Reynier Rivero
Reynier Rivero

Reputation: 2392

This solution solution runs in N (log N) which is the most efficient thing you can do. assuming you have a lot of items in the responses array

const responses = [......];
var group = [];
responses.sort((a, b) => {
    if (a.name === b.name)
        return a.uniqueId < b.uniqueId;
    return a.name < b.name;
}).forEach(item => {
    if(group.length === 0 || group[group.length - 1].name !== item.name || group[group.length - 1].uniqueId !== item.uniqueId) {
        group.push({
            name: item.name,
            uniqueId: item.uniqueId,
            scores: { [item.roundNumber]: item.score }
        })
    } else {
        if (group[group.length - 1].scores[item.roundNumber]) {
            group[group.length - 1].scores[item.roundNumber] += item.score;
        } else {
            group[group.length - 1].scores[item.roundNumber] = item.score;
        }
    }
});

group = group.map(item => {
    const scores = [];
    for( const i in item.scores ) {
        scores.push(item.scores[i]);
    }
    return {...item, scores};
});

Upvotes: 0

Sajeeb Ahamed
Sajeeb Ahamed

Reputation: 6390

You can try this with a reduce method. Also, I make the scores as an object with roundNumber index which can help you to define which score gained for which round.

const responses = [{ name: "personA", uniqueId: "abcd", roundNumber: 0, questionNumber: 0, score: 1 }, { name: "personA", uniqueId: "abcd", roundNumber: 0, questionNumber: 1, score: 1 }, { name: "personA", uniqueId: "abcd", roundNumber: 1, questionNumber: 0, score: 0 }, { name: "personB", uniqueId: "efgh", roundNumber: 0, questionNumber: 0, score: 1 }, { name: "personB", uniqueId: "efgh", roundNumber: 0, questionNumber: 1, score: 0 }, { name: "personB", uniqueId: "efgh", roundNumber: 1, questionNumber: 0, score: 1 }];


const res = responses.reduce((a, c) => {
	if (!a.find(x => x.name === c.name)) {
	    a.push({name: c.name, uniqueId: c.uniqueId, scores: {[c.roundNumber]: c.score}});
	} else {
	    let index = a.findIndex(x => x.name === c.name);
	    if ( index > -1) {
	        a[index].scores[c.roundNumber] = (a[index].scores[c.roundNumber] || 0) + c.score;
	    }
	}
	return a;
}, []);

console.log(res);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Upvotes: 0

Related Questions