3

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}}}
Mixon
  • 31
  • 3
  • 2
    Can you update your question to provide a minimal example? I can see a lot of drawing and other unrelated code in your examples making it difficult to answer you question. What format are your users stored in? You mention JSON but the structure would be helpful to give you a more directed answer. – ricekab Apr 15 '21 at 15:45
  • Thank you, this seems like a pretty straightforward sorting problem. Are you familiar with sorting (in Python or in general)? In particular, you may find [this post](https://stackoverflow.com/questions/613183/how-do-i-sort-a-dictionary-by-value) useful as I believe this is what you're trying to achieve. Let me know if that answers your question so I can mark your question as a duplicate. If that post didn't help, please let me know as well so I can try to make a more detailed response for you. – ricekab Apr 15 '21 at 16:00
  • I understand, but I don't know how to do it here – Mixon Apr 15 '21 at 16:08
  • [This post](https://stackoverflow.com/questions/11902665/top-values-from-dictionary/11902719) is actually more representative of what you'd need. The only additional step you'll need is to convert the dictionary values to something sortable. – ricekab Apr 15 '21 at 16:30
  • Ok, I will try. – Mixon Apr 15 '21 at 18:22

2 Answers2

1

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:

Ceres
  • 2,498
  • 1
  • 10
  • 28
  • I have an error: discord.ext.commands.errors.CommandInvokeError: Command raised an exception: KeyError: 'level' – Mixon Apr 16 '21 at 08:25
  • yeah i tested it with your json, its kinda broken. Check your json again, what is the correct format for it, its just user ids mapped to the experience and level right? or are you also storing guild data – Ceres Apr 16 '21 at 10:21
1

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))]
"""
ricekab
  • 630
  • 5
  • 17