itertools.groupby()用于什么?

12
我在阅读 Python 文档时发现了 itertools.groupby() 函数。这个函数不是很直观,所以我决定在 stackoverflow 上查找一些信息。我在 How do I use Python's itertools.groupby()? 找到了一些内容。

这里和文档中似乎都没有太多关于它的信息,所以我决定发布我的观察结果以供评论。

谢谢


你有查看过 grouby() 文档 吗?其中哪一部分不够直观? - Moinuddin Quadri
@MoinuddinQuadri,OP的问题的第一句话表明他们阅读了Python文档。 - SudoKid
1
你问一个问题,而你已经准备好了详细的答案?真的吗?为什么不把所有的内容都放在问题中,把回答部分留给讨论呢? - hiro protagonist
6
“完全可以提出一个问题,只是为了回答它”(It's perfectly acceptable to ask a question for the sole purpose of answering it),我自己也这样做过。 “为什么不把答案都写在问题里?”因为答案不属于问题,答案是答案。 - Tagc
@EmettSpeer 我真正想问的是“哪一部分不够直观?”。我提供文档链接只是为了确保 OP 查看的是官方 Python 文档,而不是任何教程。 - Moinuddin Quadri
显示剩余2条评论
2个回答

24
首先,您可以在此处阅读文档

我认为最重要的一点是:始终使用相同的键对项目进行排序以用于分组,以避免出现意外结果。希望通过示例后这个原因变得清晰。

itertools.groupby(iterable, key=None or some func) 接受一个可迭代对象列表,并根据指定的键将它们分组。键指定要应用于每个单独可迭代对象的操作,其结果随后用作每个分组的标题; 最终具有相同“key”值的项将位于同一组中。

返回值类似于字典的可迭代对象,其形式为{key : value}

示例1

# note here that the tuple counts as one item in this list. I did not
# specify any key, so each item in the list is a key on its own.
c = groupby(['goat', 'dog', 'cow', 1, 1, 2, 3, 11, 10, ('persons', 'man', 'woman')])
dic = {}
for k, v in c:
    dic[k] = list(v)
dic

导致
{1: [1, 1],
 'goat': ['goat'],
 3: [3],
 'cow': ['cow'],
 ('persons', 'man', 'woman'): [('persons', 'man', 'woman')],
 10: [10],
 11: [11],
 2: [2],
 'dog': ['dog']}

例子2

# notice here that mulato and camel don't show up. only the last element with a certain key shows up, like replacing earlier result
# the last result for c actually wipes out two previous results.

list_things = ['goat', 'dog', 'donkey', 'mulato', 'cow', 'cat', ('persons', 'man', 'woman'), \
               'wombat', 'mongoose', 'malloo', 'camel']
c = groupby(list_things, key=lambda x: x[0])
dic = {}
for k, v in c:
    dic[k] = list(v)
dic

导致
{'c': ['camel'],
 'd': ['dog', 'donkey'],
 'g': ['goat'],
 'm': ['mongoose', 'malloo'],
 'persons': [('persons', 'man', 'woman')],
 'w': ['wombat']}

现在是排序后的版本。
 # but observe the sorted version where I have the data sorted first on same key I used for grouping
list_things = ['goat', 'dog', 'donkey', 'mulato', 'cow', 'cat', ('persons', 'man', 'woman'), \
               'wombat', 'mongoose', 'malloo', 'camel']
sorted_list = sorted(list_things, key = lambda x: x[0])
print(sorted_list)
print()
c = groupby(sorted_list, key=lambda x: x[0])
dic = {}
for k, v in c:
    dic[k] = list(v)
dic

导致
['cow', 'cat', 'camel', 'dog', 'donkey', 'goat', 'mulato', 'mongoose', 'malloo', ('persons', 'man', 'woman'), 'wombat']
{'c': ['cow', 'cat', 'camel'],
 'd': ['dog', 'donkey'],
 'g': ['goat'],
 'm': ['mulato', 'mongoose', 'malloo'],
 'persons': [('persons', 'man', 'woman')],
 'w': ['wombat']}

示例3

things = [("animal", "bear"), ("animal", "duck"), ("plant", "cactus"), ("vehicle", "harley"), \
          ("vehicle", "speed boat"), ("vehicle", "school bus")]
dic = {}
f = lambda x: x[0]
for key, group in groupby(sorted(things, key=f), f):
    dic[key] = list(group)
dic

导致
{'animal': [('animal', 'bear'), ('animal', 'duck')],
 'plant': [('plant', 'cactus')],
 'vehicle': [('vehicle', 'harley'),
  ('vehicle', 'speed boat'),
  ('vehicle', 'school bus')]}

现在是排序后的版本。这里我将元组更改为列表。两种方式结果都相同。
things = [["animal", "bear"], ["animal", "duck"], ["vehicle", "harley"], ["plant", "cactus"], \
          ["vehicle", "speed boat"], ["vehicle", "school bus"]]
dic = {}
f = lambda x: x[0]
for key, group in groupby(sorted(things, key=f), f):
    dic[key] = list(group)
dic

导致
{'animal': [['animal', 'bear'], ['animal', 'duck']],
 'plant': [['plant', 'cactus']],
 'vehicle': [['vehicle', 'harley'],
  ['vehicle', 'speed boat'],
  ['vehicle', 'school bus']]}

"itertools.groupby(iterable, key=None or some func)接受一个可迭代对象列表。" 它是接受一个可迭代对象列表,还是只接受一个可迭代对象?列表是可迭代对象。 - Tagc
文档并没有明确说明。但是从我发布的示例中,你可以看到我使用了列表和嵌套列表。因此它可以接受“可迭代对象”(示例1)以及“可迭代对象列表”(示例2)。你甚至可以传入一个单独的字符串,仍然可以正常运行。 - chidimo

11

像往常一样,首先应该查看函数文档。然而,itertools.groupby 绝对是最棘手的 itertools 之一,因为它有一些可能会出现问题的地方:

  • 仅当连续项目的 key 返回相同时才分组:

  • from itertools import groupby
    
    for key, group in groupby([1,1,1,1,5,1,1,1,1,4]):
        print(key, list(group))
    # 1 [1, 1, 1, 1]
    # 5 [5]
    # 1 [1, 1, 1, 1]
    # 4 [4]
    

    如果想要进行整体的groupby,可以先使用sorted

  • 它产生两个元素,第二个是迭代器(所以需要迭代第二个元素!)。在前面的例子中,我需要将它们明确地强制转换为list

  • 如果向前移动groupby迭代器,第二个生成的元素将被丢弃:

    it = groupby([1,1,1,1,5,1,1,1,1,4])
    key1, group1 = next(it)
    key2, group2 = next(it)
    print(key1, list(group1))
    # 1 []
    

    即使group1不为空!

正如已经提到的,可以使用sorted执行整体groupby操作,但这非常低效(并且如果您想在生成器上使用groupby则会浪费内存效率)。如果无法保证输入已排序(也不需要O(n log(n))的排序时间开销),则有更好的替代方法可用:

但是检查本地属性很棒。 在itertools-recipes部分中有两个配方:

def all_equal(iterable):
    "Returns True if all the elements are equal to each other"
    g = groupby(iterable)
    return next(g, True) and not next(g, False)

并且:

def unique_justseen(iterable, key=None):
    "List unique elements, preserving order. Remember only the element just seen."
    # unique_justseen('AAAABBBCCDAABBB') --> A B C D A B
    # unique_justseen('ABBCcAD', str.lower) --> A B C A D
    return map(next, map(itemgetter(1), groupby(iterable, key)))

谢谢。如果我需要一些替代方案,我一定会记下来的。现在我正在逐节阅读文档,以免混淆一切。祝你新年快乐。 - chidimo
这里有很棒的信息。collections.defaultdict 的文档提供了一个非常简单明了的示例,说明如何对值进行分组:https://docs.python.org/3/library/collections.html#defaultdict-examples - Mass Dot Net

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接