Эволюция подсчета в Python

в 5:36, , рубрики: python, Питон, Программирование, словарь

У одной задачи может быть несколько способов решения. Возьмем классическую задачу программирования — задачу подсчета, в которой мы считаем, сколько раз каждый элемент списка встречается в нем. Способ решения этой задачи на Python менялся по мере развития языка. Именно об этом мы будем говорить в этой статье.

Большинство из нас присоединилось к программированию на Python с его третьей версии. Однако мы начнем с Python 1.4. Пристегните ремни, отправляемся в далекое прошлое — в 1997 год!

Эволюция подсчета в Python - 1

Python 1.4

Мало кто помнит эти времена. Это даже не Python 2. Тем не менее уже в этой версии языка были словари — тип данных dict.

Приведенный ниже код:

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

выводит (порядок может отличаться):

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

В приведенном выше коде мы итерируем список letters и проверяем каждый элемент на наличие его в словаре letter_counts. В случае отсутствия мы его туда добавляем, а в случае присутствия просто увеличиваем на единицу его счетчик.

Обратите внимание, что в Python 1.4 вместо использования привычного нам оператора принадлежности in (он был добавлен только в Python 2.2) мы используем словарный метод has_key() (которого сейчас у словарей, конечно, уже нет). Вместо оператора составного присваивания += (он был добавлен только в Python 2.0) мы используем обычный оператор сложения +. Ну и наконец, в данной версии языка print — это оператор, а не функция, поэтому мы не используем круглые скобки вокруг letter_counts.

Приведенный выше код следует идеологии LBYL-подхода (Look Before You Leap) — "посмотри перед прыжком": сначала мы проверяем наличие элемента в словаре, а уже после совершаем действия.

Можно переписать приведенную выше программу с использованием конструкции try-except, которая уже имелась в Python 1.4.

Приведенный ниже код:

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

выводит (порядок может отличаться):

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

Теперь наш код пытается увеличить счетчик для каждого элемента списка, не проверяя явно его наличие в словаре. Если элемент отсутствует в словаре, то будет возбуждено исключение KeyError, которое затем будет перехвачено.

Теперь приведенный выше код следует идеологии EAFP-подхода (Easier to Ask Forgiveness than Permission) — "проще просить прощения, чем разрешения". Она, кстати, считается более Pythonic. Напомним, Pythonic — стиль кода, который соответствует идиомам Python, он читабельный и понятный.

Python 1.5

В Python 1.5 словарям был добавлен метод get(key, default=None), который возвращает значение словаря, соответствующее ключу key, если ключ находится в словаре. Если ключ отсутствует, то метод возвращает значение по умолчаниюdefault.

Приведенный ниже код:

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

выводит (порядок может отличаться):

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

В приведенном выше коде мы итерируем список letters и с помощью метода get() получаем из словаря letter_counts текущее количество рассматриваемого элемента списка, увеличиваем его на единицу и записываем обратно в словарь увеличенное значение. Если в словаре еще нет ключа для некоторого элемента, то используется значение по умолчанию, равное 0.

Python 2.0

В Python 2.0 словарям был добавлен метод setdefault(key, default=None). Данный метод возвращает значение словаря, соответствующее ключу key, при этом:

  • если указанный ключ key отсутствует в словаре, метод вставит его в словарь со значением default и вернет значение default

  • если значение по умолчанию default не установлено и ключ отсутствует, метод вставит ключ в словарь со значением None и вернет значение None

В Python 2.0 также стал доступен оператор составного присваивания +=. Перепишем наш код, используя новые возможности.

Приведенный ниже код:

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

выводит (порядок может отличаться):

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

К минусам такого кода можно отнести тот факт, что метод setdefault() вызывается на каждой итерации вне зависимости от того, нужен он или нет.

Python 2.3

В Python 2.3 словарям был добавлен метод fromkeys(iterable, value=None), который формирует словарь с ключами из указанной последовательности iterable и значениями, установленными в value.

Приведенный ниже код:

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

выводит (порядок может отличаться):

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

Здесь создается словарь, который уже содержит все необходимые ключи, поэтому нам не нужно беспокоиться об их добавлении, избегая лишних проверок, что является огромным плюсом.

Python 2.5

С выходом Python 2.5 в стандартную библиотеку языка был добавлен класс defaultdict, который доступен из модуля collections. Этот класс похож на обычный словарь за тем исключением, что он может использовать произвольный вызываемый объект, который возвращает значение по умолчанию для его ключей в случае их отсутствия.

Приведенный ниже код:

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

выводит (порядок может отличаться):

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

В нашем коде вызываемым объектом является класс int, который возвращает значение 0 в качестве значения по умолчанию для отсутствующих ключей словаря.

Python 2.7

С выходом Python 2.7 в стандартную библиотеку языка был добавлен класс Counter, который так же, как и defaultdict, доступен из модуля collections. Класс Counter — это специальный вид словаря, который заточен под подсчет.

Приведенный ниже код:

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

выводит (порядок может отличаться):

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

Использование класса Counter является, пожалуй, самым Pythonic способом: проще придумать уже невозможно, да и не нужно!

Отметим, что классы defaultdict и Counter являются подклассами обычных словарей, то есть типа dict.

Приведенный ниже код:

from collections import defaultdict, Counter

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

выводит:

True
True

Python 3.x

В современном Python (версия 3.x) самым Pythonic способом решить задачу подсчета является рассмотренный выше класс Counter.

Приведенный ниже код:

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)

выводит:

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

В последних версиях языка класс Counter стал намного быстрее, его оптимизировали специально под задачу подсчета. Исходный код класса Counter доступен по ссылке.

Заключение

Согласно философии Python "должен существовать один и желательно только один очевидный способ сделать что-то". Но не всегда очевидное бывает единственным, а единственное — очевидным. Очевидный способ может меняться в зависимости от времени, необходимости, возможностей языка и наших знаний.

P.S. Словари в Python занимают очень важное место. Во-первых, они очень активно используются самим языком "под капотом". Во-вторых, их активно применяют программисты, пишущие на Python. В последних версиях языка словари сильно оптимизировали: они стали быстрее, потребляют меньше памяти, а еще стали упорядоченными.

Присоединяйтесь к нашему телеграм-каналу, будет интересно и познавательно!

❤️Happy Pythoning!🐍

Автор: Тимур Гуев

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js