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;
CodePudding user response:
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.id
// 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}
CodePudding user response:
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);