0

I want to iterate though an array of different ratings, sort by each unique id, sum and calculate the average of each id's ratings. Then save the averages in a new array, where I can call something like averageRating[i], where each entry will be each id's rating.

The original array of objects looks like this, where id could be any number.

data = [{id: 1, rating: 1}, {id: 1, rating: 3}, {id: 1, rating: 1}, {id: 1, rating: 4}, {id: 1, rating}, {id: 2, rating: 3}, {id: 3, rating: 5}, {id: 1, rating: 5}, {id: 1, rating: 5}, {id: 1, rating: 5, {id: 1, rating: 1}, {id: 2, rating: 4}, {id: 1, rating: 3}, {id: 1, rating: 2}]

I was able to do this work it out with only one particular id, doing something like as follows, but having some trouble working out how to do with a dynamic number of ids.

var [average, updateAverage] = useState(0);

let ratings = data.map((item) => item.rating);

// Calculate average of the array
let sum = ratings.reduce((a, b) => a + b, 0);
let avg = sum / ratings.length || 0;
let avgRounded = Math.round(avg); // Round to nearest whole number

updateAverage = avgRounded;
1
  • Inside map function,you can use index and use that index as well . That's how all the data will be unique Commented Oct 10, 2022 at 19:41

2 Answers 2

2

This is probably over the top concise but you can actually do it in one loop if you enjoy a bit of maths. See this mathematical solution

const avgCount = {} // This is a temp var needed for accumulative averaging mathematical formula

// THIS VAR HAS THE ANSWER IN
const averages = data.reduce((averages, individualRating) => {
    // We need a counter of the number of times this ID has been seen already. This is needed by the algorithm.
    // Now we have seen a new rating with this id, increase the counter to reflect that.
    // If it's not there we start from 1.
    avgCount[individualRating.id] =  (avgCount[individualRating.id] ?? 0) + 1

    // Get the current rolling average. If there isn't one (this is first rating with this id we have seen), then its just the rating of this current item.
    const currAccumulatedAverage = averages[individualRating.id] ?? individualRating.rating

    // Calculate the new rolling average from the previous one
    averages[individualRating.id] = ((currAccumulatedAverage * (avgCount[individualRating.id] - 1)) + individualRating.rating) / avgCount[individualRating.id]
    
    return averages
}, {}) 

This might be highly performant as there are no multiple looping or intermediary structures. I say might because you also need to consider the complexity of the math operations and only benchmarking could prove it in real terms for modern JS engines. But it is at least a compact and satisfying solution that could also serve in streaming situations.

For this input:

let data = [{id: 1, rating: 1}, {id: 1, rating: 3}, {id: 1, rating: 1}, {id: 1, rating: 4}, {id: 1, rating: 1}, {id: 2, rating: 3}, {id: 3, rating: 5}, {id: 1, rating: 5}, {id: 1, rating: 5}, {id: 1, rating: 5}, {id: 1, rating: 1}, {id: 2, rating: 4}, {id: 1, rating: 3}, {id: 1, rating: 2}]

It returns

{1: 2.8181818181818183, 2: 3.5, 3: 5}
9
  • It's giving me {1: 11, 2: 2, 3: 1}
    – Miller42
    Commented Oct 10, 2022 at 20:23
  • @Miller42 Sorry yes i updated, have you tried latest?
    – adsy
    Commented Oct 10, 2022 at 20:23
  • Yes, it looks good because no for loops but it's giving those values
    – Miller42
    Commented Oct 10, 2022 at 20:26
  • Thats weird, can you paste your input array here?
    – adsy
    Commented Oct 10, 2022 at 20:26
  • 1
    No need to change but I just updated with an even cleaner version. Vanity reasons really -- its the same logic.
    – adsy
    Commented Oct 10, 2022 at 20:49
0

You can use regular loops and do all processing. Read inline comments:

// Rating data
const data = [
  {id: 1, rating: 1},
  {id: 1, rating: 3},
  {id: 1, rating: 1},
  {id: 1, rating: 4},
  {id: 1, rating: 2},
  {id: 2, rating: 3},
  {id: 3, rating: 5},
  {id: 1, rating: 5},
  {id: 1, rating: 5},
  {id: 1, rating: 5},
  {id: 1, rating: 1},
  {id: 2, rating: 4},
  {id: 1, rating: 3},
  {id: 1, rating: 2}
];

// Create new object for id -> average ratings
const avr = {};

// Iterate data array
for(const item of data) {
  // Create object of arrays
  // for calculate avarage later
  if(avr[item.id]) avr[item.id].push(item.rating);
  else avr[item.id] = [item.rating];
}

// Iterate object and calculate average values
for(const el in avr) {
  // Reduce
  const sum = avr[el].reduce((a, b) => a + b, 0);
  // Calculate average and update object
  avr[el] = Math.round(sum / avr[el].length || 0);
}

// Test
console.log(avr);

3
  • Using ternary for side effects is so lame.
    – Robo Robok
    Commented Oct 10, 2022 at 20:03
  • @RoboRobok ok, specially for you answer was updated)
    – tarkh
    Commented Oct 10, 2022 at 20:16
  • Thank you very much, it works great! However, I will avoid for loops if possible!
    – Miller42
    Commented Oct 10, 2022 at 20:31

Not the answer you're looking for? Browse other questions tagged or ask your own question.