Skip to content

← Stdlib Mastery step 13 of 55

Medium Primitives

groupby: summarise rows without losing half of them

Summarise (key, value) rows into key -> (count, sum) using itertools.groupby.

def summarise(rows: list[tuple[str, int]]) -> dict[str, tuple[int, int]]: ...
def solve(rows: list[tuple[str, int]]) -> dict[str, tuple[int, int]]: ...

The input is not sorted, and the same key appears in several runs. The result must have exactly one entry per distinct key, with the total count and total sum across every occurrence.

Both of groupby‘s failure modes are silent, and the tests here are built to expose them.

It does not group, it run-length encodes. A new group starts every time the key differs from the previous element. Feed it unsorted data and you get [('a', [...]), ('b', [...]), ('a', [...])] — a duplicate key, no exception. Build a dict from that and the later run overwrites the earlier one; records vanish with no error. The test rows interleave keys precisely so a no-sort solution loses data.

The group iterator is a view over the shared source. Advancing the outer groupby invalidates the group you were handed. list(groupby(data, key)) returns the right keys with empty groups — so a test that asserts on keys passes while every record is gone. Drain each group inside the loop body, before asking for the next one.

Your submission must pass mypy --strict. Use a named, annotated key function rather than a lambda: with a lambda the group key usually degrades to Any, which then disables checking on the dict you index with it. A named key function also makes it impossible to pass one key to sorted and a different one to groupby, which is a common near-miss that works on fixtures and breaks on real data.

The values are tuples, not lists. The harness compares container types exactly.

Loading visualization…