jason ewins
jason ewins

Reputation: 33

How to loop through array of objects and accumulate a value based on a separate matching value

Given I have the following array of objects:

const data = [
 {
   name: 'item1',
   score: 5,
   colour: 'red'
 },
 {
   name: 'item2',
   score: 5,
   colour: 'blue'
 },
 {
   name: 'item1',
   score: 5,
   colour: 'blue'
 },
 {
   name: 'item3',
   score: 15,
   colour: 'blue'
 },
 {
   name: 'item2',
   score: 25,
   colour: 'green'
 }
]

I'm looking for the most semantic approach to looping through this and accumulating the score, given the name value is a match - example result (we don't care about the colour key):

[
 {
  name: item1,
  score: 10
 },
 {
  name: item2,
  score: 30
 },
 {
  name: item3,
  score: 15
 }
]

The final order isn't of importance, but the data will be generated randomly by site visitors (so we have no prior knowledge of each name for example) there could be 2 entries or a maximum of 15.

Right now, I am solving this with what feels like a long winded (Sunday evening!) approach:

const data = [
 {
   name: 'item1',
   score: 5,
   colour: 'red'
 },
 {
   name: 'item2',
   score: 5,
   colour: 'blue'
 },
 {
   name: 'item1',
   score: 5,
   colour: 'blue'
 },
 {
   name: 'item3',
   score: 15,
   colour: 'blue'
 },
 {
   name: 'item2',
   score: 25,
   colour: 'green'
 }
];

let scoreData;

const checkScore = (data) => {
        // check to see if we have existing data, if not create new array and pass in our initial data
        if (!scoreData) {
           scoreData = [];
           scoreData.push(data);
        }

        // check to see if name is already present in our data, if so, accumulate the score
        else if (scoreData.filter(e => e.name === data.name).length > 0) {
            scoreData
                .filter(e => e.name === data.name)
                .map(e => e.score += data.score)
        } else {
            scoreData.push(data)
        }
}

for (let i = 0; i < data.length; i++) {
    
   // save data to new object to compare
   let dataObj = {
         name: data[i].name,
         score: Number(data[i].score)
    }
    
    checkScore(dataObj)
}
console.log(scoreData)

My gut says there's a cleaner approach here and I would love to find one! Greatly appreciate any feedback. Apologies for the poor title, also!

Upvotes: 0

Views: 439

Answers (1)

Kinglish
Kinglish

Reputation: 23654

array.reduce() was made for this! It loops through each item in the array and - in this case- checks to see if we already have used that team name. If so, we add to that items 'score' value. If not, we set a new item in our return array.

Edit answer was updated to support IE which meant doing away with arrow functions and findIndex

const data = [{
    name: 'item1',
    score: 5,
    colour: 'red'
  },
  {
    name: 'item2',
    score: 5,
    colour: 'blue'
  },
  {
    name: 'item1',
    score: 5,
    colour: 'blue'
  },
  {
    name: 'item3',
    score: 15,
    colour: 'blue'
  },
  {
    name: 'item2',
    score: 25,
    colour: 'green'
  }
]

let scores = data.reduce(function(b, a) {
  delete a.colour;
  let ind=-1;
  b.forEach(function(e,i) { if (e.name === a.name) ind = i});
  if (ind > -1) b[ind].score += a.score;
  else b.push(a);
  return b
}, [])

console.log(scores)

Upvotes: 1

Related Questions