Applying Array Superpowers π¦Έπ½ββοΈ
Manav Misra
Posted on November 2, 2020
Overview
Previously, we came to understand some basics about map
, filter
, and the 'King π' Array Method - reduce
.
This post will just serve as some slightly more practical application where we'll apply our knowledge to an array of student data.
To follow along, you can fork this:
The code shown ππ½ is simply to get you an Array of 50 random numbers between 50 and 100. We will use these as a basis to retend that these are some exam scores for a class π€·π½ββοΈ.
Transforming our Data into Objects
It doesn't make sense to just have some random numbers. These should be objects with some student ids associated with them.
We'll retend that our ids are just from 1 to 50. So, our data will be transformed to something like: [{id: 1, score: 55}
all the way down.
Transforming each and every piece of data sounds like...π€...map
β
const studentScores = examResults.map((examResult, index) => ({studentId: index + 1, examResult}))
Breakdown
(examResult, index)
shows usage of an optional 2nd parameter, index
that we can specify in the map
callback function. This parameter represents the index of the current item. With 50 elements, this will start at 0
and end at 49.
({studentId: index + 1, examResult})
We are returning an object literal with 2 πs, studentId
and examResult
.
studentId
's value is nothing but the current index + 1
- so it will run from 1-50, as we see in the results.
examResult
is nothing but...the examResult
π€·π½ββοΈ. We use object shorthand so that the π takes on that name and the value is the value bound to examResult
(which is that first parameter in the _callback function).
Our results look something like this:
[
{ studentId: 1, examResult: 75 },
{ studentId: 2, examResult: 85 },
{ studentId: 3, examResult: 61 },
Add Letter Grades
Next, we want to add another π, letterGrade
. This will give us the letter grade on a standard 10 point scale.
For this, let's make a pure library function that we can reuse at will:
const assignLetterGrade = score => {
if (score > 90) {
return "A"
}
if (score > 80) {
return "B"
}
if (score > 70) {
return "C"
}
if (score > 60) {
return "D"
}
return "F"
}
This function simply takes in a score
and return
s the appropriate 'letter grade.' Note π΅ that there is no need for else
with the use of 'early' return
s inside of the if
s.
const studentGrades = studentScores.map(studentScore => {
// Avoid mutation of the original object data
const currStudentScore = {...studentScore}
currStudentScore.letterGrade = assignLetterGrade(currStudentScore.examResult)
return currStudentScore
})
We saw this same sort of technique in our previous post
Array 'Superpowers' π¦ΈπΎ
Manav Misra γ» Nov 1 '20 γ» 5 min read
currStudentScore.letterGrade = assignLetterGrade(currStudentScore.examResult)
Here, we are doing the update; that is, adding a new π and using the return
ed result from assignLetterGrade
that we wrote earlier.
Filter Out Low Scores
Again, let's write a pure library function that just takes in any number and some specific 'threshold' number and just return
s a boolean that let's us know whether it's 'low' or not, based on the 'threshold:' const isLow = (num, threshold) => num < threshold
Now, we'll use filter
along with this 'library function' to create a list of all of the students that scored under 75
: const lowGrades = studentGrades.filter(({examResult}) => isLow(examResult, 75))
Inside of our filter
callback, we are destructuring the property that we care about, examResult.
We send this to our 'library function' to see if the score is less than 75
. If it is, this entire 'student object' will be returned. The result if an array of all students that have scored less than 75
.
[
{ studentId: 1, examResult: 57, letterGrade: 'F' },
{ studentId: 2, examResult: 71, letterGrade: 'C' },
{ studentId: 3, examResult: 74, letterGrade: 'C' },
Get Average Score
To figure out the average score, we will need to get the total after adding up each and every examResult
, and then divide the the length
of studentGrades
, which is of course '50.'
studentGrades.reduce((total, {examResult}) => {
total += examResult;
return total
}, 0) / studentGrades.length
Breakdown
(total, {examResult}
- reduce
requires two parameters. One keeps the 'ππ½ββοΈ total' (commonly referred to as an 'accumulator). The second parameter is each individual 'student grade record,' from which we are destructuring just the examResult
.
total += examResult;
return total
Here we are updating total
and continuing to return
it as we keep interating over each 'student score.'
Stepping back and taking a look π at reduce
, we can see that there are 2 arguments. The first is the callback function (that takes 2 parameters as discussed ππ½) _and the second is the 0
.
reduce((total, {examResult}) => {
total += examResult;
return total
},
// Optional second parameter initializes 'total' to '0'
0)
}, 0
- β οΈ This part is critical. This parameter initializes total
to be 0
. W/o this, total
would be initialized as the first element in the 'student grades array' - an object. So, we would be 'adding' an _object literal and we would get NaN
ππ½.
/ studentGrades.length
Finally, we are dividing our numerical total by that length, '50,' resulting in the average! ππ½
Tally Up the Grade Distribution
For our final task, we want to know how many "As," "Bs," "Cs," etc. there were. We want our results to look something like this: {A: 10, B: 12
- an object literal where each π is one of the letter grades and the value is the 'count' of however many of that grade that there is...
const gradeTally = studentGrades.reduce((tally, {letterGrade}) => {
// Does 'tally' already have a π for the current letter grade?
if (tally[letterGrade]) {
// Add 1 to its value
tally[letterGrade] = tally[letterGrade] + 1
} else {
// Initialize it with a value of 1
tally[letterGrade] = 1
}
return tally
},
// Initialize 'tally' as an empty object
{})
Breakdown
-
tally
is initialized as an empty object -{})
- We bring in the first
letterGrade
-{letterGrade}
- Use bracket notation to see if there is any current value inside of
tally
for the current letter grade:tally[letterGrade]
. Naturally, astally
is empty the first time, this will always be false. - Set this 'letter grade π' inside of
tally
with a value of1
-tally[letterGrade] = 1
- Continue this process by either adding a new π with a value of
1
, or by adding 1 to the current value.
Refactor β»οΈ With a Ternary
const gradeTally = studentGrades.reduce((tally, {letterGrade}) => {
tally[letterGrade] = tally[letterGrade] ? tally[letterGrade] += 1 : 1
return tally
}, {})
Final Code
Posted on November 2, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.