Skip to content
Advertisement

How can I sort and operate on unique key values

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;

Advertisement

Answer

This is probably over the top performant 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 should be highly performant as there’s no multiple looping or intermediary structures.

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}
User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement