在列表列表中查找重复项
Posted
技术标签:
【中文标题】在列表列表中查找重复项【英文标题】:finding duplicates in a list of lists 【发布时间】:2013-11-17 15:37:20 【问题描述】:我正在使用 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])
从这里我需要搜索 original_list 中的所有 duplicates_list 项目并将值相加,但我不确定最好的方法是什么。
【问题讨论】:
请注意,到目前为止,大多数答案都没有保持键的顺序,这很重要吗?这样可以吗:[['b', 7],['a', 2],['c', 2]]
?
嗨,不,顺序无关紧要。对不起,我应该提到这一点。我将编辑问题。
Removing the lists from a list which are duplicated for some items的可能重复
@beroe 感谢您指出这个问题。之前搜索的时候没看到。它类似,但处理不同的匹配逻辑,不需要任何值的总和。
【参考方案1】:
很多很好的答案,但他们都使用了比我更多的代码,所以这是我的看法,因为它的价值:
totals =
for k,v in original_list:
totals[k] = totals.get(k,0) + v
# totals = 'a': 2, 'c': 2, 'b': 7
一旦你有了这样的字典,就可以从这些答案中的任何一个中使用items
来获取元组列表:
totals.items()
# => [('a', 2), ('c', 2), ('b', 7)]
并在元组之间映射list
以获得列表列表:
map(list, totals.items())
# => [['a', 2], ['c', 2], ['b', 7]]
如果你想要它们按顺序排序:
sorted(map(list, totals.items()))
# => [['a', 2], ['b', 7], ['c', 2]]
【讨论】:
他想要一个列表的结果。 获得您的 +1,为简单起见,除了转换为列表和设置测试用例之外,代码量几乎可以忽略不计 谢谢,这正是我想要做的,以这种方式解释代码有助于我更快地理解它。 可以使用totals = collections.defaultdict(int)
,然后只需使用totals[k] += v
@arshajii 它本身并没有什么问题,但是(如对该问题的各种答案所示)人们喜欢在不应该使用它的时候使用它只是让代码比它需要的更复杂。【参考方案2】:
>>> 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]]
【讨论】:
或:sum((Counter(**k:v) for k, v in lst), Counter())
请注意,如果列表包含一些像 ['a', 10000]
这样的键,那么这个解决方案将迭代 10000 次而不是简单的求和,这是低效的,在这种情况下,alko 的解决方案更好。
@hcwhsa 我认为我们不需要**
@kroolik 寻找性能测试(在我的回答中),我们已经超越了 :)
@alko,可能是因为我们使用的是 Counter
,与其他解决方案 (see repo) 相比,它完全用 Python 实现。如果性能很重要,我建议用 C 编写一个模块来正确地做到这一点:P【参考方案3】:
解决方案
使用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]]
发现
所以,有很多答案、观点和赞成票。我什至凭空获得了我的第一个Nice answer
(在过去的两天里,我做了很多值得更多研究和努力的答案)。有鉴于此,我决定用从头开始编写的simple script 至少做一些研究和测试解决方案的性能。不要为了大小而直接在答案中包含代码。
每个函数都以其作者命名,很容易找到相关问题。 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 解决方案被取消资格,因为任何合理数量的样本*迭代都将执行数小时。这是最后的测试。我手动向 ouptut 添加了赞成票数:
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 :) 似乎这个问题得到了一些关注:P 对我来说看起来最“pythonic”。 你为什么要result.update(k:v)
? SIt 等价于result[k] += v
,但开销更高(使用+=
可避免创建dict
以及带有查找的方法调用)。【参考方案4】:
如果顺序无关紧要,可以用这个
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,代码就变成了oneliner
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() 正如 georgesl 提到的,你应该使用defaultdict(int)
或dict.get
(如果你想使用普通的dict
)。我认为在分配可变默认值时应该使用dict.setdefault
。无论如何+1。【参考方案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)
效率更高【参考方案6】:
我知道这很丑,但尝试在 1 班轮中实现它时我很开心:
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]]
【讨论】:
【参考方案7】:也许你也可以试试这个,
>>> 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]]
【讨论】:
以上是关于在列表列表中查找重复项的主要内容,如果未能解决你的问题,请参考以下文章