Reputation: 573
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 (i.e. 69), or call personScores(3)
and it would return 124 (i.e. 106 + 13 + 5).
Upvotes: 0
Views: 140
Reputation: 1073978
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));
Upvotes: 4
Reputation: 73211
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>
Upvotes: 1
Reputation: 44087
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)
Upvotes: 0
Reputation: 508
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
};
Upvotes: 0