Як я можу використовувати itertools.groupby ()?


506

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

  • Візьміть список - у цьому випадку діти об’єктивованого lxmlелемента
  • Розділіть його на групи за деякими критеріями
  • Потім пізніше перегляньте кожну з цих груп окремо.

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

Отже, як мені користуватися itertools.groupby()? Чи є інша техніка, якою я повинен користуватися? Також будуть вдячні вказівки на гарне "обов'язкове" читання.


одним корисним випадком для цього буде leetcode.com/problems/string-compression
ShawnLee

Відповіді:


655

ВАЖЛИВА ПРИМІТКА . Спершу потрібно сортувати свої дані .


Частина, яку я не отримав, полягає в тому, що в прикладі будівництва

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 + "."

Це дасть вам вихід:

тварини: ведмідь і качка.
рослини: кактус.
транспортні засоби: швидкісний човен та шкільний автобус.


1
Чи є спосіб заздалегідь вказати групи, а потім не вимагати сортування?
Джон Сальватьє

2
itertools зазвичай натискає на мене, але я також мав "блок" для цього. Я оцінив ваші приклади - набагато зрозуміліші, ніж документи. Я думаю, що itertools, як правило, клацає чи ні, і їх набагато простіше зрозуміти, якщо у вас трапляються подібні проблеми. Цей ще не був потрібен у дикій природі.
Профан

3
Документи @Julian python здаються чудовими для більшості речей, але коли мова йде про ітераторів, генераторів та вишневих документів, документи в основному мене містифікують. Документи Джанго подвійно збивають з пантелику.
Марк Максмайстер

6
+1 для сортування - я не розумів, що ви маєте на увазі, поки я не згрупував свої дані.
Коді

4
@DavidCrook дуже пізно пішов на вечірку, але може комусь допомогти. Можливо, тому, що ваш масив не відсортований, спробуйте groupby(sorted(my_collection, key=lambda x: x[0]), lambda x: x[0]))під припущенням, що my_collection = [("animal", "bear"), ("plant", "cactus"), ("animal", "duck")]і ви хочете згрупуватися поanimal or plant
Робін Немет

71

Приклад в документах 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метод насправді просто повторює список і кожного разу, коли ключ змінюється, він створює нову групу.


45
Отже, ви читали keyfuncі були на кшталт "так, я точно знаю, що це, тому що ця документація є простою". Неймовірно!
Джарад

5
Я вважаю, що більшість людей вже знають про цей "простий", але марний приклад, оскільки він не говорить про те, які "дані" та "keyfunc" використовувати !! Але я думаю, ви і цього не знаєте, інакше ви допоможете людям, уточнивши його, а не просто скопіювавши їх. Або ти?
Апостолос

69

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. Згрупуйте послідовно пункти разом
  • B. Згрупуйте всі події предмета за поданим відсортованим ітерабельним
  • 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
>>> # keyfunc = lambda s: s.islower()                      # equivalent
>>> def keyfunc(s):
...     """Return a True if a string is lowercase, else False."""   
...     return s.islower()
>>> print_groupby(sorted("bCAaCacAADBbB"), keyfunc)
key: 'False'--> group: ['A', 'A', 'A', 'B', 'B', 'C', 'C', 'D']
key: 'True'--> group: ['a', 'a', 'b', 'b', 'c']

Використання

Примітка: Кілька останніх прикладів походять з PyCon (розмова) (іспанська) Віктора Террона , "Кунг-фу на світанку з Itertools". Дивіться також groupbyвихідний код, написаний на С.

* Функція, при якій всі елементи передаються та порівнюються, впливаючи на результат. Інші об'єкти з основними функціями включають в себе sorted(), max()і min().


Відповідь

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

1
У технічному плані, мабуть, повинні сказати документи [''.join(g) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D.
Матін Ульхак

1
Так. Більшість документів itertools таким чином "скорочуються". Оскільки всі itertools є ітераторами, їх потрібно передати вбудованому ( list(), tuple()) або споживати в циклі / розумінні для відображення вмісту. Це надмірності, які автор, ймовірно, виключив для економії простору.
pylang

39

Неточний трюк з groupby - запустити кодування довжини в одному рядку:

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

дасть вам список з 2-х кортежів, де перший елемент є знаком, а другий - кількістю повторень.

Редагувати: Зауважте, що це те, що відокремлюється itertools.groupbyвід GROUP BYсемантики SQL : itertools не заздалегідь сортує ітератор, тому групи з тим самим "ключем" не об'єднуються.


27

Ще один приклад:

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]

Це цікаво, але хіба itertools.islice не буде кращим для того, щоб перебрати ітерабельний? Він повертає об'єкт, ітераційний як генератор, але він використовує код C.
trojjer

@trojjer islice було б краще, якщо групи мають однаковий розмір.
woodm1979

Хочу отримати: [0, 1, 2], [1, 2, 3], [2, 3, 4] ...
Гілберт

21

УВАГА:

Список синтаксису (groupby (...)) не працюватиме так, як ви плануєте. Здається, руйнує внутрішні об’єкти ітератора, тому використовуючи

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

буде виробляти:

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

Замість списку (groupby (...)) спробуйте [(k, list (g)) для k, g у groupby (...)], або якщо ви часто використовуєте цей синтаксис,

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

і отримати доступ до функціональних можливостей groupby, уникаючи цих прискіпливих (для невеликих даних) ітераторів разом.


3
Багато відповідей стосуються камені спотикання, яку ви повинні сортувати перед групою, щоб отримати очікувані результати. Я щойно стикався з цією відповіддю, яка пояснює дивну поведінку, якої я раніше не бачив. Я раніше не бачив, тому що тільки зараз я намагався перерахувати (groupby (діапазон (10)), як говорить @singular. До цього я завжди використовував "рекомендований" підхід "вручну" ітерації через об'єкти groupby, а не дозволяючи конструктору list () "зробити це автоматично"
Red Pea

9

Я хотів би навести ще один приклад, коли 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.

Є дві групи з автомобілем, тоді як можна очікувати лише однієї групи


5
Спершу потрібно сортувати дані, використовуючи в якості ключа функцію, за якою групуєте. Про це згадується у двох публікаціях вище, але не висвітлюється.
mbatchkarov

Я робив розуміння диктату, щоб зберегти субітератори за ключем, поки я не зрозумів, що це так просто, як dict (groupby (ітератор, ключ)). Солодке.
trojjer

З другої думки і після експериментів, виклик дикту, обгорнутий навколо групи, вичерпає групові субітератори. Блін.
trojjer

У чому сенс цієї відповіді? Як будується на оригінальній відповіді ?
codeforester

7

@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)]

Як бачимо, є два o та два e, але вони потрапили в окремі групи. Тоді я зрозумів, що потрібно сортувати список, переданий до функції 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)]

Пам'ятайте лише, що якщо список не відсортований, функція groupby не працюватиме !


7
Насправді це працює. Ви можете подумати, що така поведінка порушена, але вона корисна в деяких випадках. Див. Відповіді на це запитання для прикладу: stackoverflow.com/questions/1553275/…
Денис Откідач

6

Сортування та групування

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'}

5

Як я можу використовувати itertools.groupby () Python ()?

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

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

Ось приклад групи за допомогою підпрограми для згрупування за рахунком; він використовує ключ, який може викликатись (у цьому випадку 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])]

1

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

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)


1

Ця основна реалізація допомогла мені зрозуміти цю функцію. Сподіваюся, що це допомагає і іншим:

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

0

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

           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]}

1
винаходити колесо - не чудова ідея, також питання полягає в тому, щоб пояснити itertools groupby, а не писати власні
user2678074

1
@ user2678074 Ви маєте рацію. Це щось, якщо ви хочете написати самостійно для точки зору навчання.
Sky

2
Також краще скористатися
Міккі Перлштайн

@MickeyPerlstein і швидше.
смішний чоловік
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.