itertools.groupby() некорректно группируется

#python #itertools

#python #python-itertools

Вопрос:

У меня есть эти данные:

 self.data = [(1, 1, 5.0),
             (1, 2, 3.0),
             (1, 3, 4.0),
             (2, 1, 4.0),
             (2, 2, 2.0)]
  

Когда я запускаю этот код:

 for mid, group in itertools.groupby(self.data, key=operator.itemgetter(0)):
  

для list(group) я получаю:

 [(1, 1, 5.0),
 (1, 2, 3.0),
 (1, 3, 4.0)]
  

это то, чего я хочу.

Но если я использую 1 вместо 0

 for mid, group in itertools.groupby(self.data, key=operator.itemgetter(1)):
  

для группировки по второму номеру в кортежах я получаю только:

 [(1, 1, 5.0)]
  

даже несмотря на то, что есть другие кортежи, которые имеют «1» в этой 1 (2-й) позиции.

Ответ №1:

itertools.groupby собирает вместе смежные элементы с одним и тем же ключом. Если вы хотите, чтобы все элементы имели один и тот же ключ, вам нужно сначала отсортировать self.data .

 for mid, group in itertools.groupby(
    sorted(self.data,key=operator.itemgetter(1)), key=operator.itemgetter(1)):
  

Комментарии:

1. Ранее я отсортировал нулевую позицию. Поэтому я просто снова отсортировал перед выполнением groupby, и это работает. self.data.sort(ключ=operator.itemgetter(1))

2. Сортировать не нужно; вместо этого вы хотите использовать словарь : grouped = {} тогда for v in self.data: grouped.setdefault(v[1], []).append(v) . Сортировка — это операция O (NlogN), при которой использование словаря для группировки значений позволяет выполнить задачу за O (N) время.

Ответ №2:

Вариант без сортировки (через словарь). Должно быть лучше с точки зрения производительности.

 def full_group_by(l, key=lambda x: x):
    d = defaultdict(list)
    for item in l:
        d[key(item)].append(item)
    return d.items()
  

Комментарии:

1. Вернулся, чтобы опубликовать то же самое, я не читал ваш ответ! Очевидно, что это правильный путь 🙂

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

3. @Jeronimo: вы бы попытались найти хэшируемое отражение ключа; скажем, tuple() для ключа списка или frozenset(d.items()) для словарей и т.д. Если это действительно невозможно, только тогда вам придется вернуться к цене сортировки O (NlogN). Использование словаря для группировки позволяет выполнить задачу за линейное (O (N)) время.

Ответ №3:

Ниже «исправлено» несколько проблем с Python itertools.groupby .

 def groupby2(l, key=lambda x:x, val=lambda x:x, agg=lambda x:x, sort=True):
    if sort:
        l = sorted(l, key=key)
    return ((k, agg((val(x) for x in v))) 
        for k,v in itertools.groupby(l, key=key))
  

В частности,

  1. Это не требует, чтобы вы сортировали свои данные.
  2. Это не требует, чтобы вы использовали key только как именованный параметр.
  3. На выходе получается чистый генератор, tuple(key, grouped_values) где значения задаются третьим параметром.
  4. Возможность легко применять функции агрегирования, такие как sum или avg.

Пример использования

 import itertools
from operator import itemgetter
from statistics import *

t = [('a',1), ('b',2), ('a',3)]
for k,v in groupby2(t, itemgetter(0), itemgetter(1), sum):
  print(k, v)
  

Это выводит,

 a 4
b 2
  

Поиграйте с этим кодом

Комментарии:

1. Почему это «раздражает»? groupby() позволяет объединять * последовательные совпадающие значения в группы, никогда не предназначалось группировать по целому ряду, что требует чтения каждого значения во входном итерируемом файле. Основная цель использования itertools модуля заключается в том, чтобы по возможности избегать использования всех значений итератора.

2. Обратите внимание, что сортировка требует затрат: требуется O (NlogN) времени, чтобы отсортировать N элементов в отсортированную последовательность. С другой стороны, группировка с использованием словаря занимает линейное время (O (N)). Ваша «служебная функция» исключает возможность избежать оплаты стоимости сортировки., и поскольку вы не используете аргументы только для ключевых слов, любому, кто читает ваши group2() вызовы, придется каждый раз обращаться к документации, чтобы выяснить, что делают все аргументы.

3. Ваш t запрос было бы лучше обработать с помощью from collections import defaultdict , summed = defaultdict(int) , for k, v in t: summed[k] = v , for k, v in summed: print(k, v) . Это гораздо более самоочевидно в отношении того, чего достигает код, и делает это за линейное время, сортировка не требуется.

4. @MartijnPieters Пример приведен только для демонстрации. Безусловно, есть более эффективные способы сделать это.

5. Смотрите также: more_itertools.groupby_transform(iterable, keyfunc=None, valuefunc=None, reducefunc=None) . keyfunc похож на ваш key , valuefunc похож на ваш val и reducefunc похож на ваш agg .