Как использовать itertools.groupby ()?

Мне не удалось найти понятного объяснения того, как на самом деле использовать itertools.groupby() функцию Python . Я пытаюсь сделать следующее:

  • Возьмите список - в данном случае дочерние lxmlэлементы объективированного элемента.
  • Разделите его на группы по некоторым критериям
  • Затем переберите каждую из этих групп отдельно.

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

Итак, как мне использовать itertools.groupby() ? Есть ли еще одна техника, которую я должен использовать? Также приветствуются указатели на хорошее "предварительное" чтение.

Ответов (13)

Решение

ВАЖНОЕ ПРИМЕЧАНИЕ: сначала вы должны отсортировать свои данные .


Я не понял, что в примере конструкции

groups = []
uniquekeys = []
for k, g in groupby(data, keyfunc):
   groups.append(list(g))    # Store group iterator as a list
   uniquekeys.append(k)

k - это текущий ключ группировки и g итератор, который можно использовать для итерации по группе, определенной этим ключом группировки. Другими словами, groupby итератор сам возвращает итераторы.

Вот пример этого с более четкими именами переменных:

from itertools import groupby

things = [("animal", "bear"), ("animal", "duck"), ("plant", "cactus"), ("vehicle", "speed boat"), ("vehicle", "school bus")]

for key, group in groupby(things, lambda x: x[0]):
    for thing in group:
        print("A %s is a %s." % (thing[1], key))
    print("")
    

Это даст вам результат:

Медведь - это животное.
Утка - это животное.

Кактус - это растение.

Скоростной катер - это средство передвижения.
Школьный автобус - это средство передвижения.

В этом примере things это список кортежей, где первый элемент в каждом кортеже - это группа, к которой принадлежит второй элемент.

groupby() Функция принимает два аргумента: (1) данные для группы и (2) функцию к группе его с.

Здесь lambda x: x[0] указывает groupby() использовать первый элемент в каждом кортеже в качестве ключа группировки.

В приведенном выше for операторе groupby возвращает три пары (ключ, групповой итератор) - по одному разу для каждого уникального ключа. Вы можете использовать возвращенный итератор для перебора каждого отдельного элемента в этой группе.

Вот немного другой пример с теми же данными, использующий понимание списка:

for key, group in groupby(things, lambda x: x[0]):
    listOfThings = " and ".join([thing[1] for thing in group])
    print(key + "s:  " + listOfThings + ".")

Это даст вам результат:

животные: медведь и утка.
растения: кактус.
Транспорт: скоростной катер и школьный автобус.

Я хотел бы привести еще один пример, когда groupby без сортировки не работает. По примеру Джеймса Сулака

from itertools import groupby

things = [("vehicle", "bear"), ("animal", "duck"), ("animal", "cactus"), ("vehicle", "speed boat"), ("vehicle", "school bus")]

for key, group in groupby(things, lambda x: x[0]):
    for thing in group:
        print "A %s is a %s." % (thing[1], key)
    print " "

выход

A bear is a vehicle.

A duck is a animal.
A cactus is a animal.

A speed boat is a vehicle.
A school bus is a vehicle.

есть две группы с транспортными средствами, тогда как можно было ожидать только одну группу

Другой пример:

for key, igroup in itertools.groupby(xrange(12), lambda x: x // 5):
    print key, list(igroup)

приводит к

0 [0, 1, 2, 3, 4]
1 [5, 6, 7, 8, 9]
2 [10, 11]

Обратите внимание, что igroup - это итератор (суб-итератор, как его называют в документации).

Это полезно для разбивки генератора на части:

def chunker(items, chunk_size):
    '''Group items in chunks of chunk_size'''
    for _key, group in itertools.groupby(enumerate(items), lambda x: x[0] // chunk_size):
        yield (g[1] for g in group)

with open('file.txt') as fobj:
    for chunk in chunker(fobj):
        process(chunk)

Другой пример groupby - когда ключи не отсортированы. В следующем примере элементы в xx сгруппированы по значениям в yy. В этом случае сначала выводится один набор нулей, затем набор единиц, а затем снова набор нулей.

xx = range(10)
yy = [0, 0, 0, 1, 1, 1, 0, 0, 0, 0]
for group in itertools.groupby(iter(xx), lambda x: yy[x]):
    print group[0], list(group[1])

Производит:

0 [0, 1, 2]
1 [3, 4, 5]
0 [6, 7, 8, 9]

ПРЕДУПРЕЖДЕНИЕ:

Список синтаксиса (groupby (...)) не будет работать так, как вы предполагали. Кажется, что уничтожаются внутренние объекты итератора, поэтому использование

for x in list(groupby(range(10))):
    print(list(x[1]))

произведет:

[]
[]
[]
[]
[]
[]
[]
[]
[]
[9]

Вместо list (groupby (...)) попробуйте [(k, list (g)) для k, g в groupby (...)] или, если вы часто используете этот синтаксис,

def groupbylist(*args, **kwargs):
    return [(k, list(g)) for k, g in groupby(*args, **kwargs)]

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

Один полезный пример, с которым я столкнулся, может оказаться полезным:

from itertools import groupby

#user input

myinput = input()

#creating empty list to store output

myoutput = []

for k,g in groupby(myinput):

    myoutput.append((len(list(g)),int(k)))

print(*myoutput)

Пример ввода: 14445221

Пример вывода: (1,1) (3,4) (1,5) (2,2) (1,1)

Сортировка и группировка

from itertools import groupby

val = [{'name': 'satyajit', 'address': 'btm', 'pin': 560076}, 
       {'name': 'Mukul', 'address': 'Silk board', 'pin': 560078},
       {'name': 'Preetam', 'address': 'btm', 'pin': 560076}]


for pin, list_data in groupby(sorted(val, key=lambda k: k['pin']),lambda x: x['pin']):
...     print pin
...     for rec in list_data:
...             print rec
... 
o/p:

560076
{'name': 'satyajit', 'pin': 560076, 'address': 'btm'}
{'name': 'Preetam', 'pin': 560076, 'address': 'btm'}
560078
{'name': 'Mukul', 'pin': 560078, 'address': 'Silk board'}

Как использовать Python itertools.groupby ()?

Вы можете использовать groupby, чтобы сгруппировать элементы для перебора. Вы даете groupby итерацию и необязательную ключевую функцию / вызываемый объект для проверки элементов по мере их выхода из итерируемого объекта, и он возвращает итератор, который дает два кортежа результата вызываемого ключа и фактических элементов в другой итеративный. Из справки:

groupby(iterable[, keyfunc]) -> create an iterator which returns
(key, sub-iterator) grouped by each value of key(value).

Вот пример groupby, использующего сопрограмму для группировки по счетчику, он использует вызываемый ключ (в данном случае coroutine.send ), чтобы просто выплюнуть счетчик для любого количества итераций и сгруппированный под-итератор элементов:

import itertools


def grouper(iterable, n):
    def coroutine(n):
        yield # queue up coroutine
        for i in itertools.count():
            for j in range(n):
                yield i
    groups = coroutine(n)
    next(groups) # queue up coroutine

    for c, objs in itertools.groupby(iterable, groups.send):
        yield c, list(objs)
    # or instead of materializing a list of objs, just:
    # return itertools.groupby(iterable, groups.send)

list(grouper(range(10), 3))

отпечатки

[(0, [0, 1, 2]), (1, [3, 4, 5]), (2, [6, 7, 8]), (3, [9])]

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

           def groupby(data):
                kv = {}
                for k,v in data:
                    if k not in kv:
                         kv[k]=[v]
                    else:
                        kv[k].append(v)
           return kv

     Run on ipython:
       In [10]: data = [('a', 1), ('b',2),('a',2)]

        In [11]: groupby(data)
        Out[11]: {'a': [1, 2], 'b': [2]}

itertools.groupby это инструмент для группировки предметов.

Из документации мы узнаем, что он может делать:

# [k for k, g in groupby('AAAABBBCCDAABBB')] --> A B C D A B

# [list(g) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D

groupby объекты образуют пары ключ-группа, где группа является генератором.

Функции

  • A. Сгруппируйте последовательные элементы вместе
  • Б. Сгруппируйте все вхождения элемента по отсортированному итеративному
  • C. Укажите, как группировать элементы с помощью ключевой функции *

Сравнения

# Define a printer for comparing outputs
>>> def print_groupby(iterable, keyfunc=None):
...    for k, g in it.groupby(iterable, keyfunc):
...        print("key: '{}'--> group: {}".format(k, list(g)))
# Feature A: group consecutive occurrences
>>> print_groupby("BCAACACAADBBB")
key: 'B'--> group: ['B']
key: 'C'--> group: ['C']
key: 'A'--> group: ['A', 'A']
key: 'C'--> group: ['C']
key: 'A'--> group: ['A']
key: 'C'--> group: ['C']
key: 'A'--> group: ['A', 'A']
key: 'D'--> group: ['D']
key: 'B'--> group: ['B', 'B', 'B']

# Feature B: group all occurrences
>>> print_groupby(sorted("BCAACACAADBBB"))
key: 'A'--> group: ['A', 'A', 'A', 'A', 'A']
key: 'B'--> group: ['B', 'B', 'B', 'B']
key: 'C'--> group: ['C', 'C', 'C']
key: 'D'--> group: ['D']

# Feature C: group by a key function
>>> # islower = lambda s: s.islower()                      # equivalent
>>> def islower(s):
...     """Return True if a string is lowercase, else False."""   
...     return s.islower()
>>> print_groupby(sorted("bCAaCacAADBbB"), keyfunc=islower)
key: 'False'--> group: ['A', 'A', 'A', 'B', 'B', 'C', 'C', 'D']
key: 'True'--> group: ['a', 'a', 'b', 'b', 'c']

Использует

Примечание. Некоторые из последних примеров взяты из PyCon (разговор) Виктора Террона (испанский) , «Кунг-фу на рассвете с Itertools». См. Также groupby исходный код, написанный на C.

* Функция, в которой все элементы передаются и сравниваются, влияя на результат. Другие объекты с основными функциями включают в себя sorted(), max()и min().


Ответ

# OP: Yes, you can use `groupby`, e.g. 
[do_something(list(g)) for _, g in groupby(lxml_elements, criteria_func)]

Эта базовая реализация помогла мне понять эту функцию. Надеюсь, это поможет и другим:

arr = [(1, "A"), (1, "B"), (1, "C"), (2, "D"), (2, "E"), (3, "F")]

for k,g in groupby(arr, lambda x: x[0]):
    print("--", k, "--")
    for tup in g:
        print(tup[1])  # tup[0] == k
-- 1 --
A
B
C
-- 2 --
D
E
-- 3 --
F

Пример в документации Python довольно прост:

groups = []
uniquekeys = []
for k, g in groupby(data, keyfunc):
    groups.append(list(g))      # Store group iterator as a list
    uniquekeys.append(k)

Итак, в вашем случае данные представляют собой список узлов, keyfunc в котором выполняется логика вашей функции критериев, а затем groupby() группируются данные.

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

Изящный трюк с groupby - запустить кодировку длины в одну строку:

[(c,len(list(cgen))) for c,cgen in groupby(some_string)]

предоставит вам список из двух кортежей, где первый элемент - это символ, а второй - количество повторений.

Изменить: обратите внимание, что это то, что отделяется itertools.groupby от GROUP BY семантики SQL : itertools не (и, как правило, не может) сортировать итератор заранее, поэтому группы с одним и тем же «ключом» не объединяются.

@CaptSolo, я попробовал ваш пример, но он не сработал.

from itertools import groupby 
[(c,len(list(cs))) for c,cs in groupby('Pedro Manoel')]

Выход:

[('P', 1), ('e', 1), ('d', 1), ('r', 1), ('o', 1), (' ', 1), ('M', 1), ('a', 1), ('n', 1), ('o', 1), ('e', 1), ('l', 1)]

Как видите, есть два "о" и два "е", но они разделились на отдельные группы. Именно тогда я понял, что вам нужно отсортировать список, переданный функции groupby. Итак, правильное использование будет:

name = list('Pedro Manoel')
name.sort()
[(c,len(list(cs))) for c,cs in groupby(name)]

Выход:

[(' ', 1), ('M', 1), ('P', 1), ('a', 1), ('d', 1), ('e', 2), ('l', 1), ('n', 1), ('o', 2), ('r', 1)]

Просто помните, что если список не отсортирован, функция группировки не будет работать !