Discord.py top 5 users with the highest level

Viewed 139

I created a level system in discord.py, the data is saved in json. I have a command that displays the level of a given user, but I don't know how to make the top 5 users with the highest level. I haven't found how to do it anywhere.

This is my code from levels system:

async def update_data(users, user, server):
    if not str(server.id) in users:
        users[str(server.id)] = {}
        if not str(user.id) in users[str(server.id)]:
            users[str(server.id)][str(user.id)] = {}
            users[str(server.id)][str(user.id)]['experience'] = 0
            users[str(server.id)][str(user.id)]['level'] = 1
    elif not str(user.id) in users[str(server.id)]:
            users[str(server.id)][str(user.id)] = {}
            users[str(server.id)][str(user.id)]['experience'] = 0
            users[str(server.id)][str(user.id)]['level'] = 1

async def add_experience(users, user, exp, server):
  users[str(user.guild.id)][str(user.id)]['experience'] += exp

async def level_up(users, user, channel, server):
  experience = users[str(user.guild.id)][str(user.id)]['experience']
  lvl_start = users[str(user.guild.id)][str(user.id)]['level']
  lvl_end = int(experience ** (1/4))
  if str(user.guild.id) != '757383943116030074':
    if lvl_start < lvl_end:
      await channel.send('{} has leveled up to Level {}'.format(user.mention, lvl_end))
      users[str(user.guild.id)][str(user.id)]['level'] = lvl_end

JSON file:

{"678938477710278667": {"experience": 692, "level": 5}, "627244746729062421": {"experience": 48, "level": 2}, "826822736340713522": {"678938477710278667": {"experience": 2548, "level": 7}, "627244746729062421": {"experience": 16, "level": 2}}}
2 Answers

As the comments say, you need to sort through your dictionary and get the highest level and experience.

# sorting a dictionary with key
top5 = sorted(a.items(), key= lambda x: (x[1]['level'], x[1]['experience']), reverse=True)[:5]

What this does is pass a tuple as key, which python can sort through.

References:

Setup

I'll assume you've loaded the JSON file into a variable called users that is in the following format:

users = {'server1': {'userA': {'experience': 100, 'level':2},  # #2
                     'userB': {'experience': 101, 'level':1},  # #5
                     'userC': {'experience': 105, 'level':1},  # #3
                     'userD': {'experience': 103, 'level':1},  # #4
                     'userE': {'experience': 5, 'level':1},    # #7
                     'userF': {'experience': 6, 'level':1},    # #6
                     'userG': {'experience': 103, 'level':3},  # #1
                    },
         'server2': {'userA': {'experience': 61, 'level': 5}
                    }
        }

                 

Solution

Essentially, you want to sort your dictionary of users to their level+experience values. The main complication you're facing here is that your data is represented as a dictionary of level and experience, which is not built-in sortable.

Ther are several solutions we can use to sort this. We could:

  • Recalculate the total experience based on the level and experience.
  • Calculate the level as a fraction (float) based on the current experience points.
  • Use a tuple of the form (level, experience,) to sort on (Python can sort tuples)

The collections.Counter class has a built-in method for retrieving the top N of a dictionary (with sortable values). We can also create our own sort function. I've provided an example for both here:

# Scenario Top N from a specific server
def experience_sort_key(user_kvpair):
    """ Return the sort value for a user (key, value) pair in the format of (userid, experience_dict). """
    return (user_kvpair[1]['level'], user_kvpair[1]['experience'],)

def get_top_n_for_server(user_data_dict, server_name, n=5):
    """ 
    We use the above sort function to sort our data as tuples for the server we're interested in.
    Then we return the top N results of the sorted data. (by default top 5)
    """
    sorted_data = sorted(user_data_dict[server_name].items(), key=experience_sort_key, reverse=True)
    return sorted_data[:n]

def get_top_n_for_server_using_counter(user_data_dict, server_name, n=5):
    """
    We use the built-in Counter class, passing a (modified) copy of our dictionary to sort with.
    """
    server_data = {k: (v['level'],v['experience'],)for k, v in user_data_dict[server_name].items()}
    counter = collections.Counter(server_data)
    return counter.most_common(n)

# NOTE: The functions below won't print anything.
# You will have to add print statements if you want terminal output.

top_users = get_top_n_for_server(users, 'server1')
# top_users:
"""
[('userG', {'experience': 103, 'level': 3}),
 ('userA', {'experience': 100, 'level': 2}),
 ('userC', {'experience': 105, 'level': 1}),
 ('userD', {'experience': 103, 'level': 1}),
 ('userB', {'experience': 101, 'level': 1})]
"""
get_top_n_for_server(users, 'server1', n=3)  # Top 3
# top_users:
"""
[('userG', {'experience': 103, 'level': 3}),
 ('userA', {'experience': 100, 'level': 2}),
 ('userC', {'experience': 105, 'level': 1})]
"""
get_top_n_for_server_using_counter(users, 'server1')
# top_users:
"""
[('userG', (3, 103)),
 ('userA', (2, 100)),
 ('userC', (1, 105)),
 ('userD', (1, 103)),
 ('userB', (1, 101))]
"""
get_top_n_for_server_using_counter(users, 'server1', n=3)
# top_users:
"""
[('userG', (3, 103)),
 ('userA', (2, 100)),
 ('userC', (1, 105))]
"""
Related