Clay_F
Clay_F

Reputation: 589

JavaScript array reduce to calculate Object values of min, max, and avg

I've been stuck on this for a while. Thanks for any help!

I have these two JavaScript (es6) arrays:

const examListArray = [
  {examId: 'eee1', examName: 'Qtr 1 Exam'},
  {examId: 'eee2', examName: 'Qtr 2 Exam'},
  {examId: 'eee3', examName: 'Qtr 3 Exam'},
  {examId: 'eee4', examName: 'Final Exam'},
]

const examScoresArray = [

  {resultId: 'rrr01', examId: 'eee1', studentId: 'sss1', grade: 78},
  {resultId: 'rrr02', examId: 'eee1', studentId: 'sss2', grade: 82},
  {resultId: 'rrr03', examId: 'eee1', studentId: 'sss3', grade: 93},
  {resultId: 'rrr04', examId: 'eee1', studentId: 'sss4', grade: 85},
  {resultId: 'rrr05', examId: 'eee1', studentId: 'sss5', grade: 78},
  {resultId: 'rrr06', examId: 'eee1', studentId: 'sss6', grade: 84},
  {resultId: 'rrr07', examId: 'eee2', studentId: 'sss1', grade: 89},
  {resultId: 'rrr08', examId: 'eee2', studentId: 'sss2', grade: 88},
  {resultId: 'rrr09', examId: 'eee2', studentId: 'sss3', grade: 81},
  {resultId: 'rrr10', examId: 'eee2', studentId: 'sss4', grade: 95},

]

Here's the reduce function that I have so far, but need help with the Min, Max, and Avg parts, the examCount works perfectly.

const reducedExams = (list, scores) => {

  const countReducer = (scores, currentId) => {
    const countNum = scores.reduce((result, current) => {
      const foundAMatch = currentId === current.examId

      if (foundAMatch) {
        result++;
      }
      return result;
    }, 0);

    return countNum
  }

  const minScoreReducer = (scores, currentId) => {
     /*Need Help Here Please!*/
  }

  const maxScoreReducer = (scores, currentId) => {
     /*Need Help Here Please!*/
  }

  const avgScoreReducer = (scores, currentId) => {
     /*Need Help Here Please!*/
  }

  const resultData = list.reduce((result, current) => {

    const currentId = current.examId

    const examCount = countReducer(scores, currentId)
    const minScore = minScoreReducer(scores, currentId)
    const maxScore = maxScoreReducer(scores, currentId)
    const avgScore = avgScoreReducer(scores, currentId)

    return {
      ...result,
      [current.examName]: {
        ...result[current],
        examCount,
        minScore,
        maxScore,
        avgScore,

      }
    }

  }, {})

  return resultData

}

const examListTable = reducedExams(examListArray, examScoresArray)

console.log('exam table output', examListTable)

What I'm trying to do is is loop through my Exams List and make a table that will show something like this.

_____________Count____Min____Max___Avg

Qtr 1 Exam:____6______78_____93___83.333

Qtr 2 Exam:____4______81_____95___88.25

Qtr 3 Exam:____0______0_____0_____0

Final Exam:____0______0_____0_____0

Any help would be greatly appreciated!

And one last thing... if I'm going about this all wrong, ie, unnecessarily using reduce instead of some type of loop, Please by all means, I'm open to any suggestions. This feels very messy.

My stack: React, Redux, Firebase/FireStore

I always upvote! btw Thanks again!

Upvotes: 0

Views: 590

Answers (2)

Arman Charan
Arman Charan

Reputation: 5797

The trick to solving these problems is breaking them down into smaller objectives.

The first objective being to categorise scores by exam and the second being to derive metrics.

Arrays can typically be categorised into object form using reduce().

Math is often useful for simple processes such as finding min / max values.

See below for a practical example.

// Input.
const exams = [{examId: 'eee1', examName: 'Qtr 1 Exam'},{examId: 'eee2', examName: 'Qtr 2 Exam'},{examId: 'eee3', examName: 'Qtr 3 Exam'},{examId: 'eee4', examName: 'Final Exam'}]
const scores = [{resultId: 'rrr01', examId: 'eee1', studentId: 'sss1', grade: 78},{resultId: 'rrr02', examId: 'eee1', studentId: 'sss2', grade: 82},{resultId: 'rrr03', examId: 'eee1', studentId: 'sss3', grade: 93},{resultId: 'rrr04', examId: 'eee1', studentId: 'sss4', grade: 85},{resultId: 'rrr05', examId: 'eee1', studentId: 'sss5', grade: 78},{resultId: 'rrr06', examId: 'eee1', studentId: 'sss6', grade: 84},{resultId: 'rrr07', examId: 'eee2', studentId: 'sss1', grade: 89},{resultId: 'rrr08', examId: 'eee2', studentId: 'sss2', grade: 88},{resultId: 'rrr09', examId: 'eee2', studentId: 'sss3', grade: 81},{resultId: 'rrr10', examId: 'eee2', studentId: 'sss4', grade: 95}]

// Math.Average.
Math.average = (...args) => (args.reduce((total, x) => total + x, 0) / args.length)

// Categorise.
const categorise = scores => scores.reduce((output, score) => {
  const {examId: id, grade} = score
  if (output[id]) output[id].push(grade)
  else output[id] = [grade]
  return output
}, {})

// Metrics.
const getMetrics = (exams, scores) => exams.map(exam => {
  const {examId: id, examName: name} = exam
  const s = scores[id] || []
  const isEmpty = !s.length
  return {
    name,
    count: s.length,
    min: isEmpty ? 0 : Math.min(...s),
    max: isEmpty ? 0 : Math.max(...s),
    average: isEmpty ? 0 : Math.average(...s)
  }
})

// Output + Proof.
const categorisedScores = categorise(scores)
const metrics = getMetrics(exams, categorisedScores)
console.log(metrics)

Upvotes: 1

Anthony Liu
Anthony Liu

Reputation: 322

You can use the array method filter to get the target you want and process.

const reducedExams = (list, scores) => {

  const countReducer = (scores, currentId) => {
    const matchedScores = scores.filter(score => score.examId === currentId);
    return matchedScores.length;
  }

  const minScoreReducer = (scores, currentId) => {
    const count = scores.filter(score => score.examId === currentId)
                        .reduce((min, score) => {
                          return score.grade < min ? score.grade : min;
                        }, Number.MAX_VALUE);
    if (count === Number.MAX_VALUE) {
      return 0;
    }
    return count;
  }

  const maxScoreReducer = (scores, currentId) => {
    const count = scores.filter(score => score.examId === currentId)
                        .reduce((max, score) => {
                          return score.grade > max ? score.grade : max;
                        }, Number.MIN_VALUE);
    if (count === Number.MIN_VALUE) {
      return 0;
    }
    return count;
  }

  const avgScoreReducer = (scores, currentId) => {
    let num = 0;
    const total = scores.filter(score => score.examId === currentId)
                      .reduce((sum, score) => {
                        num++;
                        sum += score.grade;
                        return sum;
                      }, 0);
    if (num === 0) {
      return 0;
    }

    return total / num;
  }

  const resultData = list.reduce((result, current) => {

    const currentId = current.examId
    const examCount = countReducer(scores, currentId)
    const minScore = minScoreReducer(scores, currentId)
    const maxScore = maxScoreReducer(scores, currentId)
    const avgScore = avgScoreReducer(scores, currentId)

    return {
      ...result,
      [current.examName]: {
        ...result[current],
        examCount,
        minScore,
        maxScore,
        avgScore,

      }
    }

  }, {})

  return resultData;

}

const examListTable = reducedExams(examListArray, examScoresArray)

console.log('exam table output', examListTable)

Upvotes: 1

Related Questions