ฉันจะใช้ itertools.groupby () ได้อย่างไร


506

ฉันไม่สามารถค้นหาคำอธิบายที่เข้าใจได้เกี่ยวกับวิธีการใช้งานitertools.groupby()ฟังก์ชั่นของไพ ธ อน สิ่งที่ฉันพยายามทำคือ:

  • จดรายการ - ในกรณีนี้คือลูกของ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()ให้ใช้รายการแรกในแต่ละ tuple เป็นคีย์การจัดกลุ่ม

ใน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
มีวิธีระบุกลุ่มไว้ล่วงหน้าแล้วไม่ต้องเรียงลำดับหรือไม่?
John Salvatier

2
มันมักจะคลิกเพื่อฉัน แต่ฉันก็มี 'บล็อก' สำหรับอันนี้ด้วย ฉันชื่นชมตัวอย่างของคุณ - ชัดเจนยิ่งกว่าเอกสาร ฉันคิดว่า itertools มีแนวโน้มที่จะคลิกหรือไม่และจะเข้าใจได้ง่ายขึ้นถ้าคุณประสบปัญหาที่คล้ายกัน ยังไม่ต้องการสิ่งนี้ในป่า
ดูหมิ่น

3
@Julian python docs นั้นดูยอดเยี่ยมสำหรับสิ่งของส่วนใหญ่ แต่เมื่อพูดถึง iterators, generators และ cherrypy docs ส่วนใหญ่จะทำให้ฉันประหลาดใจ เอกสารของ Django ยุ่งเหยิงเป็นสองเท่า
Marc Maxmeister

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
Robin Nemeth

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และเป็นเหมือน "ใช่ฉันรู้ว่าสิ่งที่เป็นเพราะเอกสารนี้ค่อนข้างตรงไปตรงมา"? ! เหลือเชื่อ
Jarad

5
ฉันเชื่อว่าคนส่วนใหญ่รู้แล้วเกี่ยวกับเรื่องนี้ "ตรงไปตรงมา" แต่ตัวอย่างที่ไร้ประโยชน์เพราะมันไม่ได้บอกว่า 'data' และ 'keyfunc' แบบไหนที่จะใช้ !! แต่ฉันคิดว่าคุณก็ไม่รู้เหมือนกันไม่งั้นคุณจะช่วยคนอื่นให้ชัดเจนโดยไม่ต้องลอกเลียนแบบ หรือคุณ
Apostolos

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. จัดกลุ่มรายการทั้งหมดที่เกิดขึ้นโดยกำหนดให้เรียงลำดับ iterable
  • 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 ของVíctorTerrón (พูดคุย) (สเปน) , "Kung Fu at Dawn with 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)]

1
เทคนิค, [''.join(g) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC Dเอกสารอาจจะบอกว่า
Mateen Ulhaq

1
ใช่. เอกสารส่วนใหญ่ของ itertools นั้น "ย่อ" ด้วยวิธีนี้ เนื่องจาก itertools ทั้งหมดเป็นตัววนซ้ำพวกมันจะต้องถูกส่งไปยัง builtin ( list(), tuple()) หรือใช้ในลูป / ความเข้าใจเพื่อแสดงเนื้อหา สิ่งเหล่านี้คือความซ้ำซ้อนที่ผู้เขียนคาดว่าจะถูกยกเว้นในการอนุรักษ์พื้นที่
pylang

39

เคล็ดลับเรียบร้อยกับ groupby คือการเข้ารหัสความยาวในหนึ่งบรรทัด:

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

จะให้รายชื่อของ 2-tuples โดยที่องค์ประกอบแรกคือถ่านและที่สองคือจำนวนการทำซ้ำ

แก้ไข: โปรดทราบว่านี่คือสิ่งที่แยกออกitertools.groupbyจากซีแมนทิกส์ SQL GROUP BY: 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 เป็นตัววนซ้ำ (ตัวทำซ้ำย่อยตามที่เอกสารเรียกว่า)

สิ่งนี้มีประโยชน์สำหรับการ chunking 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)

อีกตัวอย่างของ 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]

มันน่าสนใจ แต่จะไม่ดีกว่าหรือเปล่าจะดีกว่าสำหรับการ chunking iterable หรือไม่ ส่งคืนวัตถุที่ทำซ้ำเหมือนเครื่องกำเนิดไฟฟ้า แต่ใช้รหัส C
trojjer

@trojjer islice จะดีกว่าหากกลุ่มมีขนาดที่สอดคล้องกัน
woodm1979

ฉันต้องการได้รับ: [0, 1, 2], [1, 2, 3], [2, 3, 4] ...
GilbertS

21

คำเตือน:

รายการไวยากรณ์ (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)]

และเข้าถึงฟังก์ชั่นกลุ่มโดยในขณะที่หลีกเลี่ยงการทำซ้ำ (สำหรับข้อมูลขนาดเล็ก) ที่น่ารำคาญ


3
คำตอบหลายข้ออ้างอิงถึงบล็อกสะดุดที่คุณต้องเรียงลำดับก่อนที่กลุ่มจะได้รับผลลัพธ์ที่คาดหวัง ฉันเพิ่งพบคำตอบนี้ซึ่งอธิบายพฤติกรรมแปลก ๆ ที่ฉันไม่เคยเห็นมาก่อน ฉันไม่เคยเห็นมาก่อนเพราะตอนนี้ฉันกำลังพยายามที่จะแสดงรายการ (groupby (ช่วง (10)) ตามที่ @singular พูดก่อนหน้านี้ฉันมักจะใช้วิธี "แนะนำ" ของ "ด้วยตนเอง" ที่วนผ่านวัตถุ groupby แทน ปล่อยให้ตัวสร้างรายการ () "อัตโนมัติ" ทำ
ถั่วแดง

9

ฉันอยากจะให้อีกตัวอย่างหนึ่งที่ groupby ที่ไม่มีการเรียงลำดับไม่ทำงาน ดัดแปลงมาจากตัวอย่างโดย 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 " "

ผลลัพธ์คือ

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 เพื่อรักษา sub-iterators ด้วยคีย์จนกว่าฉันจะรู้ว่าสิ่งนี้เป็นเรื่องง่ายเหมือน dict (groupby (iterator, key)) หวาน.
trojjer

ในความคิดที่สองและหลังการทดลองการเรียกใช้ dict ที่ล้อมรอบ groupby จะทำให้หมดกลุ่มการวนซ้ำ ประณาม.
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)]

อย่างที่คุณเห็นมีสองตัวและสองตัว แต่พวกมันแยกกันเป็นกลุ่ม นั่นคือเมื่อฉันรู้ว่าคุณต้องเรียงลำดับรายการที่ส่งผ่านไปยังฟังก์ชัน 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

ฉันจะใช้ Perton itertools.groupby () ได้อย่างไร

คุณสามารถใช้ groupby เพื่อจัดกลุ่มสิ่งต่าง ๆ เพื่อทำซ้ำ คุณให้กลุ่มโดย iterable และฟังก์ชั่นคีย์ที่เป็นตัวเลือก/ callable ที่จะตรวจสอบรายการที่พวกเขาออกมาจาก iterable และมันจะส่งกลับ iterator ที่ให้สอง tuple ของผลลัพธ์ของคีย์ callable และรายการที่เกิดขึ้นจริงใน อีก iterable จากความช่วยเหลือ:

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

นี่คือตัวอย่างของ groupby ที่ใช้ coroutine เพื่อจัดกลุ่มตามจำนวนโดยใช้คีย์ callable (ในกรณีนี้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

คุณสามารถเขียนฟังก์ชั่น groupby ของตัวเอง:

           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
reinventing wheel ไม่ใช่ความคิดที่ดีคำถามก็คือการอธิบาย itertools groupby ไม่ใช่การเขียนของตัวเอง
2678074

1
@ user2678074 คุณพูดถูก มันเป็นสิ่งที่ถ้าคุณต้องการที่จะเขียนของตัวเองสำหรับมุมมองการเรียนรู้
Sky

2
และควรใช้ defaultdict (รายการ) ที่ดีกว่าดังนั้นจึงยิ่งสั้นกว่านี้
Mickey Perlstein

@ MickeyPerlstein และเร็วกว่า
funnydman
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.