Ratan Uday Kumar
Ratan Uday Kumar

Reputation: 6512

How to calculate rank from total points in aggregate pipeline for gaming application

I am working on gaming application will have thousands of users.

My current aggregate pipeline output

[
    {
        "USERID": "U0004",
        "Total_Points": 10
    },
    {
        "USERID": "U0001",
        "Total_Points": 8
    },
    {
        "USERID": "U0006",
        "Total_Points": 8
    },
    {
        "USERID": "U0002",
        "Total_Points": 2
    },
    {
        "USERID": "U0003",
        "Total_Points": 1
    },

    {
        "USERID": "U0005",
        "Total_Points": 1
    }
]

Expected Output

[
    {
        "USERID": "U0004",
        "Total_Points": 10,
        "Rank": 1
    },
    {
        "USERID": "U0001",
        "Total_Points": 8,
        "Rank": 2
    },
    {
        "USERID": "U0006",
        "Total_Points": 8,
        "Rank": 2
    },
    {
        "USERID": "U0002",
        "Total_Points": 2,
        "Rank": 4
    },
    {
        "USERID": "U0003",
        "Total_Points": 1,
        "Rank": 5
    },

    {
        "USERID": "U0005",
        "Total_Points": 1,
        "Rank": 5
    }
]

Which is the best and most optimised way to get the output from mongodb aggregate with the leaderboard rank concept.

I am using mongoose mongodb connection in my project.

I can manipulate data using a for loop but it seems rather inefficent.

Thanks in advance.

Comments are greatly appreciated.

Below is image of example leader board

link

Upvotes: 3

Views: 2255

Answers (3)

Hardik Shah
Hardik Shah

Reputation: 131

you can do this with aggregate. you can check my answer on a similiar question (this seems like a duplicate): https://stackoverflow.com/a/62402340/6134150

To summarize, in the aggregate pipeline you can: 1) sort documents by score or points 2) push the players in a new array while preserving the index, this index can act as the rank 3) find the top n players from the newly formed array and/or the player in question, this player/s will have a field depicting the player's rank.

[{
    "$sort": { 
        "wins": -1
    }
},
{
    "$group": {
        "_id": false,
        "players": {
            "$push": {
                "_id": "$_id",
                "playerId": "$playerId",
                "wins": "$wins"
            }
        }
    }
},
{
    "$unwind": {
        "path": "$players",
        "includeArrayIndex": "rank"
    }
},
{
    "$match": {
        "players._id": 1234567890
    }
}]

Upvotes: 0

Daphoque
Daphoque

Reputation: 4678

Don't think you can do this with the aggregate.

You can do it in one line with javascript. Sort your array then map to get the rank with the index.

  a = [
    {
        "USERID": "U0004",
        "Total_Points": 10
    },
    {
        "USERID": "U0001",
        "Total_Points": 8
    },
    {
        "USERID": "U0006",
        "Total_Points": 8
    },
    {
        "USERID": "U0002",
        "Total_Points": 2
    },
    {
        "USERID": "U0003",
        "Total_Points": 1
    },

    {
        "USERID": "U0005",
        "Total_Points": 1
    }
]
a = a.sort(function(a, b){
    return b.Total_Points - a.Total_Points;
}).map(function(e, i){
  e.Rank = (i + 1);
  return e;
});

{USERID: "U0004", Total_Points: 10, Rank: 1}
{USERID: "U0001", Total_Points: 8, Rank: 2}
{USERID: "U0006", Total_Points: 8, Rank: 3}
{USERID: "U0002", Total_Points: 2, Rank: 4}
{USERID: "U0003", Total_Points: 1, Rank: 5}
{USERID: "U0005", Total_Points: 1, Rank: 6}

Upvotes: 1

Joseph Webber
Joseph Webber

Reputation: 2173

Here's what I came up with.

var users = [
  { "USERID": "U0001", "Total_Points": 8  },
  { "USERID": "U0004", "Total_Points": 10 },
  { "USERID": "U0003", "Total_Points": 1  },
  { "USERID": "U0006", "Total_Points": 8  },
  { "USERID": "U0002", "Total_Points": 2  },
  { "USERID": "U0005", "Total_Points": 1  }
];

users.sort((a, b) => b.Total_Points - a.Total_Points);

for (let i = 0; i < users.length; i++) {
  let totalPoints = users[i].Total_Points;
  let usersWithRank = users.filter(user => user.Total_Points === totalPoints);
  for (let user of usersWithRank) {
    user.Rank = i + 1;
  }
  i += usersWithRank.length - 1;
}

console.log(users);

Upvotes: 0

Related Questions