Bagaimana cara menggunakan itertools.groupby ()?

507

Saya belum dapat menemukan penjelasan yang dapat dimengerti tentang bagaimana sebenarnya menggunakan itertools.groupby()fungsi Python . Yang saya coba lakukan adalah ini:

  • Ambil daftar - dalam hal ini, anak-anak dari lxmlelemen yang diobjekkan
  • Bagilah menjadi beberapa kelompok berdasarkan beberapa kriteria
  • Kemudian, ulangi masing-masing kelompok ini secara terpisah.

Saya telah meninjau dokumentasi , dan contoh-contohnya , tetapi saya mengalami kesulitan mencoba menerapkannya di luar daftar angka sederhana.

Jadi, bagaimana saya menggunakan itertools.groupby()? Apakah ada teknik lain yang harus saya gunakan? Petunjuk untuk bacaan "prasyarat" yang baik juga akan dihargai.

James Sulak
sumber
satu kasus yang berguna untuk itu adalah leetcode.com/problems/string-compression
ShawnLee

Jawaban:

657

CATATAN PENTING: Anda harus mengurutkan data Anda terlebih dahulu.


Bagian yang saya tidak dapatkan adalah bahwa dalam contoh konstruksi

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

kadalah kunci pengelompokan saat ini, dan gmerupakan iterator yang dapat Anda gunakan untuk beralih di atas grup yang ditentukan oleh kunci pengelompokan itu. Dengan kata lain, groupbyiterator itu sendiri mengembalikan iterator.

Berikut ini contohnya, menggunakan nama variabel yang lebih jelas:

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

Ini akan memberi Anda output:

Seekor beruang adalah binatang.
Bebek adalah binatang.

Kaktus adalah tanaman.

Kapal cepat adalah kendaraan.
Bus sekolah adalah kendaraan.

Dalam contoh ini, thingsadalah daftar tuple di mana item pertama di setiap tuple adalah grup item kedua.

The groupby()Fungsi membutuhkan dua argumen: (1) data ke kelompok dan (2) fungsi untuk kelompok dengan.

Di sini, lambda x: x[0]diperintahkan groupby()untuk menggunakan item pertama di setiap tuple sebagai kunci pengelompokan.

Dalam forpernyataan di atas , groupbymengembalikan tiga pasang (kunci, grup iterator) - satu kali untuk setiap kunci unik. Anda dapat menggunakan iterator yang dikembalikan untuk beralih setiap item individu dalam grup itu.

Berikut adalah contoh yang sedikit berbeda dengan data yang sama, menggunakan pemahaman daftar:

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

Ini akan memberi Anda output:

hewan: beruang dan bebek.
tanaman: kaktus.
kendaraan: speed boat dan bus sekolah.

James Sulak
sumber
1
Apakah ada cara untuk menentukan grup sebelumnya dan kemudian tidak memerlukan penyortiran?
John Salvatier
2
itertools biasanya mengklik untuk saya, tetapi saya juga punya 'blok' untuk ini. Saya menghargai contoh Anda - jauh lebih jelas daripada dokumen. Saya pikir itertools cenderung mengklik atau tidak, dan lebih mudah dipahami jika Anda mengalami masalah yang sama. Belum membutuhkan yang ini di alam liar.
Profane
3
@Julian python docs tampak hebat untuk sebagian besar barang tetapi ketika menyangkut iterator, generator, dan cherrypy, sebagian besar docs membingungkan saya. Dokumen-dokumen Django membingungkan dua kali lipat.
Marc Maxmeister
6
+1 untuk penyortiran - Saya tidak mengerti apa yang Anda maksud sampai saya mengelompokkan data saya.
Cody
4
@ Davidvidrook sangat terlambat ke pesta tetapi mungkin membantu seseorang. Mungkin karena array Anda tidak diurutkan, cobalah groupby(sorted(my_collection, key=lambda x: x[0]), lambda x: x[0]))dengan asumsi itu my_collection = [("animal", "bear"), ("plant", "cactus"), ("animal", "duck")]dan Anda ingin dikelompokkan berdasarkananimal or plant
Robin Nemeth
72

Contoh pada dokumen Python cukup mudah:

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

Jadi dalam kasus Anda, data adalah daftar node, keyfunc adalah tempat logika fungsi kriteria Anda berjalan dan kemudian groupby()mengelompokkan data.

Anda harus berhati-hati untuk mengurutkan data berdasarkan kriteria sebelum Anda menelepon groupbyatau itu tidak akan berfungsi. groupbyMetode sebenarnya hanya mengulang melalui daftar dan setiap kali kunci itu berubah itu membuat grup baru.

Seb
sumber
46
Jadi Anda membaca keyfuncdan seperti "ya, saya tahu persis apa itu karena dokumentasi ini cukup mudah."? Luar biasa!
Jarad
5
Saya percaya kebanyakan orang sudah tahu tentang ini "langsung" tetapi contoh tidak berguna, karena tidak mengatakan seperti apa 'data' dan 'keyfunc' untuk digunakan !! Tapi saya kira Anda juga tidak tahu, kalau tidak, Anda akan membantu orang-orang dengan mengklarifikasi dan tidak hanya menyalinnya. Atau apakah Anda
Apostolos
69

itertools.groupby adalah alat untuk mengelompokkan item.

Dari dokumen , kami mengumpulkan lebih lanjut apa yang mungkin dilakukan:

# [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 objek menghasilkan pasangan-pasangan kunci-grup di mana grup tersebut adalah generator.

fitur

  • A. Kelompokkan item berurutan bersama
  • B. Kelompokkan semua kemunculan suatu item, dengan diberi pilihan iterable
  • C. Tentukan cara mengelompokkan item dengan fungsi tombol *

Perbandingan

# 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']

Penggunaan

Catatan: Beberapa contoh terakhir berasal dari PyCon (bicara) Víctor Terrón (Spanyol) , "Kung Fu at Dawn with Itertools". Lihat juga groupbykode sumber yang ditulis dalam C.

* Fungsi di mana semua item dilewati dan dibandingkan, mempengaruhi hasilnya. Objek lain dengan fungsi utama meliputi sorted(), max()dan min().


Tanggapan

# OP: Yes, you can use `groupby`, e.g. 
[do_something(list(g)) for _, g in groupby(lxml_elements, criteria_func)]
pylang
sumber
1
Secara teknis, dokumen mungkin harus mengatakan [''.join(g) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D.
Mateen Ulhaq
1
Iya. Sebagian besar dokumen itertools "diringkas" dengan cara ini. Karena semua itertools adalah iterator, mereka harus dilemparkan ke builtin ( list(), tuple()) atau dikonsumsi dalam satu lingkaran / pemahaman untuk menampilkan konten. Ini adalah redudansi yang kemungkinan besar dikecualikan penulis untuk menghemat ruang.
pylang
39

Trik neato dengan groupby adalah menjalankan pengkodean panjang dalam satu baris:

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

akan memberi Anda daftar 2-tupel di mana elemen pertama adalah char dan yang kedua adalah jumlah pengulangan.

Sunting: Perhatikan bahwa ini adalah apa yang terpisah itertools.groupbydari GROUP BYsemantik SQL : itertools tidak (dan secara umum tidak dapat) mengurutkan iterator terlebih dahulu, sehingga grup dengan "kunci" yang sama tidak digabungkan.

nimish
sumber
27

Contoh lain:

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

hasil dalam

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

Perhatikan bahwa igroup adalah iterator (sub-iterator sesuai dengan dokumentasi menyebutnya).

Ini berguna untuk memotong generator:

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)

Contoh lain dari groupby - ketika kunci tidak diurutkan. Dalam contoh berikut, item dalam xx dikelompokkan berdasarkan nilai dalam yy. Dalam hal ini, satu set nol adalah output pertama, diikuti oleh satu set, diikuti lagi oleh set nol.

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

Menghasilkan:

0 [0, 1, 2]
1 [3, 4, 5]
0 [6, 7, 8, 9]
pengguna650654
sumber
Itu menarik, tetapi tidakkah itertools.islice lebih baik untuk memotong iterable? Ini mengembalikan objek yang iterasi seperti generator, tetapi menggunakan kode C.
trojjer
@trojjer islice akan lebih baik JIKA ukuran grup konsisten.
woodm1979
Saya ingin mendapatkan: [0, 1, 2], [1, 2, 3], [2, 3, 4] ...
GilbertS
21

PERINGATAN:

Daftar sintaks (groupby (...)) tidak akan berfungsi seperti yang Anda inginkan. Tampaknya untuk menghancurkan objek iterator internal, jadi gunakan

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

akan menghasilkan:

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

Alih-alih, dari list (groupby (...)), coba [(k, list (g)) untuk k, g di groupby (...)], atau jika Anda sering menggunakan sintaks itu,

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

dan mendapatkan akses ke fungsi groupby sambil menghindari iterator yang sial (untuk data kecil) bersama-sama.

RussellStewart
sumber
3
Banyak jawaban merujuk pada batu sandungan yang harus Anda sortir sebelum kelompok untuk mendapatkan hasil yang diharapkan. Saya baru saja menemukan jawaban ini, yang menjelaskan perilaku aneh yang belum pernah saya lihat sebelumnya. Saya belum pernah melihat sebelumnya karena baru sekarang saya mencoba mendaftar (groupby (range (10)) seperti yang dikatakan @singular. Sebelumnya saya selalu menggunakan pendekatan "yang disarankan" secara "manual" untuk mengulangi objek groupby daripada menggunakan membiarkan daftar () konstruktor "secara otomatis" melakukannya
The Red Pea
9

Saya ingin memberikan contoh lain di mana groupby tanpa sort tidak berfungsi. Diadaptasi dari contoh oleh James Sulak

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

output adalah

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.

ada dua kelompok dengan kendaraan, sedangkan yang bisa diharapkan hanya satu kelompok

Kiriloff
sumber
5
Anda harus mengurutkan data terlebih dahulu, menggunakan sebagai kunci fungsi yang Anda kelompokkan. Ini disebutkan dalam dua pos di atas, tetapi tidak disorot.
mbatchkarov
Saya melakukan pemahaman dict untuk menjaga sub-iterator dengan kunci, sampai saya menyadari bahwa ini sesederhana dict (groupby (iterator, key)). Manis.
trojjer
Pada pikiran kedua dan setelah percobaan, panggilan diktik yang melilit kelompok oleh akan menghabiskan sub-iterator kelompok. Sial.
trojjer
Apa gunanya jawaban ini? Bagaimana itu membangun jawaban asli ?
codeforester
7

@ CapSolo, saya mencoba contoh Anda, tetapi tidak berhasil.

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

Keluaran:

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

Seperti yang Anda lihat, ada dua dan dua, tetapi mereka masuk ke dalam kelompok yang terpisah. Saat itulah saya menyadari Anda perlu mengurutkan daftar yang diteruskan ke fungsi groupby. Jadi, penggunaan yang benar adalah:

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

Keluaran:

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

Hanya dengan mengingat, jika daftar tidak diurutkan, fungsi groupby tidak akan berfungsi !

pedromanoel
sumber
7
Sebenarnya itu berhasil. Anda mungkin berpikir perilaku ini rusak, tetapi ini berguna dalam beberapa kasus. Lihat jawaban atas pertanyaan ini untuk contoh: stackoverflow.com/questions/1553275/…
Denis Otkidach
6

Sortasi dan kelompokkan

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'}
Satyajit Das
sumber
5

Bagaimana cara menggunakan Python itertools.groupby ()?

Anda dapat menggunakan groupby untuk mengelompokkan hal-hal yang harus diulangi. Anda memberi grup dengan iterable, dan fungsi kunci opsional / callable yang digunakan untuk memeriksa item ketika mereka keluar dari iterable, dan itu mengembalikan iterator yang memberikan dua-tuple dari hasil callable kunci dan item aktual dalam iterable lain. Dari bantuan:

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

Berikut adalah contoh pengelompokan dengan menggunakan coroutine untuk dikelompokkan berdasarkan suatu hitungan, ia menggunakan pemanggil kunci (dalam hal ini, coroutine.send) untuk hanya memuntahkan hitungan untuk berapa banyak iterasi dan sub-iterator elemen yang dikelompokkan:

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

cetakan

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

Salah satu contoh berguna yang saya temui mungkin membantu:

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)

Input sampel: 14445221

Output sampel: (1,1) (3,4) (1,5) (2,2) (1,1)

Arko
sumber
1

Implementasi dasar ini membantu saya memahami fungsi ini. Semoga ini membantu orang lain juga:

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
Tiago
sumber
0

Anda dapat menulis fungsi groupby sendiri:

           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]}
Langit
sumber
1
menciptakan kembali roda bukanlah ide bagus, juga pertanyaannya adalah untuk menjelaskan itertools groupby, bukan menulis sendiri
user2678074
1
@ user2678074 Anda benar. Ini sesuatu jika Anda ingin menulis sendiri untuk sudut pandang pembelajaran.
Langit
2
Juga lebih baik gunakan defaultdict (daftar) sehingga bahkan lebih pendek
Mickey Perlstein
@MickeyPerlstein dan lebih cepat.
funnydman