对Python元组列表进行分组

31
我有一个类似这样的(label,count)元组列表:

[('grape', 100), ('grape', 3), ('apple', 15), ('apple', 10), ('apple', 4), ('banana', 3)]

我希望对具有相同标签的所有值进行求和(相同标签始终相邻),并按照相同标签的顺序返回一个列表:

[('grape', 103), ('apple', 29), ('banana', 3)]
我知道我可以用像这样的东西来解决它:
def group(l):
    result = []
    if l:
        this_label = l[0][0]
        this_count = 0
        for label, count in l:
            if label != this_label:
                result.append((this_label, this_count))
                this_label = label
                this_count = 0
            this_count += count
        result.append((this_label, this_count))
    return result

但是是否有更符合Python语言风格、更优雅、更高效的方法来实现这个?

8个回答

44

itertools.groupby 可以实现你想要的功能:

import itertools
import operator

L = [('grape', 100), ('grape', 3), ('apple', 15), ('apple', 10),
     ('apple', 4), ('banana', 3)]

def accumulate(l):
    it = itertools.groupby(l, operator.itemgetter(0))
    for key, subiter in it:
       yield key, sum(item[1] for item in subiter) 

print(list(accumulate(L)))
# [('grape', 103), ('apple', 29), ('banana', 3)]

5
我喜欢使用operator.itemgetter代替lambda的用法。 - jathanism
9
这需要对列表按照第一个键进行排序。如果列表尚未排序,则ghostdog74提供的defaultdict方法是更好的解决方案。 - Martijn Pieters
1
为什么你会使用 operator 而不是 lambda - Adrian Guerra

8
使用itertools和列表推导式
import itertools

[(key, sum(num for _, num in value))
    for key, value in itertools.groupby(l, lambda x: x[0])]

编辑:正如gnibbler指出的那样:如果l尚未排序,请将其替换为sorted(l)


5
使用groupby前,必须确保序列已经被预分组(所有“葡萄”相邻等)。一种方法是先对序列进行排序。 - John La Rooy
@Thomas Wouters,是的,您是正确的(“相同的标签始终相邻”) - John La Rooy

6
import collections
d=collections.defaultdict(int)
a=[]
alist=[('grape', 100), ('banana', 3), ('apple', 10), ('apple', 4), ('grape', 3), ('apple', 15)]
for fruit,number in alist:
    if not fruit in a: a.append(fruit)
    d[fruit]+=number
for f in a:
    print (f,d[f])

输出

$ ./python.py
('grape', 103)
('banana', 3)
('apple', 29)

这会在alist中搜索每个项目,使您的算法为O(n^2),这不是一个好的选择。 - Shital Shah

5
>>> from itertools import groupby
>>> from operator import itemgetter
>>> L=[('grape', 100), ('grape', 3), ('apple', 15), ('apple', 10), ('apple', 4), ('banana', 3)]
>>> [(x,sum(map(itemgetter(1),y))) for x,y in groupby(L, itemgetter(0))]
[('grape', 103), ('apple', 29), ('banana', 3)]

4

没有使用itertools的代码版本
[(k, sum([y for (x,y) in l if x == k])) for k in dict(l).keys()]


1

方法

def group_by(my_list):
    result = {}
    for k, v in my_list:
        result[k] = v if k not in result else result[k] + v
    return result 

使用
my_list = [
    ('grape', 100), ('grape', 3), ('apple', 15),
    ('apple', 10), ('apple', 4), ('banana', 3)
]

group_by(my_list) 

# Output: {'grape': 103, 'apple': 29, 'banana': 3}

你需要将其转换为元组列表,例如list(group_by(my_list).items())

0

或者一个更简单易读的答案(不使用itertools):

pairs = [('foo',1),('bar',2),('foo',2),('bar',3)]

def sum_pairs(pairs):
  sums = {}
  for pair in pairs:
    sums.setdefault(pair[0], 0)
    sums[pair[0]] += pair[1]
  return sums.items()

print sum_pairs(pairs)

0

没有使用第三方库的更简单的答案:

dct={}

for key,value in alist:
    if key not in dct:
        dct[key]=value
    else:
        dct[key]+=value

我这里没有看到任何第三方库。itertoolsoperatorcollections都是Python标准库的一部分,它们随Python一起提供。 - Chris

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