What This Challenge Tests
3 minBy the end of this lesson you should be comfortable with:
- Picking the right comprehension shape (list / dict / set / generator).
- Writing one-line
sorted(..., key=lambda)calls for any sort criterion. - Chaining a filter, a sort and a slice.
- Combining multiple criteria with tuple keys.
The Dataset
5 minSave this dataset in arcade.py:
# arcade.py — 10 score entries from an arcade entries = [ {"player": "Aisyah", "game": "Tetris", "score": 12_400, "duration": 145}, {"player": "Wei Jie", "game": "Tetris", "score": 8_900, "duration": 92}, {"player": "Priya", "game": "Snake", "score": 4_200, "duration": 60}, {"player": "Aisyah", "game": "Snake", "score": 5_900, "duration": 75}, {"player": "Iman", "game": "Tetris", "score": 15_300, "duration": 180}, {"player": "Aizat", "game": "Pac-Man", "score": 9_800, "duration": 110}, {"player": "Priya", "game": "Tetris", "score": 11_100, "duration": 130}, {"player": "Hafiz", "game": "Pac-Man", "score": 6_400, "duration": 85}, {"player": "Aisyah", "game": "Pac-Man", "score": 13_200, "duration": 160}, {"player": "Wei Jie", "game": "Snake", "score": 3_500, "duration": 55}, ]
Ten entries. Players play multiple games. Each entry records score and duration in seconds. Five leaderboards to build.
Task 1 · Overall Top 5
7 minTop 5 entries by score, regardless of player or game.
Hint
top5 = sorted(entries, key=lambda e: e["score"], reverse=True)[:5] for i, e in enumerate(top5, 1): print(f" {i}. {e['player']:<10} {e['game']:<10} {e['score']:>6}")
Sort, slice, enumerate. Three operations, one line for the sort.
Task 2 · Per-Game Top Scorer
8 minFor each game, find the entry with the highest score. Build a dict {game: best_entry}.
Hint
games = {e["game"] for e in entries} # set comprehension best_per_game = { g: max((e for e in entries if e["game"] == g), key=lambda e: e["score"]) for g in games } for game, e in best_per_game.items(): print(f" {game:<12} {e['player']:<10} {e['score']:>6}")
A set comp for the unique games. A dict comp where the value is max of a filtered generator. The kind of dense expression that earns Level-3 status.
Task 3 · Player Total Score
8 minFor each player, sum their scores across all games. Return as a sorted list of (player, total) tuples, highest first.
Hint
players = {e["player"] for e in entries} totals = sorted( [(p, sum(e["score"] for e in entries if e["player"] == p)) for p in players], key=lambda t: t[1], reverse=True, ) for player, total in totals: print(f" {player:<10} {total:>7}")
List comprehension over players. Inside, a generator expression that filters and sums. Then sorted with a lambda key.
Task 4 · Efficiency Ranking
8 minCalculate "points per second" for each entry. Rank the top 5 most efficient players overall.
Hint
efficient = sorted( entries, key=lambda e: e["score"] / e["duration"], reverse=True, )[:5] print(f"{'Player':<10}{'Game':<10}{'PPS':>8}") for e in efficient: pps = e["score"] / e["duration"] print(f" {e['player']:<8}{e['game']:<10}{pps:>8.1f}")
The lambda computes the "points per second" ratio. sorted picks the highest. One line for the entire sort criterion.
Task 5 · Multi-Game Leaders
8 minFind players who appear in at least 2 different games. For each such player, find their highest individual score.
Hint
# Step 1 — count distinct games per player using a dict comp distinct_games = { p: {e["game"] for e in entries if e["player"] == p} for p in {e["player"] for e in entries} } # Step 2 — filter to multi-game players multi_game_players = [p for p, games in distinct_games.items() if len(games) >= 2] # Step 3 — for each, find their personal best personal_best = { p: max(e["score"] for e in entries if e["player"] == p) for p in multi_game_players } # Step 4 — sort descending and print ranking = sorted(personal_best.items(), key=lambda x: x[1], reverse=True) for player, best in ranking: print(f" {player:<10} {best:>6} ({len(distinct_games[player])} games played)")
Three comprehensions and one sorted, each doing one job. Pipeline thinking — every step transforms the data slightly closer to the answer.
Putting It All Together · leaderboards.py
8 minSave the full file. Wrap each task in a function so the main script reads cleanly:
Show one complete solution
# leaderboards.py — five leaderboards, one dataset entries = [ {"player": "Aisyah", "game": "Tetris", "score": 12_400, "duration": 145}, # ... (full dataset above) ... {"player": "Wei Jie", "game": "Snake", "score": 3_500, "duration": 55}, ] def overall_top(n=5): return sorted(entries, key=lambda e: e["score"], reverse=True)[:n] def best_per_game(): games = {e["game"] for e in entries} return { g: max((e for e in entries if e["game"] == g), key=lambda e: e["score"]) for g in games } def totals_by_player(): players = {e["player"] for e in entries} return sorted( [(p, sum(e["score"] for e in entries if e["player"] == p)) for p in players], key=lambda t: -t[1], ) def most_efficient(n=5): return sorted(entries, key=lambda e: -e["score"] / e["duration"])[:n] def multi_game_leaders(): distinct = { p: {e["game"] for e in entries if e["player"] == p} for p in {e["player"] for e in entries} } multi = [p for p, gs in distinct.items() if len(gs) >= 2] best = {p: max(e["score"] for e in entries if e["player"] == p) for p in multi} return sorted(best.items(), key=lambda x: -x[1]) # --- main --- print("=== Overall Top 5 ===") for i, e in enumerate(overall_top(), 1): print(f" {i}. {e['player']:<8} {e['game']:<10} {e['score']:>6}") print("\n=== Best per game ===") for game, e in best_per_game().items(): print(f" {game:<10} {e['player']:<8} {e['score']:>6}") print("\n=== Totals by player ===") for player, total in totals_by_player(): print(f" {player:<10} {total:>7}") print("\n=== Most efficient (PPS) ===") for e in most_efficient(): pps = e["score"] / e["duration"] print(f" {e['player']:<8} {e['game']:<10} {pps:>6.1f}") print("\n=== Multi-game leaders ===") for player, best in multi_game_leaders(): print(f" {player:<10} personal best {best}")
Non-negotiables: five functions, each one mostly a single comprehension or sort. The main block reads like a table of contents — each call produces one leaderboard. Zero loops at the top level except for printing.
Recap · The First 24 Lessons
5 minHalfway through Level 3. Here's what's in your fingers now:
OOP foundations PY-L3-01..06 classes, attrs, methods, init,
many objects, Dungeon Hero
Inheritance PY-L3-07..09 Child(Parent), super, monster family
Encapsulation PY-L3-10 _underscore convention
Polymorphism PY-L3-11..13 shared interface, monsters revisited
Dunder methods PY-L3-14..16 str, repr, eq, lt, add, len, contains, iter
Pet Shop project PY-L3-17 every L3-1-16 idea in one CLI
Decorators PY-L3-18..19 @property, @classmethod, @staticmethod
Functional PY-L3-20..23 lambda, comprehensions (list/dict/set/gen)
Leaderboards challenge PY-L3-24 functional + sorted + lambda comboYou now have OOP and functional Python — the two big tool kits — in roughly equal measure. The remaining 24 lessons add iterators/generators (the third big tool), recursion (including turtle fractals), classic algorithms with Big-O, and the data-structures arc (stack/queue/linked list). It ends with the Dungeon Quest capstone and PCEP exam prep.
PY-L3-25 introduces map() and filter() — the "old" functional tools that mostly got replaced by comprehensions. Then PY-L3-26..29 dives into iterators and generators (yield), ending with the Lazy Enemy Spawner game.
Homework
4 minAdd three new leaderboards to your leaderboards.py:
- Player average score — for each player, their mean score across all entries.
- Game popularity — sorted by number of entries (most-played first).
- Hidden gems — entries with at least 8000 score AND duration under 100 seconds. Sorted by score desc.
Sample · three new leaderboards
def average_per_player(): players = {e["player"] for e in entries} return sorted( [ (p, sum(e["score"] for e in entries if e["player"] == p) / sum(1 for e in entries if e["player"] == p)) for p in players ], key=lambda t: -t[1], ) def game_popularity(): games = {e["game"] for e in entries} return sorted( [(g, sum(1 for e in entries if e["game"] == g)) for g in games], key=lambda t: -t[1], ) def hidden_gems(): return sorted( [e for e in entries if e["score"] >= 8000 and e["duration"] < 100], key=lambda e: -e["score"], ) # Print all three for p, avg in average_per_player(): print(f" {p:<10} avg {avg:>7.1f}") for g, n in game_popularity(): print(f" {g:<10} {n} plays") for e in hidden_gems(): print(f" {e['player']:<8} {e['game']:<10} {e['score']:>6} ({e['duration']}s)")
Non-negotiables: three new functions, each mostly a single comprehension + sorted, each with a lambda key. Multi-criteria filters use and. By now this shape should feel like second nature.