[Tutor] Re: Sorting dictionary values

Christopher Smith csmith@blakeschool.org
Thu, 16 Aug 2001 01:25:19 -0500


dman <dsh8290@rit.edu> wrote:
<cut>
| | > Problem:
| | > ------
| | > I'm building a "Leader Board" application which tracks user names
| | > and displays the top 10 scores from a contest
| | 
| | > I'm using shelve/pickle for my persistence so basically what I'm
| | > confronted with is something that looks like this:
| | > 
| | > userDB = {uniqueUserID:userData}
| | > 
| | > where userData is itself a dictionary like
| | > 
| | > userData = {"fname": "Jarrett",
| | >                          "lname": "Campbell",
| | >                          "email": "jarrett@ydyn.com",
| | >                          "score":"98.8"}
| | > 
| | > 
| | > What I need to do is find the 10 uniqueUserIDs from userDB where
| the | > value of "score" is the in the top 10 of all the records and
| sort | > them in order from highest to lowest
| | 
| | > Once that is done, I have a routine to display the leaderboard.
| | > 
| | > Any suggestions for an efficient way to do this?
| 
| Dictionaries can't be sorted because the keys are hashed instead.  I
| think you need to iterate over your db and create a list of all the
| users, then sort them based on score.  The easiest way would probably
| be to make a class for the userData instead of using a plain
| dictionary -- that way you can define the comparision to behave the

<cut>

Here is another great use for the zip function:  use it to create your
own multipurpose sort function which behaves like reduce, map, and filter
in that it applies the provided function to all the elements...the
difference
is that it creates a new list with this mapping, zips it onto the user
specified list, and then sorts the user list and returns the sorted list
unzipped from the original.  

e.g.  sorted_keys=sort(lambda key:db[key]["score"], keys)

The sort function, along with some code to test the database 
problem follow:

####
def sort(func,l):
	'''Like reduce, map, and filter:  provide a 1-arg function 
        to use to determine the sort order. e.g. sort(len,l) will 
        sort l according to the length of the elements.
	'''
	l[:]=zip([func(li) for li in l],l)
	l.sort()
	l[:]=[li[1] for li in l]

#--make some data
from random import random as rnd
db = {}
for i in range(10):
	userid=int(10000*(rnd()))
	db[userid]={\
			"first":"F"+str(userid),\
			"last":"L"+str(userid),\
			"email":"F.L@dom"+str(userid)+".com",\
			"score":int(100*rnd())}

#--show it
k=db.keys()
for ki in k:
	print "User",ki,"has score",db[ki]["score"]
	
#--use sort to sort it according to the score criteria
def score(ki):
	return db[ki]["score"]
sort(score,k) # or in lambda form: sort(lambda ki:db[ki]["score"],k)

#--show that we really got them sorted
print
print '--Top 3 in DB--'
for ki in k[-3:]:
	print "User",ki,"with",db[ki]["score"]

#### Output
#User 6127 has score 49
#User 5661 has score 38
#User 8395 has score 44
#User 7978 has score 50
#User 7384 has score 78
#User 3620 has score 30
#User 7381 has score 98
#User 2964 has score 14
#User 9458 has score 77
#User 5584 has score 1
#
#--Top 3 in DB--
#User 9458 with 77
#User 7384 with 78
#User 7381 with 98

####

I suppose you could even make a general db sorter like this:

def dbsort(key,db):
  k=db.keys()
  sort(lambda ki,defkey=key:db[ki][defkey],k)
  return k

by_email=dbsort("email",db) #keys sorted according to email addresses

Happy DB'ing!

/c