简体   繁体   中英

How to return the sum of values from an array of objects based inside an array of objects

I have the following example array of objects, each object in the array contains an id , personId , scores and finally score . In some objects the scores is either null or it contains another array of objects which are the scores. In other objects the score may contain a value instead of being null . Finally, there may be a case when the object can contain both scores and score .

const startingArray = [
  {
    id: 1,
    personId: 1,
    scores: [
      {
        id: 1,
        title: 'Google',
        score: 12
      },
      {
        id: 2,
        title: 'Bing',
        score: 23
      },
      {
        id: 3,
        title: 'Facebook',
        score: 34
      }
    ],
    score: null
  },
  {
    id: 2,
    personId: 1,
    scores: null,
    score: 123
  },
  {
    id: 3,
    personId: 2,
    scores: [
      {
        id: 4,
        title: 'Google',
        score: 7
      },
      {
        id: 5,
        title: 'Bing',
        score: 32
      },
      {
        id: 6,
        title: 'Facebook',
        score: 9
      }
    ],
    score: null
  },
  {
    id: 4,
    personId: 3,
    scores: null,
    score: 106
  },
  {
    id: 5,
    personId: 3,
    scores: [
      {
        id: 7,
        title: 'Google',
        score: 6
      },
      {
        id: 8,
        title: 'Bing',
        score: 4
      },
      {
        id: 9,
        title: 'Facebook',
        score: 3
      }
    ],
    score: 5
  }
]

I can filter the startingArray to return the valid objects for a person:

startingArray.filter(item => item.personId === personId)

And I also figured out how to use map and reduce to return a value of the score items for the person:

startingArray.filter(item => item.personId === personId).map(item => item.score).reduce((a, b) => a + b, 0)

Where I'm struggling is to be able to sum the score items in the scores array where it's set against a person.

Ultimately what I'm after is to be able to call personScores(1) and it return the total of the scores for person 1 (ie 69), or call personScores(3) and it would return 124 (ie 106 + 13 + 5).

It's not clear whether a person can appear more than once in the startingArray . Assuming they can appear more than once:

One popular way to do it would be to use Array#reduce , but I'd just use a couple of for-of loops. You don't need to pre- filter (although some prefer to, which is fine).

Here's the for-of version:

function personScore(personId) {
  let sum = 0;
  for (const entry of startingArray) {
    if (entry.personId === personId) {
      sum += entry.score || 0;
      if (entry.scores) {
        for (const {score} of entry.scores) {
          sum += score;
        }
      }
    }
  }
  return sum;
}

Live Copy:

 const startingArray = [ { id: 1, personId: 1, scores: [ { id: 1, title: 'Google', score: 12 }, { id: 2, title: 'Bing', score: 23 }, { id: 3, title: 'Facebook', score: 34 } ], score: null }, { id: 2, personId: 1, scores: null, score: 123 }, { id: 3, personId: 2, scores: [ { id: 4, title: 'Google', score: 7 }, { id: 5, title: 'Bing', score: 32 }, { id: 6, title: 'Facebook', score: 9 } ], score: null }, { id: 4, personId: 3, scores: null, score: 106 }, { id: 5, personId: 3, scores: [ { id: 7, title: 'Google', score: 6 }, { id: 8, title: 'Bing', score: 4 }, { id: 9, title: 'Facebook', score: 3 } ], score: 5 } ] function personScore(personId) { let sum = 0; for (const entry of startingArray) { if (entry.personId === personId) { sum += entry.score || 0; if (entry.scores) { for (const {score} of entry.scores) { sum += score; } } } } return sum; } console.log(personScore(1)); 

Here's the reduce version:

function personScore(personId) {
  return startingArray.reduce((sum, entry) => {
    if (entry.personId !== personId) {
      return sum;
    }
    sum += entry.score || 0;
    if (entry.scores) {
      sum += entry.scores.reduce((acc, {score}) => acc + score, 0);
    }
    return sum;
  }, 0);
}

Live Copy:

 const startingArray = [ { id: 1, personId: 1, scores: [ { id: 1, title: 'Google', score: 12 }, { id: 2, title: 'Bing', score: 23 }, { id: 3, title: 'Facebook', score: 34 } ], score: null }, { id: 2, personId: 1, scores: null, score: 123 }, { id: 3, personId: 2, scores: [ { id: 4, title: 'Google', score: 7 }, { id: 5, title: 'Bing', score: 32 }, { id: 6, title: 'Facebook', score: 9 } ], score: null }, { id: 4, personId: 3, scores: null, score: 106 }, { id: 5, personId: 3, scores: [ { id: 7, title: 'Google', score: 6 }, { id: 8, title: 'Bing', score: 4 }, { id: 9, title: 'Facebook', score: 3 } ], score: 5 } ] function personScore(personId) { return startingArray.reduce((sum, entry) => { if (entry.personId !== personId) { return sum; } sum += entry.score || 0; if (entry.scores) { sum += entry.scores.reduce((acc, {score}) => acc + score, 0); } return sum; }, 0); } console.log(personScore(1)); 

If they can appear only once, an array really isn't the way to organize that data (I'd use an object or a Map ), but with an array I'd use find to find them, and then just get the information from that one entry:

function personScore(personId) {
  const entry = startingArray.find(entry => entry.personId === personId);
  if (!entry) {
    return 0;
  }
  let sum = entry.score || 0;
  if (entry.scores) {
    for (const {score} of scores) {
      sum += score;
    }
  }
  return sum;
}

Live Copy:

 const startingArray = [ { id: 1, personId: 1, scores: [ { id: 1, title: 'Google', score: 12 }, { id: 2, title: 'Bing', score: 23 }, { id: 3, title: 'Facebook', score: 34 } ], score: null }, { id: 2, personId: 1, scores: null, score: 123 }, { id: 3, personId: 2, scores: [ { id: 4, title: 'Google', score: 7 }, { id: 5, title: 'Bing', score: 32 }, { id: 6, title: 'Facebook', score: 9 } ], score: null }, { id: 4, personId: 3, scores: null, score: 106 }, { id: 5, personId: 3, scores: [ { id: 7, title: 'Google', score: 6 }, { id: 8, title: 'Bing', score: 4 }, { id: 9, title: 'Facebook', score: 3 } ], score: 5 } ] function personScore(personId) { const entry = startingArray.find(entry => entry.personId === personId); if (!entry) { return 0; } let sum = entry.score || 0; if (entry.scores) { for (const {score} of scores) { sum += score; } } return sum; } console.log(personScore(1)); 

You can use reduce to get the sum and either use reduce again if we have an array for scores, or simply add what is at score

 function getPersonScore(arr, id) { const filtered = id ? arr.filter(e => e.personId === id) : arr; return filtered.reduce((a, b) => { if (Array.isArray(b.scores)) a += getPersonScore(b.scores); return a + (b.score || 0); }, 0); } console.log(getPersonScore(startingArray, 1)); 
 <script> const startingArray = [ { id: 1, personId: 1, scores: [ { id: 1, title: 'Google', score: 12 }, { id: 2, title: 'Bing', score: 23 }, { id: 3, title: 'Facebook', score: 34 } ], score: null }, { id: 2, personId: 1, scores: null, score: 123 }, { id: 3, personId: 2, scores: [ { id: 4, title: 'Google', score: 7 }, { id: 5, title: 'Bing', score: 32 }, { id: 6, title: 'Facebook', score: 9 } ], score: null }, { id: 4, personId: 3, scores: null, score: 106 }, { id: 5, personId: 3, scores: [ { id: 7, title: 'Google', score: 6 }, { id: 8, title: 'Bing', score: 4 }, { id: 9, title: 'Facebook', score: 3 } ], score: 5 } ]; </script> 

const personScores = (id, arr) => {
  // error catching
  if (!Array.isArray(arr)) {
    throw new Error('Input array is not an array');
  }
  if (id >= arr.length) {
    throw new Error(`Id ${id} out of array range (length ${arr.length})`);
  }
  if (!('scores' in arr[id])) {
    throw new Error(`scores key missing in input array`);
  }
  if (!Array.isArray(arr[id].scores)) {
    throw new Error(`Scores of input id ${id} not an array`);
  }

  // iterate scores array of startingArray[id], defaultValue of sum is 0
  return arr[id].scores.reduce((sum, scoreObj) => {
    if ('score' in scoreObj && !isNaN(parseInt(scoreObj.score, 10))) {
      sum += parseInt(scoreObj.score, 10);
    }
    return sum;
  }, 0); // defaultValue of the reducer
};

First find the person you want, then use reduce to add their scores , and finally add that to their score .

 const startingArray = [{id:1,personId:1,scores:[{id:1,title:'Google',score:12},{id:2,title:'Bing',score:23},{id:3,title:'Facebook',score:34}],score:null},{id:2,personId:1,scores:null,score:123},{id:3,personId:2,scores:[{id:4,title:'Google',score:7},{id:5,title:'Bing',score:32},{id:6,title:'Facebook',score:9}],score:null},{id:4,personId:3,scores:null,score:106},{id:5,personId:3,scores:[{id:7,title:'Google',score:6},{id:8,title:'Bing',score:4},{id:9,title:'Facebook',score:3}],score:5}]; const personScores = id => { let { scores, score } = startingArray.find(({ personId }) => personId); let finalScore = 0; if (score && score.length) finalScore += (Array.isArray(score) ? score.reduce((a, { score }) => a + score, 0) : score); if (scores && scores.length) finalScore += (Array.isArray(scores) ? scores.reduce((a, { score }) => a + score, 0) : scores); return finalScore; }; console.log(personScores(1)); console.log(personScores(3)); 

(The above code checks both score and scores to see whether they are arrays or not, and performs the appropriate logic accordingly)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM