Функции groupby() и starmap() модуля itertools

Курс по стандартной библиотеке: https://stepik.org/a/259466?utm_source=proproprogs

Смотреть материал на YouTube | RuTube

Это занятие мы с вами начнем с рассмотрения функции groupby() модуля itertools, которая позволяет группировать элементы коллекций по определенным ключам. Синтаксис этой функции следующий:

itertools.groupby(iterable, key=None)

Она возвращает итератор, который, в свою очередь, выдает кортежи формата:

(ключ, группа)

Рассмотрим ее работу на конкретных примерах. Пусть имеется последовательность из оценок учащегося:

marks = (2, 2, 2, 3, 5, 4, 4, 3)

Требуется сгруппировать их по оценкам. Для этого вначале последовательность обязательно нужно упорядочить по ключам. Это требование использования функции groupby(). В нашем случае ключи – это сами оценки, поэтому просто отсортируем этот список, получим:

s_marks = sorted(marks)

После этого список s_marks укажем в функции groupby():

it_res = groupby(s_marks)
pprint.pprint(list(it_res))

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

[(2, <itertools._grouper object at 0x000001C33225B580>),
 (3, <itertools._grouper object at 0x000001C332791D80>),
 (4, <itertools._grouper object at 0x000001C332793490>),
 (5, <itertools._grouper object at 0x000001C332793430>)]

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

for k, g in groupby(s_marks):
    print(k, list(g))

Получим:

2 [2, 2, 2]
3 [3, 3]
4 [4, 4]
5 [5]

Действительно, у нас три двойки, две тройки и четверки, и одна пятерка.

Давайте усложним пример и сгруппируем слова по первым буквам:

names = ["Антон", "Алексей", "Николай", "Никита", "Андрей", 
            "Сергей", "Афанасий", "Степан"
           ]

Вначале не забывает выполнить сортировку этого списка по ключу. Так как ключ – первая буква слова, то алгоритм сортировки будет выглядеть следующим образом:

names.sort(key=lambda x: x[0]) # ['Антон', 'Алексей', 'Андрей', 'Афанасий', 'Николай', 'Никита', 'Сергей', 'Степан']

После этого группируем слова по первой букве:

for k, g in groupby(names, key=lambda x: x[0]):
    print(k, list(g))

Получим:

А ['Антон', 'Алексей', 'Андрей', 'Афанасий']
Н ['Николай', 'Никита']
С ['Сергей', 'Степан']

Обратите внимание, что ключ определяется с помощью лямбда-функции в параметре key функции groupby(). Если ее не указывать, то ключами будут выступать слова целиком и результат станет совсем иным – не то, что нам требовалось. Поэтому наличие лямбда-функции здесь строго обязательно.

Давайте рассмотрим еще один пример группировки. Пусть у нас имеется список из словарей с информацией о поездках:

trips = [
    {'name': 'Сергей', 'city': 'Москва', 'duration': 10},
    {'name': 'Федор', 'city': 'Сочи', 'duration': 7},
    {'name': 'Кирилл', 'city': 'Сочи', 'duration': 10},
    {'name': 'Алексей', 'city': 'Питер', 'duration': 7},
    {'name': 'Елена', 'city': 'Москва', 'duration': 10},
]

Требуется сгруппировать эти данные по городам пребывания. Сделать это можно следующим образом:

s_trips = sorted(trips, key=lambda x: x['city'])
for k, g in groupby(s_trips, key=lambda x: x['city']):
    print(k, list(g))

Получим результат:

Москва [{'name': 'Сергей', 'city': 'Москва', 'duration': 10}, {'name': 'Елена', 'city': 'Москва', 'duration': 10}]
Питер [{'name': 'Алексей', 'city': 'Питер', 'duration': 7}]
Сочи [{'name': 'Федор', 'city': 'Сочи', 'duration': 7}, {'name': 'Кирилл', 'city': 'Сочи', 'duration': 10}]

В качестве домашнего задания выполните группировку данных списка trips по времени пребывания (полю duration).

Функция starmap()

Следующая весьма полезная функция starmap() модуля itertools работает по аналогии с уже знакомой нам функцией map() из базового курса по языку Python. Напомню, что функция map() позволяет к элементам коллекций применять заданную функцию. Очень частый пример – это ввод значений с клавиатуры и преобразования их к заданному типу:

digits_it = map(int, input().split())  # итератор для перебора введенных данных
digits = tuple(digits_it)  # кортеж из введенных чисел

Но функцию map() можно применить и для обработки сразу нескольких коллекций. Например:

data_x = [5, 3, 2]
data_y = [4, 8, 7]
 
sum_it = map(lambda x, y: x + y, data_x, data_y)
sum_data = tuple(sum_it)  # (9, 11, 9)

Получим результаты суммирования соответствующих величин из первого и второго списка.

Функция starmap() в целом работает по аналогичному принципу, только ей можно передавать всего одну итерируемую коллекцию. Чтобы повторить последний пример, воспользуемся следующим списком:

data = [(5, 4), (3, 8), (2, 7)]

и суммирование элементов кортежей можно реализовать так:

sum_it = starmap(lambda x, y: x + y, data)
sum_data = tuple(sum_it)  # (9, 11, 9)

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

Конечно, вместо отдельного описания списка data мы могли бы воспользоваться исходными списками data_x, data_y:

sum_it = starmap(lambda x, y: x + y, zip(data_x, data_y))

Результат будет аналогичный.

Если же попытаться в starmap() указать итерируемый объект без возможности распаковки его элементов, например:

sum_it = starmap(lambda x: x, data_x)

то возникнет ошибка. В отличие от функции map(), которая отработает здесь без ошибок.

Курс по стандартной библиотеке: https://stepik.org/a/259466?utm_source=proproprogs

Видео по теме