Summarizing Your Data

Sorting and Ranked Summaries


Learning Objectives

  • You can sort a list with sorted(...) and list.sort(), and explain the difference between the two.
  • You can sort by a chosen field using a small named key function.
  • You can take the top n items of a sorted list with [:n] slicing.
  • You can find the best row in a list using an ordinary “best so far” loop.

Several summaries in the rest of this part need ordered data, and several need to pick the best of something. What were my three longest sessions? Which day was heaviest? The two questions are related but answered with different tools. This chapter covers both.

Two Sorting Tools

Python has two ways to put a list in order. They look similar and differ in one important way.

The first is sorted(...), a built-in function. It takes a list and returns a new list with the same elements in sorted order. The original is unchanged:

minutes_list = [45, 90, 30, 60]
ordered = sorted(minutes_list)
print(ordered) # [30, 45, 60, 90]
print(minutes_list) # [45, 90, 30, 60] -- original unchanged

The second is list.sort(), a method on the list. It modifies the list in place and returns None:

minutes_list = [45, 90, 30, 60]
result = minutes_list.sort()
print(minutes_list) # [30, 45, 60, 90] -- the list itself is now sorted
print(result) # None

If you need the original order around for other purposes, use sorted. If you do not, either works — though .sort() modifying its receiver makes it the same kind of operation as .append() and .pop(), which is a useful pattern to recognise.

A common mistake is to write ordered = minutes_list.sort(). As .sort() returns None, ordered becomes None. This is the same as we saw with subjects.append("math") in the chapter on parameters and return values in part 3.

Check this distinction before using sorting inside a larger ranking helper.

Reversing the Order

Both sorted and .sort() accept an optional reverse=True argument:

sorted([45, 90, 30, 60], reverse=True)
# [90, 60, 45, 30]

Sorting Rows by a Field

The study log holds sessions as rows of three fields. Plain sorted(sessions) does something, but probably not what you want:

sessions = [
["math", 45, "2026-05-10"],
["physics", 90, "2026-05-10"],
["history", 30, "2026-05-11"],
]
sorted(sessions)
# [['history', 30, '2026-05-11'],
# ['math', 45, '2026-05-10'],
# ['physics', 90, '2026-05-10']]

What happened: Python compared the rows the way it compares lists — element by element, starting from the left. The first field is the subject, so the rows ended up sorted alphabetically by subject. Sometimes that is what you want; usually it is not.

To sort rows by a specific field, both sorted and .sort() accept a key= argument. The key is a function that maps each row to the value you want to sort by:

def by_minutes(row):
return row[1]
sessions_by_length = sorted(sessions, key=by_minutes)
# [['history', 30, '2026-05-11'],
# ['math', 45, '2026-05-10'],
# ['physics', 90, '2026-05-10']]

For each row, by_minutes returns the minutes field, and sorted uses that as the comparison value. The result is sessions in ascending order of minutes.

Two small things to notice. You pass by_minutes, not by_minutes(...). You are handing the function itself to sorted — sorted will call it on each row, and you do not supply arguments yourself.

A function name without parentheses is the function. Sorting is the first place in this course where that distinction has real practical use.

Several Useful Key Functions

Any “sort by field” question is one named function away. A small library of them for the study log:

def by_subject(row):
return row[0]
def by_minutes(row):
return row[1]
def by_date(row):
return row[2]

Each function takes a row and returns the field you want to sort by. The call site reads naturally:

sorted(sessions, key=by_date)
sorted(sessions, key=by_minutes, reverse=True) # longest first
sorted(sessions, key=by_subject)

For sessions specifically, sorting by by_date uses the side property of ISO strings — they sort correctly as text, so the result is also chronological order. If the dates were in a different format, you would write a key function that parses them first; for ISO, sorting by the string is fine.

Sorting without mutation

0 / 5 points

Consider this helper:

def by_wait(row):
return row[1]

Which choice returns rows sorted by wait time while keeping the original list unchanged? Assume that rows is a list of lists, where the second element of each inner list is the wait time in minutes.

Slicing: Take the First N

A common ranking question is “what are the top three something?”. Once a list is sorted, you can do this with slicing:

ordered = sorted(sessions, key=by_minutes, reverse=True)
top_three = ordered[:3]

ordered[:3] is a slice expression. It means “a new list containing the first three items of ordered.” More generally, L[:n] is the first n items of L. If L has fewer than n items, the slice is as long as L allows — no error, just whatever is available:

[1, 2][:3] # → [1, 2] (fewer items than asked)
[1, 2, 3, 4][:3] # → [1, 2, 3]
[][:3] # → [] (empty input → empty slice)

This is the property that makes [:n] the right tool for “give me the top n, or all of them if there are fewer.” The empty case and the few-items case are handled automatically.

A small top_three_longest function pulls the shape together:

def top_three_longest(sessions):
"""Return the three longest sessions, longest first.
If there are fewer than three sessions, returns all of them.
Returns [] for empty input.
"""
ordered = sorted(sessions, key=by_minutes, reverse=True)
return ordered[:3]

The empty-input and short-list cases both work without special handling: sorted([]) returns [], and [][:3] returns [].

Top support tickets

0 / 5 points

Arrange the lines into a function that returns the three support tickets with the largest waiting times.

A test:

assert top_three_longest([]) == []
assert top_three_longest([["math", 45, "2026-05-10"]]) == [
["math", 45, "2026-05-10"],
]
sessions = [
["math", 45, "2026-05-10"],
["physics", 90, "2026-05-10"],
["history", 30, "2026-05-11"],
["math", 60, "2026-05-11"],
["math", 25, "2026-05-12"],
]
assert top_three_longest(sessions) == [
["physics", 90, "2026-05-10"],
["math", 60, "2026-05-11"],
["math", 45, "2026-05-10"],
]
print("top_three_longest tests passed.")

The empty case, the single-row case, and the populated case are all checked.

Top slowest deliveries

0 / 15 points

Write a function named top_n_slowest_deliveries(deliveries, n).

Each row in deliveries has this shape:

[tracking_id, days, region]

The function must return a new list containing at most n delivery rows, sorted from largest days value to smallest.

Requirements:

  • sort by the days field in column 1,
  • return a new list,
  • do not change the original list,
  • return [] when deliveries is empty,
  • return [] when n is 0 or negative,
  • return all rows in sorted order when n is larger than the list length,
  • do not print anything,
  • and do not use input(...).

Finding the Best Row

Sorting is the right tool for ranked results — the top three, the bottom five, every row in order. For finding just one result — the heaviest day, the longest session — sorting can be a bit overkill: you do the work of putting every row in order just to pick the first one.

The cleaner tool is the “best so far” loop. Walk the list, keep track of the best row seen so far, update it when you find something better.

For a list of [day, total_minutes] rows, the heaviest day is the row with the largest total:

def heaviest_row(rows):
"""Return the row with the largest value in its second field.
Returns None for an empty list.
Ties are broken by the order rows appear in the input.
"""
if len(rows) == 0:
return None
best = rows[0]
for row in rows[1:]:
if row[1] > best[1]:
best = row
return best

The empty case returns None — the summary-contract answer for “best of” when there is nothing. Otherwise, the loop starts with best = rows[0] and walks the rest of the list. For each row, it checks whether the new row is strictly larger than the best so far; if it is, the best updates.

If two rows are tied, the new one does not beat the old one (because it is not strictly larger), so the earlier row remains the best.

A test:

assert heaviest_row([]) is None
rows = [
["2026-05-10", 45],
["2026-05-11", 90],
["2026-05-12", 60],
]
assert heaviest_row(rows) == ["2026-05-11", 90]
# Tie: earlier row wins
tied = [
["2026-05-10", 60],
["2026-05-11", 60],
]
assert heaviest_row(tied) == ["2026-05-10", 60]

If you wish to find the least instead of the greatest, the pattern is the same — just change the comparison to < instead of >.

Fix the ranking functions

0 / 15 points

The starter code contains broken ranking helpers for support tickets. Fix the functions without changing their names.

A support ticket row has this shape:

[ticket_id, wait_minutes, queue]

Required behavior:

  • by_wait_minutes(ticket) returns the wait time from column 1.
  • sorted_by_wait(tickets) returns a new list sorted from longest wait to shortest wait.
  • sorted_by_wait(tickets) must not change the original list.
  • top_three_slowest(tickets) returns at most the first three rows from that sorted list.
  • busiest_queue(rows) receives rows shaped [queue_name, total_minutes].
  • busiest_queue(rows) returns the row with the largest total, or None for an empty list.
  • If totals tie in busiest_queue, the row that appears first in the input must win.
  • Do not print anything.
  • Do not use input(...).

Check Your Understanding

  1. What is the difference between sorted(values) and values.sort()?
  2. What does values.sort() return? Why is result = values.sort() almost always a mistake?
  3. The chapter passes by_minutes to sorted, not by_minutes(...). What is the difference, and why does sorted expect the first form?
  4. What does [][:3] evaluate to? Why does this matter for top_three_longest?
  5. In heaviest_row, the comparison is row[1] > best[1], not >=. What would you get if you used >= instead?

AI Study Aid

Create a chapter diagram

We're looking into to what extent AI-generated diagrams could help with studying.

Use this study aid to generate an AI-generated visual summary of the material you just studied. Each diagram style emphasizes the content in a different way, so you can choose the focus that feels most useful.

Using the diagram generator is voluntary and does not affect your course progress. We encourage you to try it out and see if it helps, but it's totally up to you! Your answers help us understand how to make better study aids in the future.

Diagrams tried: 0