Структура данных перехода, используемая для всех видов группировки, - dict . Идея состоит в том, чтобы использовать что-то, что однозначно идентифицирует группу как ключи dict, и хранить все значения, принадлежащие одной и той же группе, под одним и тем же ключом.
Например, ваши данные могут быть сохранены в таком виде:
{1: ['a', 'b'],
2: ['x']}
Целое число, которое вы используете для группировки значений, используется в качестве ключа dict, а значения агрегируются в список.
Причина, по которой мы используем dict, заключается в том, что он может отображать ключи на значения за постоянное время O (1). Это делает процесс группировки очень эффективным, а также очень простым. Общая структура кода всегда будет одинаковой для всех видов задач группирования: вы перебираете свои данные и постепенно заполняете данные сгруппированными значениями. Использование defaultdict
вместо обычного dict делает весь процесс еще проще, потому что нам не нужно беспокоиться об инициализации dict с пустыми списками.
import collections
groupdict = collections.defaultdict(list)
for value in data:
group = value[0]
value = value[1]
groupdict[group].append(value)
# result:
# {1: ['a', 'b'],
# 2: ['x']}
Как только данные сгруппированы, остается только преобразовать dict в желаемый формат вывода:
result = [(key, ''.join(values)) for key, values in groupdict.items()]
# result: [(1, 'ab'), (2, 'x')]
Рецепт группировки
В следующем разделе будут представлены рецепты для различных видов входов и выходов, а также показано, как группировать по разным вещам. Основой для всего является следующий фрагмент:
import collections
groupdict = collections.defaultdict(list)
for value in data: # input
group = ??? # group identifier
value = ??? # value to add to the group
groupdict[group].append(value)
result = groupdict # output
Каждая из прокомментированных строк может / должна быть настроена в зависимости от вашего варианта использования.
Input
Формат ваших входных данных определяет способ итерации по ним.
В этом разделе мы настраиваем строку рецепта for value in data:
.
Список значений
Чаще всего все значения хранятся в плоском списке:
data = [value1, value2, value3, ...]
В этом случае мы просто перебираем список с циклом for
:
for value in data:
Несколько списков
Если у вас есть несколько списков, каждый из которых содержит значение другого атрибута, например
firstnames = [firstname1, firstname2, ...]
middlenames = [middlename1, middlename2, ...]
lastnames = [lastname1, lastname2, ...]
используйте функцию zip
для итерации по всем спискам одновременно:
for value in zip(firstnames, middlenames, lastnames):
Это сделает value
кортеж (firstname, middlename, lastname)
.
Несколько диктов или список диктов
Если вы хотите объединить несколько диктовок, таких как
dict1 = {'a': 1, 'b': 2}
dict2 = {'b': 5}
Сначала поместите их все в список:
dicts = [dict1, dict2]
И затем использовать два вложенных цикла для итерации по всем (key, value)
парам:
for dict_ in dicts:
for value in dict_.items():
В этом случае переменная value
примет форму двухэлементного кортежа, например ('a', 1)
или ('b', 2)
.
Группировка
Здесь мы рассмотрим различные способы извлечения идентификаторов групп из ваших данных.
В этом разделе мы настраиваем строку рецепта group = ???
.
Группировка по элементу list / tuple / dict
Если ваши значения представляют собой списки или кортежи, такие как (attr1, attr2, attr3, ...)
, и вы хотите сгруппировать их по n-му элементу:
group = value[n]
Синтаксис для диктов одинаков, поэтому если у вас есть такие значения, как {'firstname': 'foo', 'lastname': 'bar'}
, и вы хотите сгруппировать по имени:
group = value['firstname']
Группировка по атрибуту
Если ваши значения являются объектами типа datetime.date(2018, 5, 27)
и вы хотите сгруппировать их по атрибуту, например year
:
group = value.year
Группировка по ключевой функции
Иногда у вас есть функция, которая возвращает группу значения при его вызове. Например, вы можете использовать функцию len
для группировки значений по их длине:
group = len(value)
Группировка по нескольким значениям
Если вы хотите сгруппировать данные по более чем одному значению, вы можете использовать кортеж в качестве идентификатора группы. Например, чтобы сгруппировать строки по первой букве и по длине:
group = (value[0], len(value))
Группировка по чему-то неуловимому
Поскольку ключи dict должны быть хешируемыми , у вас возникнут проблемы, если вы попытаетесь сгруппировать что-то, что не может быть хешировано. В таком случае вам нужно найти способ преобразовать неискажаемое значение в хеш-представление.
наборы : преобразование наборов в frozensets , которые можно хэшировать:
group = frozenset(group)
dicts : Dicts могут быть представлены как отсортированные (key, value)
кортежи:
group = tuple(sorted(group.items()))
Изменение агрегированных значений
Иногда вам может понадобиться изменить значения, которые вы группируете. Например, если вы группируете кортежи, такие как (1, 'a')
и (1, 'b')
, по первому элементу, вы можете удалить первый элемент из каждого кортежа, чтобы получить результат, подобный {1: ['a', 'b']}
, а не {1: [(1, 'a'), (1, 'b')]}
.
В этом разделе мы настраиваем строку value = ???
рецепта.
Без изменений
Если вы не хотите изменять значение каким-либо образом, просто удалите строку value = ???
из своего кода.
Хранение только одного элемента list / tuple / dict
Если ваши значения представляют собой списки, такие как [1, 'a']
, и вы хотите сохранить только 'a'
:
value = value[1]
Или, если они диктуют, как {'firstname': 'foo', 'lastname': 'bar'}
, и вы хотите оставить только имя:
value = value['firstname']
Удаление первого элемента списка / кортежа
Если ваши значения являются списками, такими как [1, 'a', 'foo']
и [1, 'b', 'bar']
, и вы хотите отбросить первый элемент каждого кортежа, чтобы получить группу, подобную [['a', 'foo], ['b', 'bar']]
, используйте синтаксис срезов:
value = value[1:]
Удаление / Сохранение произвольных элементов списка / кортежа / dict
Если ваши значения являются списками, такими как ['foo', 'bar', 'baz']
, или диктантами, такими как {'firstname': 'foo', 'middlename': 'bar', 'lastname': 'baz'}
, и вы хотите удалить или оставить только некоторые из этих элементов, начните с создания набора элементов, которые вы хотите сохранить или удалить. Например:
indices_to_keep = {0, 2}
keys_to_delete = {'firstname', 'middlename'}
Затем выберите соответствующий фрагмент из этого списка:
- Для сохранения элементов списка:
value = [val for i, val in enumerate(value) if i in indices_to_keep]
- Чтобы удалить элементы списка:
value = [val for i, val in enumerate(value) if i not in indices_to_delete]
- Чтобы сохранить элементы dict:
value = {key: val for key, val in value.items() if key in keys_to_keep]
- Чтобы удалить элементы dict:
value = {key: val for key, val in value.items() if key not in keys_to_delete]
выход
Как только группировка завершена, у нас есть defaultdict
, заполненный списками. Но желаемый результат не всегда (по умолчанию) диктует.
В этом разделе мы настраиваем строку result = groupdict
рецепта.
Обычный дикт
Чтобы преобразовать defaultdict в обычный dict, просто вызовите для него конструктор dict
:
result = dict(groupdict)
Список (group, value)
пар
Чтобы получить результат, подобный [(group1, value1), (group1, value2), (group2, value3)]
из dict {group1: [value1, value2], group2: [value3]}
, используйте список понимания :
result = [(group, value) for group, values in groupdict.items()
for value in values]
Вложенный список просто значений
Чтобы получить результат, подобный [[value1, value2], [value3]]
из условия {group1: [value1, value2], group2: [value3]}
, используйте dict.values
:
result = list(groupdict.values())
Плоский список просто значений
Чтобы получить результат, подобный [value1, value2, value3]
из dict {group1: [value1, value2], group2: [value3]}
, сгладьте его с помощью списка :
result = [value for values in groupdict.values() for value in values]
Сведение итерируемых значений
Если ваши значения являются списками или другими итерациями, такими как
groupdict = {group1: [[list1_value1, list1_value2], [list2_value1]]}
и вы хотите получить плоский результат, такой как
result = {group1: [list1_value1, list1_value2, list2_value1]}
у вас есть два варианта:
Сгладить списки с помощью диктовок :
result = {group: [x for iterable in values for x in iterable]
for group, values in groupdict.items()}
Во-первых, избегайте создания списка итераций, используя list.extend
вместо list.append
. Другими словами, изменить
groupdict[group].append(value)
до
groupdict[group].extend(value)
А затем просто установите result = groupdict
.
отсортированный список
Dicts - неупорядоченные структуры данных. Если вы выполняете итерацию по dict, вы никогда не знаете, в каком порядке будут перечислены его элементы. Если вы не заботитесь о заказе, вы можете использовать рецепты, показанные выше. Но если вы действительно заботитесь о порядке, вы должны соответствующим образом отсортировать вывод.
Я буду использовать следующую команду, чтобы продемонстрировать, как сортировать вывод различными способами:
groupdict = {'abc': [1], 'xy': [2, 5]}
Имейте в виду, что это немного мета-рецепт, который, возможно, придется сочетать с другими частями этого ответа, чтобы получить именно тот результат, который вы хотите. Общая идея состоит в том, чтобы отсортировать ключи словаря перед их использованием для извлечения значений из dict:
groups = sorted(groupdict.keys())
# groups = ['abc', 'xy']
Имейте в виду, что sorted
принимает ключевую функцию в случае, если вы хотите настроить порядок сортировки. Например, если ключи dict являются строками и вы хотите отсортировать их по длине:
groups = sorted(groupdict.keys(), key=len)
# groups = ['xy', 'abc']
После того, как вы отсортировали ключи, используйте их для извлечения значений из dict в правильном порядке:
# groups = ['abc', 'xy']
result = [groupdict[group] for group in groups]
# result = [[1], [2, 5]]
Помните, что это может быть объединено с другими частями этого ответа, чтобы получить различные виды вывода. Например, если вы хотите сохранить идентификаторы группы:
# groups = ['abc', 'xy']
result = [(group, groupdict[group]) for group in groups]
# result = [('abc', [1]), ('xy', [2, 5])]
Для вашего удобства вот несколько часто используемых порядков сортировки:
Сортировка по количеству значений в группе:
groups = sorted(groudict.keys(), key=lambda group: len(groupdict[group]))
result = [groupdict[group] for group in groups]
# result = [[2, 5], [1]]
Подсчет количества значений в каждой группе
Для подсчета количества элементов, связанных с каждой группой, используйте функцию len
:
result = {group: len(values) for group, values in groupdict.items()}
Если вы хотите подсчитать количество отдельных элементов, используйте set
для устранения дубликатов:
result = {group: len(set(values)) for group, values in groupdict.items()}
Пример
Чтобы продемонстрировать, как собрать воедино рабочее решение по этому рецепту, давайте попробуем включить
data = [["A",0], ["B",1], ["C",0], ["D",2], ["E",2]]
в
result = [["A", "C"], ["B"], ["D", "E"]]
Другими словами, мы группируем списки по их второму элементу.
Первые две строки рецепта всегда одинаковы, поэтому начнем с копирования:
import collections
groupdict = collections.defaultdict(list)
Теперь мы должны выяснить, как перебрать вход. Поскольку наш ввод представляет собой простой список значений, нормального цикла for
будет достаточно:
for value in data:
Далее мы должны извлечь идентификатор группы из значения. Мы группируемся по второму элементу списка, поэтому мы используем индексацию:
group = value[1]
Следующим шагом является преобразование значения. Поскольку мы хотим сохранить только первый элемент каждого списка, мы снова используем индексирование списка:
value = value[0]
Наконец, мы должны выяснить, как превратить сгенерированный нами диктат в список. Нам нужен список значений без групп. Мы обращаемся к разделу рецепта Output , чтобы найти соответствующий фрагмент сглаживания:
result = list(groupdict.values())
Et voilà:
data = [["A",0], ["B",1], ["C",0], ["D",2], ["E",2]]
import collections
groupdict = collections.defaultdict(list)
for value in data:
group = value[1]
value = value[0]
groupdict[group].append(value)
result = list(groupdict.values())
# result: [["A", "C"], ["B"], ["D", "E"]]