Skip to content

Counter

Counter is implemented in the module collections in python. It is an interesting class, and the source codes are quite worth reading (because it is simple and fun).

Poor-man Counter

Well, what if we don't have Counter? How can we implement a poor-man version counter? Let's find out.

A Counter that can Count

At least, we need a counter to give the counts of an existing list of values.

class UnderneathAllCounter(dict):

    def __init__(self, iterable):
        super().__init__()
        for v in iterable:
            self[v] += 1

    def __missing__(self, key):
        return 0
c = UnderneathAllCounter('abcdefgabc')
c

Output

{'a': 2, 'b': 2, 'c': 2, 'd': 1, 'e': 1, 'f': 1, 'g': 1}

What have we done?

The codes are not hard. We inherit the dict class, and upon initializing, we count the occurence of each value.

__missing__ method has been explained in our Default Dict tutorial.

Looks not bad?

Example

However, one-time counting is not good enough. Given a case, we need to count the occurence of letters in more than one stage, we'll need an update function.

c = UnderneathAllCounter('abcdefgabc')
c.update('asdfjkl')
c

Output

{'a': 3, 'b': 2, 'c': 2, 'd': 2, 'f': 2, 'e': 1, 'g': 1, 's': 1, 'j': 1, 'k': 1, 'l': 1}

class UnderneathAllCounter(dict):

    def __init__(self, iterable):
        super().__init__()
        self.update(iterable)

    def __missing__(self, key):
        return 0

    def update(self, iterable):
        for v in iterable:
            self[v] += 1
c = UnderneathAllCounter('abcdefgabc')
c.update('asdfjkl')
c

Output

{'a': 3, 'b': 2, 'c': 2, 'd': 2, 'f': 2, 'e': 1, 'g': 1, 's': 1, 'j': 1, 'k': 1, 'l': 1}

Counter

Let's have a look at some source codes of collections.Counter.

Unlike defaultdict, it is mainly written in Python

Info

I only paste part of the codes. Some of the methods like comparing (less than, greater than, etc), most common, and others are excluded.

As you can tell, most of the codes are similar. However, in the update method, Counter also deal dict like objects with a different approach.

def _count_elements(mapping, iterable):
    'Tally elements from the iterable.'
    mapping_get = mapping.get
    for elem in iterable:
        mapping[elem] = mapping_get(elem, 0) + 1

class Counter(dict):

    def __init__(self, iterable=None, /, **kwds):
        '''Create a new, empty Counter object.  And if given, count elements
        from an input iterable.  Or, initialize the count from another mapping
        of elements to their counts.

        >>> c = Counter()                           # a new, empty counter
        >>> c = Counter('gallahad')                 # a new counter from an iterable
        >>> c = Counter({'a': 4, 'b': 2})           # a new counter from a mapping
        >>> c = Counter(a=4, b=2)                   # a new counter from keyword args

        '''
        super().__init__()
        self.update(iterable, **kwds)

    def __missing__(self, key):
        'The count of elements not in the Counter is zero.'
        # Needed so that self[missing_item] does not raise KeyError
        return 0

    def update(self, iterable=None, /, **kwds):
        '''Like dict.update() but add counts instead of replacing them.

        Source can be an iterable, a dictionary, or another Counter instance.

        >>> c = Counter('which')
        >>> c.update('witch')           # add elements from another iterable
        >>> d = Counter('watch')
        >>> c.update(d)                 # add elements from another counter
        >>> c['h']                      # four 'h' in which, witch, and watch
        4

        '''
        if iterable is not None:
            if isinstance(iterable, _collections_abc.Mapping):
                if self:
                    self_get = self.get
                    for elem, count in iterable.items():
                        self[elem] = count + self_get(elem, 0)
                else:
                    # fast path when counter is empty
                    super().update(iterable)
            else:
                _count_elements(self, iterable)
        if kwds:
            self.update(kwds)