Anyone want to compare solutions?

Hi Everyone,

Am trying to practice - saw this somewhere as an interview question example - if anyone solves it and wants to compare solutions…just send over :grin:

Given an array of test results (each with a Student ID, and the student’s Score), return the Final Score for each student. A student’s Final Score is calculated as the average of his/her 5 highest test scores. You can assume each student has at least 5 test scores.

const scores = [

{id: 1, score: 50},

{id: 2, score: 80},

{id: 1, score: 100},

{id: 2, score: 80},

{id: 1, score: 100},

{id: 2, score: 60},

{id: 3, score: 70},

{id: 2, score: 80},

{id: 1, score: 40},

{id: 2, score: 80},

{id: 3, score: 90},

{id: 3, score: 90},

{id: 1, score: 95},

{id: 3, score: 90},

{id: 1, score: 100},

{id: 3, score: 90},

{id: 1, score: 100},

{id: 1, score: 100}];

Ok I think I did it. Here’s what I came up with.

function makeBigBoy(scoresArr) {
  const bigBoy = {};

  scoresArr.forEach(obj => {
    if (bigBoy[obj.id]) {
      bigBoy[obj.id].push(obj.score);
    } else {
      bigBoy[obj.id] = [];
      bigBoy[obj.id].push(obj.score);
    }
  });

  return bigBoy;
}

function makeAverages(chungus) {
  const averages = {};

  Object.keys(chungus).forEach(key => {
    chungus[key].sort((a, b) => b - a);
    averages[key] = chungus[key].slice(0, 5).reduce((a, b) => a + b) / 5;
  });

  return averages;
}

const myChungus = makeBigBoy(scores);
const studentAverages = makeAverages(myChungus);

Weird names but yours look better - will study it. Thanks!

Mine works but …guess it’s really junior-ish:

/*Given an arry of test results (each with a Student ID, and the student’s Score), return the Final Score for each student. A student’s Final Score is calculated as the average of his/her 5 highest test scores. You can assume each student has at least 5 test scores.
*/

const scores = [
{id: 1, score: 50},
{id: 2, score: 80},
{id: 1, score: 100},
{id: 2, score: 80},
{id: 1, score: 100},
{id: 2, score: 60},
{id: 3, score: 70},
{id: 2, score: 80},
{id: 1, score: 40},
{id: 2, score: 80},
{id: 3, score: 90},
{id: 3, score: 90},
{id: 1, score: 95},
{id: 3, score: 90},
{id: 1, score: 100},
{id: 3, score: 90},
{id: 1, score: 100},
{id: 1, score: 100}];
  

scores.sort(function (x, y) { 
  return x.id - y.id || y.score - x.score; 
  });

function getFinalScores (scores) {

let currentID = 1;
let scoreCount = 0;
let totalScore = 0;
let finalScore = 0;
let avgScore= 0;
let topNumOfScores = 5;

scores.forEach(function(currentRow)  
  {
if (currentID == currentRow.id)
{ 
 
  scoreCount +=1;
  if (scoreCount <= 5) {
  totalScore += currentRow.score;
  }   
}
if (currentID != currentRow.id)
{
  console.log ("For ID " + currentID)
  console.log ("Score Count is " + scoreCount)
  console.log ("Total Score is " + totalScore) 
  console.log ("Average Score is " + totalScore/topNumOfScores);

  currentID = currentRow.id;
  scoreCount = 0;
  totalScore = 0;
  scoreCount +=1;
  totalScore += currentRow.score;  

}
 
  }) //end forEach
  console.log ("For ID " + currentID)
  console.log ("Score Count is " + scoreCount)
  console.log ("Total Score is " + totalScore) 
  console.log ("Average Score is " + totalScore/scoreCount)
}
getFinalScores(scores);

Yeah weird names that make me laugh to add some fun.
There are a lot of ways you could approach this problem.
Yours works and that’s the main goal.

The makeBigBoy function just returns an Object with the students id for the key and the value is an array of all there scores.

{
  1: [100, 100, 80, ...],
  2: [100, 50, 70, ...],
}

The next function takes that object and iterates over the keys.

For each key value, which is an array of the students scores, it first sorts them from large to small.

Then it take the first 5 scores of the sorted array chungus[key].slice(0, 5) and adds those with the reduce method .reduce((a,b) => a + b), and then divide by 5 to get the average.

This average value is assigned to a new obj using the same key as in chungus for the student id averages[key].

Also you can format your code for the forum by using backticks and js like this.

Edit: Added a sentence I left out to finish explaining averages[key].

Wow! Thank you for this - very helpful!

My attempt. Is a bit cryptic because I haven’t spent time in choosing proper variables names.

/*
* Utility fn to calculare the average from 2 or more numbers.
* For this problem could have assumed only 2 numbers at a time.
*/
const average = (...nums) => nums.reduce((acc, val) => acc + val, 0) / nums.length;

/**
* calculate the average score for each student id
* @param {Array} ar
* @return {Array}
*/
calcAvg = arr => {
 // look up table. Used to quickly look up already evaluated student.
  const lookUp = new Set();

  return arr.reduce((a,c) => {
   // we already saw this student
    if(lookUp.has(c.id)) {
      // update the student Obj with a new average
      return a.map(el => el.id === c.id ? Object.assign({}, el, {score: average(el.score, c.score)}): el)
    }
    // not seen - add to table
    lookUp.add(c.id);
    // add a new student Obj to  accumulator
    return [...a, Object.assign({}, c)]
  }, [])
}

calcAvg(scores) 
/* [
{ id: 1, score: 97.421875 },
{ id: 2, score: 77.5 },
{ id: 3, score: 88.75 }
 ]

This was fun :slight_smile:
Thanks for sharing

Thanks! I learned about the Set object today.

I still have to sit and focus on what the code is actually doing. Did you sort for highest test scores? I wonder if job interviews expect this (or similar ones) to be solved in one sitting.

I definitely need more practice…

@edbc.dev

I’ve edited your post for readability. When you enter a code block into a forum post, please precede it with a separate line of three backticks and follow it with a separate line of three backticks to make easier to read.

See this post to find the backtick on your keyboard. The “preformatted text” tool in the editor (</>) will also add backticks around text.

Note: Backticks are not single quotes.

markdown_Forums

maybe I misunderstood the problem but i have slightly different results

const obj ={}
for (const score of scores){
  if(Object.keys(obj).includes(String(score.id))){
    obj[score.id].push(Number(score.score))
  }else{
    obj[score.id]= [Number(score.score)]
  }
}

for (const o in obj){
  if (obj.hasOwnProperty(o)){
    const finalScore = obj[o]
    .sort((a,b) => b-a)
    .slice(0,5)
    .reduce((acc, val)=>acc+val,0)/5
    console.log(`${o} Final Score = ${finalScore}`)
  }
}

//console.log(obj)

/*
1 Final Score = 100
2 Final Score = 76
3 Final Score = 86
*/

No I didn’t do any sorting nor any rounding of the average.
I haven’t seen in the requirement so I just skipped it.

It just happens by chance that student 1, 2, 3 appears in this order and have highest -> lowest average :smile:

All I did was try to solve this without the need of iterating through scores multiple times, (like sorting / placing data then calculating the average).

In case performance is a requirement, you can swap from a Set to a full lookup table (kinda like an associative array) with id / index so that you avoid mapping the whole accumulator to update a singe average.

My other goal was providing a different approach than the one showed before, with a lookup table used for quick evaluation.

— EDIT

I should actually read the requirement:

A student’s Final Score is calculated as the average of his/her 5 highest test scores

I haven’t done any of that.
That’s why my results are different from @Dereje1 :blush:

@Dereje1 - thank you for the noters - will do use tics next time.

And thanks for the code - looking up ’ .hasOwnProperty’ - another new one!

Those are the results for top 5 scores per student. Thanks!

I’ll keep the Wrong solution up there just as a reference for the usage of Set(); note that that calculate the average of all values for each individual student.

Below a solution that actually does what’s required: get the average of the best 5 scores, assuming each student has at least 5 scores.

This time I’ll use Map(). Which is more fitting for a key => value pair than set:

const getAverage = arr => {
  const m = new Map();
  
  // utility fn to get the average between n numbers
  const average = (...nums) => nums.reduce((acc, val) => acc + val, 0) / nums.length;


  // utility fn that sort desc and limit values to to 5
  const setScore = ({id, score}) => {
    const v = m.get(id)
      ? [...m.get(id), score].sort((a,b) => b - a).slice(0,5)
      : [score];
    m.set(id, v)
  };

  // iterare array and produce Map es: Map { 1 => [100, 60, 40 ...]}
  arr.forEach(el => setScore(el))

  // if you want an object;
  const o = {};
  m.forEach((v,k) => o[k] = average(...v) );
  return o;

  // if you are fine returing a Map
  // m.forEach((v,k) => m.set(k, average(...v)))
  // return m;
}

getAverage(scores) // { 1: 100, 2: 76, 3: 86 }

Hope it’s clear enough and provide some valid input :blush:

thanks again @Dereje1 for pointing out my mistake :+1:

let result = [];
let object = [];

entries.forEach(({ id, score }) => {
  if ((object = result.find((r) => r.id === id))) {
    object.score.push(score);
  } else {
    result.push({ id, score: [score] });
  }
});

const results = result.map(({ id, score }) => {
  const averageScore =
    score
      .sort((a, b) => b - a)
      .slice(0, 5)
      .reduce((acc, val) => acc + val, 0) / 5;
  return { id, averageScore };
});

// { id: 1, averageScore: 100 }, { id: 2, averageScore: 76 }, { id: 3, averageScore: 86 }
1 Like