runtime
79 / 250
lesson 79 Collections & Itertools · intermediate

Runs of equal neighbours: groupby

`itertools.groupby` walks a sequence and hands you each run of neighbouring items that share a key. It finds streaks in time-ordered data, and groups a whole list once the list is sorted by the same key.

You'll be able to
  • Loop over groupby(items, key=…) as (key, run) pairs and turn a run into a list before measuring it
  • Sort by the same key first when every item with that key must land in one group
  • Recognise that a run is used up once groupby moves on
The idea

A health check pings a server every minute and logs up or down. The useful summary isn't *how many downs in total* but *when did it go down, and for how long*: the runs of equal values next to each other.

from itertools import groupby gives a tool for exactly that. groupby(items) yields one (key, run) pair per run of equal neighbours. run is not a list but an *iterator*: a one-pass stream of that run's items, which you can loop over once or turn into a list with list(run).

Run each example to see what it prints. Running examples doesn't count toward progress.

example-1.py · Up and down periods
from itertools import groupby

checks = ["up", "up", "down", "down", "down", "up", "up", "up"]
for status, run in groupby(checks):
    print(status, len(list(run)))
example-2.py · Measuring a run directly
from itertools import groupby

for status, run in groupby(["up", "up", "down"]):
    print(status, len(run))

To group a whole list by a key, sort it by that key first, so equal keys sit next to each other, then group with the same key function. key= works as it does for sorted: groupby compares what the key returns, not the items themselves.

example-3.py · Order totals per day
from itertools import groupby

def daily_totals(orders):
    ordered = sorted(orders, key=lambda o: o["date"])
    totals = []
    for date, day_orders in groupby(ordered, key=lambda o: o["date"]):
        totals.append((date, sum(o["total"] for o in day_orders)))
    return totals

orders = [
    {"date": "2025-03-04", "total": 35.5},
    {"date": "2025-03-03", "total": 20.25},
    {"date": "2025-03-04", "total": 14.5},
    {"date": "2025-03-03", "total": 9.75},
    {"date": "2025-03-05", "total": 60.0},
]
print(daily_totals(orders))

When to use it: streaks and periods in ordered data (uptime, consecutive hot days, repeated log lines), and per-key summaries of data that is sorted anyway. For grouping unsorted data in one pass, the defaultdict(list) from two lessons ago is simpler. Mistakes to avoid: forgetting to sort (the same key shows up in several groups); sorting by one key and grouping by another; and keeping a run to read later: once groupby moves to the next run, the old one is used up. Turn it into a list inside the loop if you need it again.

Review
  • groupby(items, key=…) yields (key, run) for each run of neighbours with the same key.
  • A run is a one-pass stream: list(run) to measure or keep it, inside the loop.
  • To group a whole list, sort it by the same key first.
  • Next: itertools.combinations, every pair (or trio) from a list, each once.
Practise this

Loading your progress…

To master this lesson, solve both challenges without hints or the answer, and pass the concept check.

Runs of equal neighbours: groupby — Collections & Itertools · Runtime