Skip to content
← Back to explainers

Python explainer

Count Repeated Values with Counter

About 4 min to read. Allow extra time to work through examples.

Count words or stock with Counter, rank ties in the order you choose, and keep negative changes when a report needs them.

This article uses Python. You can make it your practice mode to browse related content across the site.

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

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.

step 1 of 55

Everyday Standard Library Tools

Up next Count N-Grams and Break Ties with Counter