DEV Community

loading...
Cover image for Applying Array Superpowers πŸ¦ΈπŸ½β€β™‚οΈ

Applying Array Superpowers πŸ¦ΈπŸ½β€β™‚οΈ

codefinity profile image Manav Misra ・4 min read

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 },
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

This function simply takes in a score and returns the appropriate 'letter grade.' Note 🎡 that there is no need for else with the use of 'early' returns inside of the ifs.

const studentGrades = studentScores.map(studentScore => {
  // Avoid mutation of the original object data
  const currStudentScore = {...studentScore}
  currStudentScore.letterGrade = assignLetterGrade(currStudentScore.examResult)
  return currStudentScore
})
Enter fullscreen mode Exit fullscreen mode

We saw this same sort of technique in our previous post

currStudentScore.letterGrade = assignLetterGrade(currStudentScore.examResult)

Here, we are doing the update; that is, adding a new πŸ”‘ and using the returned 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 returns 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' },
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

}, 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
{})
Enter fullscreen mode Exit fullscreen mode

Breakdown

  1. tally is initialized as an empty object - {})
  2. We bring in the first letterGrade - {letterGrade}
  3. Use bracket notation to see if there is any current value inside of tally for the current letter grade: tally[letterGrade]. Naturally, as tally is empty the first time, this will always be false.
  4. Set this 'letter grade πŸ”‘' inside of tally with a value of 1 - tally[letterGrade] = 1
  5. 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
}, {})
Enter fullscreen mode Exit fullscreen mode

Final Code

Discussion

pic
Editor guide