Counter arithmetic and multisets

collections · Practical · 9 min

Comparing inventories, finding differences between word frequencies or checking anagrams all become one-liners.

A Counter doubles as a multiset: a set where elements can appear several times. It supports arithmetic between Counters:

  • a + b adds counts.
  • a - b subtracts counts and drops zero and negative results.
  • a & b intersection: the minimum of each count.
  • a | b union: the maximum of each count.
  • Unary +a removes zero and negative entries; -a keeps negatives flipped to positive.

The in-place method a.subtract(b) is different from -: it keeps negative and zero counts, which is what you want for balances like inventory minus orders.

Since 3.10, Counters support comparisons as multisets: a <= b is true if every count in a is at most the count in b, with missing items treated as zero. That gives a neat "can I build this word from these letters?" check: Counter(word) <= Counter(letters). Equality also treats missing and zero counts as equal.

c.elements() expands back into an iterator repeating each item by its count (ignoring counts below one).

These operations are linear in the number of distinct keys. They don't consider order, so use them for questions about "how many", not "in what order".

Example

from collections import Counter

stock = Counter(apple=5, pear=2, kiwi=1)
order = Counter(apple=3, pear=4)

print(stock - order)            # Counter({'apple': 2, 'kiwi': 1})
balance = stock.copy()
balance.subtract(order)
print(balance)                  # Counter({'apple': 2, 'kiwi': 1, 'pear': -2})
print(+balance)                 # Counter({'apple': 2, 'kiwi': 1})
print(stock & order)            # Counter({'apple': 3, 'pear': 2})
print(stock | order)            # Counter({'apple': 5, 'pear': 4, 'kiwi': 1})

def can_spell(word, tiles):
    return Counter(word) <= Counter(tiles)
print(can_spell("tea", "eatx"), can_spell("teat", "eatx"))  # True False
print(Counter("listen") == Counter("silent"))               # True
print(sorted(Counter(a=2, b=1).elements()))                 # ['a', 'a', 'b']

Remember

  • + - & | treat Counters as multisets; - drops non-positive counts
  • subtract() keeps negative counts for balances
  • Comparisons like <= (3.10+) test multiset inclusion

Watch out for

  • Using a - b to compute a balance and losing the items that went negative.

Check yourself

  1. Counter(a=1) - Counter(a=3) gives what?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/collections.html#collections.Counter