Count ‘em
Batteries included is a blog series about the Python Standard Library. Each day, I share insights, ideas and examples for different parts of the library. Blaugust is an annual blogging festival in August where the goal is to write a blog post every day of the month.
Today we continue with the collections module and look into Counter.
Basic usage
from collections import Counter
# A random generated list of 1000 numbers between 0 and 10
data = [6, 5, 9, 4, 4, 2, 8, 1, 10, ...]
counts = Counter(data)
print(counts)
# Prints
# Counter({10: 102, 2: 99, 6: 96, 0: 93,
# 9: 91, 8: 89, 1: 88, 7: 88,
# 4: 86, 3: 85, 5: 83})
To use Counter, you feed it any iterable or mapping and it will return a Counter object that is a subclass of dictionary. If you pass it an iterable like a list, the keys of the resulting dictionary will be items in the iterable and values will be the amount of the corresponding item in the iterable.
Most common with pattern matching
During last year’s Advent of Code, one puzzle had us matching poker hands.
Toni Fadjukoff used
a really cool combination of Counter
,
most_common
and Python’s pattern
matching:
def hand_score(hand):
cards, _ = hand
scores = [score(c) for c in cards]
cnt = Counter(cards)
match cnt.most_common():
case [(_, 5)]: return (Hand.FIVE_OF_A_KIND, scores)
case [(_, 4), _]: return (Hand.FOUR_OF_A_KIND, scores)
case [(_, 3), (_, 2)]: return (Hand.FULL_HOUSE, scores)
case [(_, 3), _, _]: return (Hand.THREE_OF_A_KIND, scores)
case [(_, 2), (_, 2), _]: return (Hand.TWO_PAIR, scores)
case [[_, 2], _, _, _]: return (Hand.ONE_PAIR, scores)
case [_, _, _, _, _]: return (Hand.HIGH_CARD, scores)
First, the score
function is run on each
card to get its numeric value. Then, all the scores are put into a Counter.
Counter.most_common()
returns a list of
tuples with the object and its count. Toni uses this format effectively for
matching for specific poker hands.
It’s one of my favourite examples of both Counter and pattern matching I’ve seen in Python.
A small handful of helpful methods
Compared to a regular dictionary, Counter adds a few helpful methods on top.
elements()
method returns a list of the
original elements, each item repeated as many times they appeared in the
original. The order is not preserved though so it’s not a copy of input but
rather a new generated list.
most_common([n])
sorts the values by
their counts and returns n
most common
items or all of them if n
is
None
.
total()
sums up the amount of items in
the Counter and returns the total sum.
These, combined with everything dictionaries can do makes Counters very handy addition to your toolbox.
While I was writing this, I learned an interesting tidbit from the docs:
>>> c = Counter({'red': 0, 'blue': 2, 'green': -1})
>>> c
Counter({'blue': 2, 'red': 0, 'green': -1})
>>> +c
Counter({'blue': 2})
If you run +c
(where
c
is a Counter), it clears up all the
items with count values of zero or negative.
It’s the kind of thing that I might never actually add into a code base or if I’d need something like that, I’d wrap it into a named function to make it clearer what it does.