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!🐍