itertools.groupby()不能正确分组

30

我有这些数据:

self.data = [(1, 1, 5.0),
             (1, 2, 3.0),
             (1, 3, 4.0),
             (2, 1, 4.0),
             (2, 2, 2.0)]

当我运行以下代码时:
for mid, group in itertools.groupby(self.data, key=operator.itemgetter(0)):

对于 list(group),我得到:

[(1, 1, 5.0),
 (1, 2, 3.0),
 (1, 3, 4.0)]

这是我想要的。 但如果我使用1而不是0。
for mid, group in itertools.groupby(self.data, key=operator.itemgetter(1)):

要按元组中第二个数字进行分组,我只能得到:

[(1, 1, 5.0)]

即使在该位置(第二个位置)有其他元组也有“1”。
3个回答

58

itertools.groupby函数可以将具有相同键的连续的项收集在一起。 如果您想要具有相同键的所有项,则必须首先对self.data进行排序。

for mid, group in itertools.groupby(
    sorted(self.data,key=operator.itemgetter(1)), key=operator.itemgetter(1)):

我之前已经按照位置零进行了排序。所以在进行分组操作之前,我再次进行了排序,这样就可以了。self.data.sort(key=operator.itemgetter(1)) - user994165
6
不需要排序,你需要使用一个字典grouped = {} 然后 for v in self.data: grouped.setdefault(v[1], []).append(v)。排序是一个 O(NlogN) 的操作,而使用字典来分组值可以在 O(N) 时间内完成任务。 - Martijn Pieters

35

通过字典实现的不带排序的变体。在性能方面应该更好。

def full_group_by(l, key=lambda x: x):
    d = defaultdict(list)
    for item in l:
        d[key(item)].append(item)
    return d.items()

回来发同样的事情,我没有看到你的答案!这显然是正确的方法 :) - Andy Hayden
2
不幸的是,所有的键都必须是可哈希的,因此如果这些键是列表,就不能像使用itertools.groupby那样工作... - Jeronimo
3
@Jeronimo: 你可以尝试寻找一个可散列的键的映射,比如对于列表键使用tuple(),对于字典键使用frozenset(d.items())等。只有当这样做不可能时,才需要退而求其次,使用O(NlogN)的排序算法。使用字典来进行分组可以在线性(O(N))时间内完成任务。 - Martijn Pieters

1

以下内容“修复”了Python的itertools.groupby中的一些烦人问题。

def groupby2(l, key=lambda x:x, val=lambda x:x, agg=lambda x:x, sort=True):
    if sort:
        l = sorted(l, key=key)
    return ((k, agg((val(x) for x in v))) \
        for k,v in itertools.groupby(l, key=key))

具体来说,

  1. 它不要求您对数据进行排序。
  2. 它不要求您必须仅使用key作为命名参数。
  3. 输出是干净的生成器tuple(key, grouped_values),其中值由第三个参数指定。
  4. 轻松应用聚合函数如sum或avg的能力。

示例用法

import itertools
from operator import itemgetter
from statistics import *

t = [('a',1), ('b',2), ('a',3)]
for k,v in groupby2(t, itemgetter(0), itemgetter(1), sum):
  print(k, v)

这将打印,

a 4
b 2

使用此代码进行操作


2
为什么这些是“令人烦恼的问题”?groupby()函数允许你将连续匹配值组合成一组,但从来没有意图跨整个系列进行分组,因为这需要读取输入可迭代对象中的每个值。 itertools模块的核心用例就是尽可能避免消耗迭代器的所有值。 - Martijn Pieters
3
请注意,排序是有成本的:将N个项目排序为一个排序序列需要O(NlogN)的时间。另一方面,使用字典进行分组只需要线性时间(O(N))。您的“效用函数”消除了避免支付排序成本的选项,并且由于您没有使用关键字参数,因此任何阅读您的group2()调用的人都必须每次参考文档以了解所有参数的作用。 - Martijn Pieters
你的t最好使用from collections import defaultdict进行处理,summed = defaultdict(int),然后 for k, v in t: summed[k] += v,最后 for k, v in summed: print(k, v)。这样做更加直观清晰,而且可以在线性时间内完成,无需排序。 - Martijn Pieters
@MartijnPieters 这个例子只是为了演示。当然有更有效的方法来做这件事。 - Shital Shah
参见:more_itertools.groupby_transform(iterable, keyfunc=None, valuefunc=None, reducefunc=None)keyfunc类似于您的keyvaluefunc类似于您的valreducefunc类似于您的agg - Stef
我认为这些“烦恼”是相当合理的。如果你看看其他语言中类似的工具(比如 .NET LINQ 的 GroupBy),它们并没有这个要求。我认为大多数使用这个函数的用户都是将其应用于无序集合,因此仅出于必要而进行排序。考虑到默认行为现在基本上已经锁定,关键字参数是一种合理的方式来暴露这个问题。 - Siddhartha Gandhi

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