Juha-Matti Santala
Community Builder. Dreamer. Adventurer.

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.