在一个列表的列表中查找重复项

29

我是使用Python 2.7版本,试图去重一个列表中的子列表,并合并重复值。

目前我的代码如下:

original_list = [['a', 1], ['b', 1], ['a', 1], ['b', 1], ['b', 2], ['c', 2], ['b', 3]]

我希望能够匹配每个嵌套列表的第一个元素,然后将第二个元素的值相加。最终要得到以下结果(最终列表的顺序无关紧要):
ideal_output = [['a', 2], ['b', 7], ['c', 2]]

到目前为止,我已经有了一些代码可以根据每个嵌套列表的第一个元素找到重复的值:

for item in original_list:
    matches = -1
    for x in original_list:
        if (item[0] == x[0]):
            matches += 1
    if matches >= 1: 
        if item[0] not in duplicates_list:
            duplicates_list.append(item[0])

我需要在原始列表中搜索所有与重复项列表中相同的项,并将其值相加,但我不确定最好的方法是什么。


1
目前大部分答案没有保持键的顺序,这很重要吗?这样做可以吗:[['b', 7],['a', 2],['c', 2]] - georg
不好意思,顺序无关紧要。我应该提前说明的,我会编辑问题的。 - e h
可能是从列表中删除重复项的列表的重复。 - beroe
3
感谢@beroe指出该问题。在搜索之前我没有看到它。虽然它相似,但涉及不同的匹配逻辑,并且不需要对值进行求和。 - e h
7个回答

33
许多好的答案,但它们都使用比我更多的代码,所以这是我的看法,仅供参考:
totals = {}
for k,v in original_list:
  totals[k] = totals.get(k,0) + v

# totals = {'a': 2, 'c': 2, 'b': 7}

如果您有一个像这样的字典,从任何一个答案中,您可以使用items来获取一个(类似列表的)元组对象:

totals.items()
# => dict_items([('a', 2), ('c', 2), ('b', 7)])

使用 list 函数对元组进行处理,使其转换为列表的列表:

[list(t) for t in totals.items()]
# => [['a', 2], ['c', 2], ['b', 7]]

如果您想按顺序排列,请进行排序:

sorted([list(t) for t in totals.items()])
# => [['a', 2], ['b', 7], ['c', 2]]
 

他希望结果以列表形式呈现。 - thefourtheye
获取您的+1,为了简单起见,尽管代码量几乎相同,但除了将其转换为列表并设置测试用例外,没有进行任何更改。 - alko
3
谢谢,这正是我想做的事情,你这样解释代码帮助我更快地理解它。 - e h
2
可以使用 totals = collections.defaultdict(int),然后简单地执行 totals[k] += v - Neil G
@arshajii,这并没有本质上的问题,但是(正如在这个问题的各种答案中所展示的那样),人们喜欢在不应该使用它的时候使用它,这只会使代码变得比必要的更加复杂。 - Izkata

15
>>> from collections import Counter
>>> lst = [['a', 1], ['b', 1], ['a', 1], ['b', 1], ['b', 2], ['c', 2], ['b', 3]]
>>> c = Counter(x for x, c in lst for _ in xrange(c))

Counter({'b': 7, 'a': 2, 'c': 2})

>>> map(list, c.iteritems())
[['a', 2], ['c', 2], ['b', 7]]

或者,不重复每个项目(a, b) b次 (@hcwhsa):

>>> from collections import Counter
>>> lst = [['a', 1], ['b', 1], ['a', 1], ['b', 1], ['b', 2], ['c', 2], ['b', 3]]
>>> c = sum((Counter(**{k:v}) for k, v in lst), Counter())

Counter({'b': 7, 'a': 2, 'c': 2})

>>> map(list, c.iteritems())
[['a', 2], ['c', 2], ['b', 7]]

4
或:sum((Counter(**{k:v}) for k, v in lst), Counter())该代码使用Python语言编写,目的是对一个名为“lst”的列表进行计数并返回总计数。具体实现方式如下:
  1. 使用Python内置模块collections中的Counter()函数进行计数。
  2. 通过列表推导式和字典解包将列表中的元素作为键值传递给Counter()函数,并且将每个Counter()对象加起来并返回最终结果。
如果需要更详细的解释或说明,请告诉我。
- Ashwini Chaudhary
请注意,如果列表中包含像['a', 10000]这样的键,则该解决方案将迭代10000次,这是低效的。在这种情况下,alko的解决方案更好。 - Ashwini Chaudhary
2
@hcwhsa 我觉得我们不需要使用 ** - thefourtheye
@kroolik,请查看性能测试(在我的回答中),我们被超越了 :) - alko
@alko,也许是因为我们使用的是完全由Python实现的Counter,相比其他解决方案(请参见repo)。如果性能很重要,我建议编写一个C模块来正确处理它 :P - Maciej Gol

13

解决方案

使用collections.Counter

from collections import Counter
original_list = [['a', 1], ['b', 1], ['a', 1], ['b', 1], ['b', 2], ['c', 2], ['b', 3]]
result = Counter()
for k, v in original_list:
     result.update({k:v})

map(list, result.items())
# [['a', 2], ['c', 2], ['b', 7]]

研究结果

很多问题都得到了回答、观点和赞同。我甚至因为在过去的两天里回答了许多值得更多研究和努力的问题而获得了自己的第一个“不错的回答”。鉴于这一点,我决定至少进行一些研究,并使用从头编写的简单脚本测试解决方案的性能。请不要为了缩小回答的大小而直接包含代码。

每个函数都以作者的名字命名,并且可以在问题中轻松找到。thefourtheye的解决方案现在等同于Mark Reed的解决方案,并以原始形式进行评估,thefourtheye2代表基于itertools.groupby的解决方案。

每个函数都经过了多次测试(样本),每个样本依次调用了几次函数迭代。我对样本时间进行了最小值、最大值和标准偏差的评估。

我们开始吧,运行10次探测测试。

testing: thefourtheye, kroolik2, void, kroolik, alko, reed, visser
   10 samples
   10 iterations each
         author   min     avg     max    stddev
           reed 0.00000 0.00000 0.00000 0.00000
         visser 0.00000 0.00150 0.01500 0.00450
   thefourtheye 0.00000 0.00160 0.01600 0.00480
  thefourtheye2 0.00000 0.00310 0.01600 0.00620
           alko 0.00000 0.00630 0.01600 0.00772
           void 0.01500 0.01540 0.01600 0.00049
       kroolik2 0.04700 0.06430 0.07800 0.00831
        kroolik 0.32800 0.34380 0.37500 0.01716

看底部两行:此时,考虑到使用kroolik解决方案将会导致执行任何合理数量的样本*迭代数需要多达数小时,因此被取消资格。以下是最终测试结果。我手动添加了点赞数到输出中:

testing: thefourtheye, kroolik2, void, kroolik, alko, reed, visser
   100 samples
  1000 iterations each
         author  upvotes   min     avg     max    stddev
           reed  [20]    0.06200 0.08174 0.15600 0.01841
   thefourtheye   [5]    0.06200 0.09971 0.20300 0.01911
         visser   [6]    0.10900 0.12392 0.23500 0.02263
  thefourtheye2          0.25000 0.29674 0.89000 0.07183
           alko  [11]    0.56200 0.62309 1.04700 0.08438
           void   [3]    1.50000 1.65480 2.39100 0.18721
        kroolik  [14]     [DSQ]

为什么我刚发布这个答案就得到了+2的赞,甚至比kroolik还要早几秒钟呢? :) - alko
2
似乎这个问题引起了一些关注 :P - Maciej Gol
2
在我看来,这似乎是最符合 Python 风格的。 - georg
2
你为什么要使用 result.update({k:v})?这与 result[k] += v 等效,但是会有更高的开销(使用 += 可以避免创建字典和方法调用查找)。 - Bakuriu

10
如果顺序不重要,您可以使用此选项。
original_list = [['a', 1], ['b', 1], ['a', 1], ['b', 1], ['b', 2], ['c', 2], ['b', 3]]
myDict = {}
for first, second in original_list:
    myDict[first] = myDict.get(first, 0) + second
result = [[key, value] for key, value in myDict.items()]
print result

或者您可以使用 groupby,这样代码就只需要一行了。

original_list = [['a', 1], ['b', 1], ['a', 1], ['b', 1], ['b', 2], ['c', 2], ['b', 3]]
from itertools import groupby
print [[key, sum(item[1] for item in list(group))]
       for key, group in groupby(sorted(original_list), lambda x:x[0])]

输出

[['a', 2], ['b', 7], ['c', 2]]

使用collections.defaultdict,您可以摆脱setdefault()。 - lucasg
1
正如georgesl所提到的,你应该使用defaultdict(int)或者dict.get(如果你想使用普通的dict)。我认为当你分配一个可变的默认值时,应该使用dict.setdefault。无论如何,点赞+1。 - Ashwini Chaudhary

5

你可以使用 collections.defaultdict

original_list = [['a', 1], ['b', 1], ['a', 1], ['b', 1], ['b', 2], ['c', 2], ['b', 3]]
import collections
data = collections.defaultdict(list)
for item in original_list:
    data[item[0]].append(item[1])

output = {key: sum(values) for key, values in data.items()}
print output
# gives: {'a': 2, 'c': 2, 'b': 7}

使用defaultdict(int)更加高效。 - Neil G

5

我知道这很丑,但我试图在一个一行代码中实现它,只是为了好玩:

map(list, set(([(x[0], sum([i[1] for i in original_list if i[0]==x[0]])) for x in original_list])))

输出:

[['a', 2], ['b', 7], ['c', 2]]

4
也许你也可以尝试这个方法:
>>> x = [[1,1],[2,2],[1,1],[2,2],[3,3],[4,4],[4,4]]
>>> z = []
>>> for i in x:
>>>    if i not in z:
>>>        z.append(i)
>>>
>>> z
[[1, 1], [2, 2], [3, 3], [4, 4]] 

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