Home > Software design >  How can I sort and operate on unique key values
How can I sort and operate on unique key values

Time:10-11

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);

  • Related