Python counters on unhashable types

Have you ever heard or used python counters? They are very useful to count the number of occurrences of “simple” items. Basically:

> from collections import Counter
> colors = ['red', 'blue', 'red', 'green']
> Counter(colors)
Counter({'red': 2, 'blue': 1, 'green': 1})

However, if you try to use it on non hashable types it doesn’t work.

> colors = [['red', 'warm'], ['blue', 'cold'], ['red', 'warm']]
> Counter(colors)
[...]
TypeError: unhashable type: 'list'

What do we do then?

Once you know the trick, it’s quite simple. You build an object that will hold your data and you define __hash__ and __eq__.

class StringList(object): 

 def __init__(self, val):
    self.val = val 

 def __hash__(self):
    return hash(str(self.val)) 

 def __repr__(self):
    # Bonus: define this method to get clean output
    return str(self.val) 

 def __eq__(self, other):
    return str(self.val) == str(other.val)

It’s a bit more overhead, but it makes it work.


> colors = [StringList(['red', 'warm']),
.           StringList(['blue', 'cold']),
.           StringList(['red', 'warm'])]
> print(Counter(colors))
Counter({['red', 'warm']: 2, ['blue', 'cold']: 1})

You can also easily iterate over the items.

> for k, v in Counter(colors).items():
.     print(k, v)
['blue', 'cold'] 1
['red', 'warm'] 2

As a side note, in this example the order of the items within StringList objects is important.

Thanks to Simon Lemieux for helping me out with that.

EDIT

That particular example could have work by simply using tuples instead.

> colors = [('red', 'warm'), ('blue', 'cold'), ('red', 'warm')]
> Counter(colors)
Counter({('red', 'warm'): 2, ('blue', 'cold'): 1})

Leave a Reply