Using sorted() in Reverse
Concepts covered: Sorting
Ranking flips the question: which items come first when bigger is better. reverse=True makes sorted() put the largest key first, and slicing the result with [:n] keeps the top N. Here are 5 tracks with a year of play counts, 2 of them tied at 310.
plays = {
"Paper Moon": 310,
"Low Tide": 95,
"Night Drive": 310,
"Static": 180,
"Afterglow": 42,
}
def by_count(pair):
return pair[1]
ranked = sorted(plays.items(), key=by_count, reverse=True)
print(ranked[:3])Output:
[('Paper Moon', 310), ('Night Drive', 310), ('Static', 180)]Each item of plays.items() is a (name, count) pair, so by_count returns pair[1]. Paper Moon and Night Drive tie at 310, and Paper Moon stays ahead because it was inserted first: reverse=True keeps equal keys in input order. Sorting ascending and then reversing with [::-1] gives a different answer:
The ascending sort keeps Paper Moon ahead of Night Drive, and [::-1] then flips the entire list, ties included. If one report uses reverse=True and another uses [::-1], the same data shows 2 different number-one tracks. Pick reverse=True for descending order with ties in input order.
Passing the dict itself to sorted() sorts its keys and returns them alone, alphabetically. To rank the names by their counts, use the dict's get method as the key: sorted() calls plays.get on each name and orders the names by the counts it returns.
plays = {
"Paper Moon": 310,
"Low Tide": 95,
"Night Drive": 310,
"Static": 180,
"Afterglow": 42,
}
print(sorted(plays))
print(sorted(plays, key=plays.get, reverse=True)[:3])Output:
['Afterglow', 'Low Tide', 'Night Drive', 'Paper Moon', 'Static'] ['Paper Moon', 'Night Drive', 'Static']
key=plays.get carries no parentheses for the same reason key=by_minutes carried none: sorted() needs the method itself, ready to call on each name. Use this form when the report lists names only, and plays.items() when each line needs its count beside the name.
Descending on one field, ascending on another
reverse=True flips every part of a tuple key together. A common ranking rule wants plays descending but names ascending, so tied tracks land alphabetically. For a numeric field, negate it inside the key: the most plays becomes the smallest key, and the sort itself stays ascending.
plays = {
"Paper Moon": 310,
"Low Tide": 95,
"Night Drive": 310,
"Static": 180,
"Afterglow": 42,
}
def rank_key(pair):
name, count = pair
return (-count, name)
for name, count in sorted(plays.items(), key=rank_key):
print(count, name)Output:
310 Night Drive 310 Paper Moon 180 Static 95 Low Tide 42 Afterglow
Say runs should be listed fastest first, and runs tied on minutes should list their names from Z to A. Names cannot be negated, so the first pass sorts by name with reverse=True and the second pass sorts by minutes:
runs = [
("sync_crm", 7),
("build_daily", 19),
("dedupe_users", 7),
("load_orders", 42),
]
def by_name(pair):
return pair[0]
def by_minutes(pair):
return pair[1]
by_name_desc = sorted(runs, key=by_name, reverse=True)
final = sorted(by_name_desc, key=by_minutes)
for job, minutes in final:
print(minutes, job)Output:
7 sync_crm 7 dedupe_users 19 build_daily 42 load_orders
After the first pass, sync_crm sits ahead of dedupe_users. The second pass sees them tied at 7 minutes and, being stable, leaves them in that order. With any number of fields the rule holds: sort by the least important field first and the most important field last, each pass with its own reverse.
The top 1 with max() and min()
When N is 1, sorting the whole list does work you then throw away. max() and min() accept the same key argument and return the single best item. When several items share the best key, they return the first one they meet.
plays = {
"Paper Moon": 310,
"Low Tide": 95,
"Night Drive": 310,
"Static": 180,
"Afterglow": 42,
}
def by_count(pair):
return pair[1]
print(max(plays.items(), key=by_count))
print(min(plays.items(), key=by_count))Output:
('Paper Moon', 310)
('Afterglow', 42)Small inputs behave differently across these tools. A slice past the end of a list returns what exists, so ranked[:5] for a listener who played only 3 tracks all year returns those 3 with no error. max() on an empty input raises ValueError, so check that a list has items before asking for its best one.
Both lines rank the same scores. What do they print?
scores = [("ana", 9), ("bo", 7), ("cy", 9)]
def by_score(pair):
return pair[1]
print(sorted(scores, key=by_score, reverse=True)[0][0])
print(sorted(scores, key=by_score)[::-1][0][0])- It prints ana, then ana
- It prints cy, then cy
- It prints cy, then ana
- It prints ana, then cy
About this lesson
This section is part of the Collections: Intermediate lesson on DataDriven, the home of data engineering. Lessons cover SQL, Python, Spark, data modeling and pipeline architecture, and each section pairs its explanation with worked examples and practice you run as you go.