1

I have a function which takes a leaderboard array (already sorted by highest to lowest total) and then assigns a rank to each object inside the array. If more than one item has the same total, then both items should have the same rank e.g:

  • 1: 60
  • 2: 55
  • 2: 55
  • 2: 55
  • 5: 50

I originally attempted to do this with map, but it appears as though I can't refer to the previous updated object inside of the map function. In the snippet below you can see how objects 2 & 3 in the array have rank: undefined.

function assignLeaderboardRanks(leaderboard) {
  return leaderboard.map((item, index) => {
    const { total } = item;
    if (index > 0 && total === leaderboard[index - 1].total) {
      return {
        rank: leaderboard[index - 1].rank,
        ...item,
      };
    }
    return {
      rank: index + 1,
      ...item,
    };
  });
}

const leaderboard = [
  { total: 60 },
  { total: 55 },
  { total: 55 },
  { total: 55 },
  { total: 50 }
];

console.log(`original`, leaderboard);

const newLeaderboard = assignLeaderboardRanks(leaderboard);

console.log(`updated`, newLeaderboard);

I am able to achieve the desired functionality using forEach, but it mutates the objects in the original array. Is there any way to do this either with map or by altering my forEach function? (Note that for some reason SO's code snippet shows the array as not mutating but if you inspect using your dev tools you can see that it is mutated).

function assignLeaderboardRanks(leaderboard) {
  const newLeaderboard = [...leaderboard];
  leaderboard.forEach((item, index) => {
    const { total } = item;
    if (index > 0 && total === leaderboard[index - 1].total) {
      item.rank = leaderboard[index - 1].rank;
    } else {
      item.rank = index + 1;
    }
  });

  return newLeaderboard;
}

const leaderboard = [
  { total: 60 },
  { total: 55 },
  { total: 55 },
  { total: 55 },
  { total: 50 }
];

console.log(`original`, leaderboard);

const newLeaderboard = assignLeaderboardRanks(leaderboard);

console.log(`updated`, newLeaderboard);
Yevhen Horbunkov
  • 14,965
  • 3
  • 20
  • 42
GuerillaRadio
  • 1,267
  • 5
  • 29
  • 59
  • `const newLeaderboard = [...leaderboard];` this only makes a shallow copy of the array, so all the objects inside are *literally* the same from the first array. Changing one changes the other. You need to clone the objects before modifying them – VLAZ Mar 23 '20 at 14:16
  • Does this answer your question? [What is the most efficient way to deep clone an object in JavaScript?](https://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-deep-clone-an-object-in-javascript) – VLAZ Mar 23 '20 at 14:17
  • Shouldn't the rank of the last object be `3` and not `5`? – ibrahim mahrir Mar 23 '20 at 14:21

1 Answers1

1

I am able to achieve the desired functionality using forEach, but it mutates the objects in the original array

As soon as you alter item inside your loop, it will modify the original array.

Is there any way to do this either with map?

Yes, there is! You can pull rank up as a closed over variable, and then increment it only if the current value is lower than the previous.

function assignLeaderboardRanks(leaderboard) {
  let rank = 1;

  return leaderboard.map((item, index) => {
    const { total } = item;
    if (index > 0 && total < leaderboard[index - 1].total) {
      rank = index + 1
    }
    return {
      rank: rank,
      ...item,
    };
  });
}

const leaderboard = [
  { total: 60 },
  { total: 55 },
  { total: 55 },
  { total: 55 },
  { total: 50 }
];

console.log(`original`, leaderboard);

const newLeaderboard = assignLeaderboardRanks(leaderboard);

console.log(`updated`, newLeaderboard);
Ja͢ck
  • 170,779
  • 38
  • 263
  • 309