Menu Search Icon

The evolution of counting in Python

The evolution of counting in Python

One problem can have several solutions. Let's take a class ic programming problem - counting problem , in which we count how many times each element of the list appears in it. The way Python solves this problem has changed as the language has evolved. This is exactly what we will talk about in this article.

Most of us joined Python programming from its third version. However, we will start with Python 1.4. Fasten your seat belts, let's go back in time - to 1997!

Few people remember these times. This is not even Python 2. Nevertheless, already in this version of the language there were dictionaries - a data type dict .

Below code :

letters = ['a', 'b', 'c', 'a', 'c', 'a', 't', 'p', 'b', 'a', 'y', 't', 'c']
letter_counts = {}

for char in letters:
    if letter_counts.has_key(char):
        letter_counts[char] = letter_counts[char] + 1
    else:
        letter_counts[char] = 1
        
print letter_counts

outputs (order may vary ):

{'a': 4, 'b': 2, 'c': 3, 't': 2, 'p': 1, 'y': 1}

In the above code we are iterating the list letters and check each element for its presence in the dictionary letter_counts . If it is absent, we add it there, and if it is present, we simply increase its counter by one.

Note that in Python 1.4, instead of using the familiar membership operator in (it was only added in Python 2.2) we use the dictionary method has_key() (which dictionaries no longer have, of course). Instead of a compound assignment operator += (it was only added in Python 2.0) we use the regular addition operator + . And finally, in this version of the language print is an operator, not a function, so we don't use parentheses around letter_counts .

The above code follows the ideology of the LBYL approach (Look Before You Leap) - " look before you jump ": first we check for the presence of an element in the dictionary, and only then we perform actions.

It is possible to rewrite the above program using the construct try-except , which was already available in Python 1.4.

Below code:

letters = ['a', 'b', 'c', 'a', 'c', 'a', 't', 'p', 'b', 'a', 'y', 't', 'c']
letter_counts = {}

for char in letters:
    try:
        letter_counts[char] = letter_counts[char] + 1
    except KeyError:
        letter_counts[char] = 1

print letter_counts

outputs (order may vary):

{'a': 4, 'b': 2, 'c': 3, 't': 2, 'p': 1, 'y': 1}

Our code now tries to increment the counter for each list item without explicitly checking to see if it's in the dictionary. If the element is not in the dictionary, an exception will be raised KeyError , which will then be intercepted.

Now the above code follows the ideology of the EAFP approach (Easier to Ask Forgiveness than Permission) - " It's easier to ask for forgiveness than permission ". By the way, it is considered more Pythonic. Let us remind you that Pythonic - a code style that follows Python idioms and is readable and understandable.

Python 1.5

In Python 1.5, a method was added to dictionaries get(key, default =None) which returns the dictionary value corresponding to the key key if the key is in the dictionary. If the key is missing, the method returns the default value default .

Below code:

letters = ['a', 'b', 'c', 'a', 'c', 'a', 't', 'p', 'b', 'a', 'y', 't', 'c']
letter_counts = {}

for char in letters:
    letter_counts[char] = letter_counts.get(char, 0) + 1

print letter_counts

outputs (order may vary):

{'a': 4, 'b': 2, 'c': 3, 't': 2, 'p': 1, 'y': 1}

In the above code we are iterating the list letters and using the method get() we get from the dictionary letter_counts the current quantity of the list element under consideration, increase it by one and write the increased value back to the dictionary. If the dictionary does not yet have a key for some element, then the default value is used, equal to 0 .

Python 2.0

In Python 2.0, a method was added to dictionaries setdefault(key, default=None) . This method returns the dictionary value corresponding to the key key , wherein:

if the specified key key is not in the dictionary, the method will insert it into the dictionary with the value default and return the value default

if default value default is not installed and the key is missing, the method will insert the key into the dictionary with the value None and return the value None

Python 2.0 also made the compound assignment operator available. += . Let's rewrite our code using new features.

Below code:

letters = ['a', 'b', 'c', 'a', 'c', 'a', 't', 'p', 'b', 'a', 'y', 't', 'c']
letter_counts = {}

for char in letters:
    letter_counts.setdefault(char, 0)
    letter_counts[char] += 1

print letter_counts

outputs (order may vary):

{'a': 4, 'b': 2, 'c': 3, 't': 2, 'p': 1, 'y': 1}

The disadvantages of such code include the fact that the method setdefault() is called at every iteration, regardless of whether it is needed or not.

Python 2.3

In Python 2.3, a method was added to dictionaries fromkeys(iterable, value=None) , which forms a dictionary with keys from the specified sequence iterable and the values ​​set in value .

Below code:

letters = ['a', 'b', 'c', 'a', 'c', 'a', 't', 'p', 'b', 'a', 'y', 't', 'c']
letter_counts = dict.fromkeys(letters, 0)

for char in letters:
    letter_counts[char] += 1

print letter_counts

outputs (order may vary):

{'a': 4, 'b': 2, 'c': 3, 't': 2, 'p': 1, 'y': 1}

This creates a dictionary that already contains all the necessary keys, so we don't have to worry about adding them, avoiding unnecessary checks, which is a huge plus.

Python 2.5

With the release of Python 2.5, the class was added to the standard library of the language defaultdict , which is accessible from the module collections . This class is similar to a regular dictionary except that it can use an arbitrary callable object that returns a default value for its keys if they are missing.

Below code:

from collections import defaultdict

letters = ['a', 'b', 'c', 'a', 'c', 'a', 't', 'p', 'b', 'a', 'y', 't', 'c']
letter_counts = defaultdict(int)

for char in letters:
    letter_counts[char] += 1

print letter_counts

outputs (order may vary):

defaultdict(<class 'int'>, {'a': 4, 'b': 2, 'c': 3, 't': 2, 'p': 1, 'y': 1})

In our code, the callable object is the class int which returns a value 0 as the default value for missing dictionary keys.

Python 2.7

With the release of Python 2.7, the class was added to the standard library of the language Counter , which is the same as defaultdict , accessible from the module collections . Class Counter is a special type of dictionary that is designed for counting.

Below code:

from collections import Counter

letters = ['a', 'b', 'c', 'a', 'c', 'a', 't', 'p', 'b', 'a', 'y', 't', 'c']
letter_counts = Counter(letters)

print letter_counts

outputs (order may vary):

Counter({'a': 4, 'c': 3, 'b': 2, 't': 2, 'p': 1, 'y': 1})

Using the class Counter is, perhaps, the most Pythonic way: it’s impossible to come up with anything simpler, and there’s no need!

Note that classes defaultdict And Counter are subclasses of ordinary dictionaries, that is, type dict .

Below code:

from collections import defaultdict, Counter

print(issubclass(defaultdict, dict))
print(issubclass(Counter, dict))

outputs:

True
True

Python 3.x

In modern Python (version 3.x), the most Pythonic way to solve the counting problem is the class discussed above Counter .

Below code:

from collections import Counter

letters = ['a', 'b', 'c', 'a', 'c', 'a', 't', 'p', 'b', 'a', 'y', 't', 'c']
letter_counts = Counter(letters)

print(letter_counts)

outputs:

Counter({'a': 4, 'c': 3, 'b': 2, 't': 2, 'p': 1, 'y': 1})

In the latest versions of the language, the class Counter became much faster, it was optimized specifically for the counting task. Class source code Counter available at link .

Conclusion

Python's philosophy is that "there should be one, and preferably only one, obvious way to do something." But the obvious is not always the only thing, and the only thing is not always obvious. The obvious way may change depending on time, need, language capabilities and our knowledge.

P.S. Dictionaries play a very important role in Python. Firstly, they are very actively used by the language itself “under the hood”. Secondly, they are actively used by programmers writing in Python. In the latest versions of the language, dictionaries have been greatly optimized: they have become faster, consume less memory, and have also become organized.

Join our telegram channel , it will be interesting and informative!

❤️Happy Pythoning!🐍