Определение частотности элементов в Python 3

Определение частотности элементов при помощи Python

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

Определим для себя, что мы будем анализировать частнотность букв английского алфавита (без учёта знаков препинания и пробелов) в каком-либо англоязычном тексте. Следовательно, наше техническое задание будет включать некоторую подготовительную работу (очистка текста и подготовка его к анализу), собственно подсчет частотности и заключительную часть (вывод информации в удобочитаемом для пользователя виде).

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

  1. Получение текста от пользователя.
  2. Очистка текста (вынесем её в отдельную функцию).
  3. Анализ частотности различными способами.
  4. Вывод информации о частоте встречаемости тех или иных букв английского алфавита (также можно реализовать в виде отдельной функции).

Логика программы анализа частотности

Весь алгоритм программы можно поместить в функцию main():

Скриншот функции main()

Для начала нам вполне достаточно такого кода основной функции.

Понятно, чем является переменная text: это неизменяемый объект класса str. А вот как будет выглядеть переменная letters? Логичнее всего представить её в виде списка букв английского алфавита, причём в том порядке и количестве, в каком эти буквы будут расположены в оригинальном тексте. Таким образом, строка 'Hello, World 2021!' станет у нас списком ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd']. Легко заметить, что буква l попадается в списке 3 раза, буква o — 2 раза, а остальные буквы (h, e, w, r и d) — только по одному разу каждая. Такую вот статистику и будет собирать наша программа.

А вы заметили, что в списке приведены только строчные английские буквы? Нам не важен регистр букв в исходном тексте; поэтому заглавные (прописные) буквы на этапе очистки текста будут преобразованы в строчные.

Очистка текста и преобразование его в список букв

Итак, текст приходит к нам в необработанном виде и подвергается очистке от знаков препинания. На этапе очистки мы также преобразуем все заглавные буквы в строчные, т.е. приведём весь текст к нижнему регистру.

Можно, конечно, создать список всех возможных знаков препинания, но проще всего воспользоваться готовым решением — константой punctuation, которые содержатся в стандартном модуле string:

Скриншот string.punctuation

и получаем !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~.

Кстати, в тексте могут попадаться и цифры — их мы тоже не будем считать. Для удаления знаков пунктуации и цифр воспользуемся регулярными выражениями, предоставляемыми модулем re из стандартной библиотеки.

Теперь можно написать последовательность команд для очистки текста. Мы приводим текст к нижнему регистру, удаляем знаки пунктуации и цифры, затем разбиваем текст на слова (простейший способ убрать пробелы), соединяем слова и делаем из очищенного текста список букв:

Скриншот кода очистки текста

Запустив данный код с тестовой строкой и добавив команды вывода после каждой операции очистки, мы увидим следующее:

text: Hello, World 2021!
hello, world
['hello,', 'world']
letters: ['h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd']

Всё работает! Давайте поместим код в функцию clear_text():

Скриншот функции clear_text()

Вывод итоговой статистики

Прежде чем перейти собственно к коду, анализирующему статистику использования букв в тексте, давайте подумаем, а в каком виде мы должны получить эту информацию? И здесь сразу напрашивается такая структура данных, как словарь. Буквы станут у нас уникальными ключами, а счётчики их использований — значениями словаря, например для фразы 'Hello, World 2021!' словарь будет выглядеть так:

{'h': 1, 'e': 1, 'l': 3, 'o': 2, 'w': 1, 'r': 1, 'd': 1}

Отлично, а теперь давайте напишем код, который будет печатать словарь в удобном для восприятия виде. Для этого воспользуемся методом .items(), который возвращает ключи и связанные с ними значения, примерно так (сразу оформим код в функцию):

Скриншот функции print_results()

Анализ частотности методом грубой силы

Вот теперь пришло время заняться ядром нашей программы — кодом, который и будет вести статистику использования букв в англоязычном тексте. Данную задачу можно решить многими способами, но для начала попробуем применить метод грубой силы, т.е. самый простой (примитивный) способ. Начнём перебирать словарь букв, каждую букву будем добавлять в новый словарь в виде ключа (если её там нет, присвоим её счётчику значение 1; если есть — увеличим уже имеющееся значение на 1):

Скриншот кода метода грубой силы

Если мы запустим список букв фразы 'Hello, World 2021!' и включим тестовый вывод, то увидим, как фомируется наш словарь:

{'h': 1}
{'h': 1, 'e': 1}
{'h': 1, 'e': 1, 'l': 1}
{'h': 1, 'e': 1, 'l': 2}
{'h': 1, 'e': 1, 'l': 2, 'o': 1}
{'h': 1, 'e': 1, 'l': 2, 'o': 1, 'w': 1}
{'h': 1, 'e': 1, 'l': 2, 'o': 2, 'w': 1}
{'h': 1, 'e': 1, 'l': 2, 'o': 2, 'w': 1, 'r': 1}
{'h': 1, 'e': 1, 'l': 3, 'o': 2, 'w': 1, 'r': 1}
{'h': 1, 'e': 1, 'l': 3, 'o': 2, 'w': 1, 'r': 1, 'd': 1}

Всё работает, соберём программу и проведём некоторый её рефакторинг для лучшего отображения данных:

Скриншот программы с методом грубой силы
Enter a text to analyze: Hello, World 2021!
Frequency of used letters:
h:   1 time(s)
e:   1 time(s)
l:   3 time(s)
o:   2 time(s)
w:   1 time(s)
r:   1 time(s)
d:   1 time(s)

Файл с кодом данной версии программы: bruteforce.py.

Питонический способ подсчёта частотности

Мы решили задачу методом грубой силы, но существуют более изящные (питонические) варианты, и вот — самый известный из них:

Скриншот питонического кода

Как работает этот код: если ключа нет в словаре (т.е. буква ещё не добавлена в новый словарь), метод .get() возвращает значение, опредённое пользователем (как будто у нас уже было значение 0) и увеличивает его на единицу; если же ключ есть — просто увеличивает существующее значение.

Полный текст программы с данным вариантом кода:

Скриншот программы с питоническим методом

Выход программы идентичен:

Enter a text to analyze: Hello, World 2021!
Frequency of used letters:
h:   1 time(s)
e:   1 time(s)
l:   3 time(s)
o:   2 time(s)
w:   1 time(s)
r:   1 time(s)
d:   1 time(s)

Файл с кодом данной версии программы: pythonic.py.

В принципе, на этом варианте можно было бы остановиться, вспомнив принцип из "Дзен Python", что должен существовать один (и желательно только один) способ решить какую-либо задачу. Но, раз мы не голландцы, в отличие от создателя языка Гвидо ван Россума, то далее рассмотрим ещё несколько не менее эффектных вариантов.

Используем метод .count()

Метод .count() проводит поиск значения по списку и возвращает количество вхождений значения в список. Воспользовавшись воззможностями генерирования словарей, получим нужный нам словарь "на лету":

Скриншот использования .count()

Очень изящно, не так ли? Соберём новый вариант программы:

Скриншот программы с методом .count()

Выход программы также идентичен:

Enter a text to analyze: Hello, World 2021!
Frequency of used letters:
h:   1 time(s)
e:   1 time(s)
l:   3 time(s)
o:   2 time(s)
w:   1 time(s)
r:   1 time(s)
d:   1 time(s)

Файл с кодом данной версии программы: count.py.

Определяем частотность при помощи defaultdict

Есть обычные словари, а есть defaultdict из модуля collections. Обычный словарь, если обратиться к несуществующему ключу, инициирует исключение KeyError; напротив, defaultdict создаст недостающий элемент. Элементы нового словаря по умолчанию создаются с помощью int(), возвращающего целочисленный объект 0:

Скриншот кода с collections.defaultdict

Похоже на приведённый выше питонический способ, но с использованием встроенных батареек! И теперь наша программа выглядит так:

Скриншот программы с collections.defaultdict

Всё тот же вывод программы:

Enter a text to analyze: Hello, World 2021!
Frequency of used letters:
h:   1 time(s)
e:   1 time(s)
l:   3 time(s)
o:   2 time(s)
w:   1 time(s)
r:   1 time(s)
d:   1 time(s)

Файл с кодом данной версии программы: defaultdict.py.

Counter — универсальный счётчик

Наконец, последний из рассмотренных нами способов использует другие батарейки (на этот раз не обычные, а алкалиновые) — Counter из того же модуля collections. Это самое простое в использовании решение, поскольку Counter сделает за вас всю работу:

Скриншот кода с collections.Counter

Теперь наша программа стала такой:

Скриншот программы с collections.Counter

Вывод программы:

Enter a text to analyze: Hello, World 2021!
Frequency of used letters:
h:   1 time(s)
e:   1 time(s)
l:   3 time(s)
o:   2 time(s)
w:   1 time(s)
r:   1 time(s)
d:   1 time(s)

Файл с кодом данной версии программы: counter.py.

Но Сounter вообще многое умеет делать. Например, он работает со строками (и мы могли бы не разбивать строку на отдельные буквы, а скормить её данному счётчику целиком); проводить арифметические операции с элементами; складывать, вычитать, объединять различные счётчики; показывать наиболее встречаемые элементы с помощью метода .most_common([n]), где n — количество топовых элементов. Например, если мы ходим вывести две самых встречаемых буквы:

Скриншот вывода Counter с методом .most_common()

Добавим такой вывод в нашу программу:

Скриншот программы с collections.Counter.most_common()

Вывод программы следующий:

Enter a text to analyze: Hello, World 2021!
Frequency of used letters:
h:   1 time(s)
e:   1 time(s)
l:   3 time(s)
o:   2 time(s)
w:   1 time(s)
r:   1 time(s)
d:   1 time(s)
Two most common letters:
l:   3 time(s)
o:   2 time(s)

Файл с кодом усовершенствованной версии программы: counter_mostcommon.py.

Вот и всё, что я хотел рассказать вам о способах подсчёта частотности.