You have a list of words and want to know which ones come up most. A plain
dict works, but every count takes the same three steps. Check for the key,
start it at zero, then add one. collections.Counter does the counting for
you.
from collections import Counter
counts: Counter[str] = Counter(["pear", "apple", "pear"])
print(counts["pear"]) # 2
print(counts["plum"]) # 0, and "plum" is still not stored
print(counts.most_common(1)) # [('pear', 2)]
A Counter is a dict that maps each item to the number of times it appears.
Reading a missing key with brackets gives 0 and stores nothing.
counts["plum"] += 1 stores the new count. The get method works as it does
on any dict, so counts.get("plum") returns None.
The methods you use most
| Method | What it does |
|---|---|
Counter(iterable) |
Counts each item. |
Counter(mapping) |
Copies existing counts. |
c.most_common(k) |
Returns up to k (item, count) pairs, highest count first. |
c.total() |
Adds up all the counts. |
c.update(other) |
Adds counts in place. |
c.subtract(other) |
Subtracts counts in place, and keeps zero and negative results. |
c.elements() |
Repeats each item as many times as its count, and skips counts below 1. |
The operators +, -, & and | return a new counter that keeps only
positive counts. The & operator takes the smaller count of each item, and
| takes the larger.
from collections import Counter
left = Counter(a=3, b=1)
right = Counter(a=1, b=4)
assert left + right == Counter(a=4, b=5)
assert left - right == Counter(a=2)
assert left & right == Counter(a=1, b=1)
assert left | right == Counter(a=3, b=4)
Check your understanding How do you report stock changes in both directions? Show answer Hide answer
Copy the new counts and call subtract with the old ones. The subtract
method keeps negative results, so a fall in stock shows up as a negative
number. Then drop the zeros if the report should list only changes:
from collections import Counter
before = Counter(apples=5, pears=2, plums=1)
after = Counter(apples=3, pears=4, plums=1)
delta = Counter(after)
delta.subtract(before)
changed = {name: count for name, count in delta.items() if count != 0}
assert changed == {"apples": -2, "pears": 2}
assert after == Counter(apples=3, pears=4, plums=1)
Writing after - before would lose the apples, because - keeps only
positive counts. Copying after first leaves the original counts as they were.
Choose how ties are ranked
When two items have the same count, most_common lists them in the order they
first appeared in the input. The exercise asks for ties in alphabetical order
instead, so sort the pairs yourself with a two-part key:
from collections import Counter
counts = Counter(["zebra", "apple", "zebra", "apple"])
print(counts.most_common(1)) # [('zebra', 2)]
ranked = sorted(counts.items(), key=lambda item: (-item[1], item[0]))
print(ranked[:1]) # [('apple', 2)]
The key (-count, word) sorts by count, highest first, and then by word.
Python's sort is stable, which means it keeps items with equal keys in their
input order. So if you sorted by -item[1] alone, tied words would stay in the
order the counter first saw them.
Sort all the pairs before you take the first k. If you sort only the output
of most_common(k), a tied item that it already left out never comes back.
Alphabetical here means Python's string order, in which the capital letters
A to Z come before the lowercase letters a to z. "Zebra" sorts before "apple".
A stored zero is still a key
from collections import Counter
with_zero = Counter(a=1, b=0)
without_zero = Counter(a=1)
assert with_zero == without_zero # a missing item counts as zero
assert "b" in with_zero # but the zero is still stored
assert len(with_zero) == 2
Two counters are equal when every item has the same count, and a missing item
counts as zero. A stored zero still shows up in in, in len and when you
loop over the keys. Writing +counts returns a new counter with only the
positive counts, which drops zeros and negatives in one step. Leave it out of
a report of changes, because it drops the falls too.
Type what you count
Counter[str] tells mypy that the keys are strings and the counts are
integers. For pairs of words, write Counter[tuple[str, str]]. An empty
Counter() with no annotation fails mypy --strict, because mypy cannot tell
what the keys will be.
Going further
Python 3.15 adds the ^ operator, which gives how far apart the two counts of
each item are. The exercises here run on Python 3.12 and later, so leave
it out of your solution. The
Counter documentation
lists every method.