在 Python 中删除列表中的重复字典
Posted
技术标签:
【中文标题】在 Python 中删除列表中的重复字典【英文标题】:Remove duplicate dict in list in Python 【发布时间】:2012-03-14 16:35:39 【问题描述】:我有一个字典列表,我想删除具有相同键值对的字典。
对于这个列表:['a': 123, 'b': 123, 'a': 123]
我想退回这个:['a': 123, 'b': 123]
另一个例子:
对于这个列表:['a': 123, 'b': 1234, 'a': 3222, 'b': 1234, 'a': 123, 'b': 1234]
我想退回这个:['a': 123, 'b': 1234, 'a': 3222, 'b': 1234]
【问题讨论】:
你能告诉我们更多关于你试图解决的实际问题吗?这似乎是一个奇怪的问题。 我正在组合一些字典列表,并且有重复项。所以我需要删除那些重复项。 我在***.com/questions/480214/…的答案中找到了一个解决方案,没有使用set()
@gfortune 我在现实生活中遇到了这个问题,其中有一个大型 ETL 脚本,该脚本将要上传的数据作为字典列表进行排队。有时,Scope A 中的多条记录会从 Scope B 中引入相同的记录,但无需将冗余输出上传到外部系统。
【参考方案1】:
试试这个:
[dict(t) for t in tuple(d.items()) for d in l]
策略是将字典列表转换为元组列表,其中元组包含字典项。由于可以对元组进行散列处理,您可以使用 set
删除重复项(在此处使用 set comprehension,较旧的 python 替代方案将是 set(tuple(d.items()) for d in l)
),然后从元组重新创建字典dict
。
地点:
l
是原始列表
d
是列表中的字典之一
t
是从字典创建的元组之一
编辑:如果您想保留排序,上面的单行将不起作用,因为set
不会这样做。但是,只需几行代码,您也可以做到这一点:
l = ['a': 123, 'b': 1234,
'a': 3222, 'b': 1234,
'a': 123, 'b': 1234]
seen = set()
new_l = []
for d in l:
t = tuple(d.items())
if t not in seen:
seen.add(t)
new_l.append(d)
print new_l
示例输出:
['a': 123, 'b': 1234, 'a': 3222, 'b': 1234]
注意:正如@alexis 所指出的,具有相同键和值的两个字典可能不会产生相同的元组。如果他们经历不同的添加/删除密钥历史,则可能会发生这种情况。如果您的问题属于这种情况,请考虑按照他的建议对d.items()
进行排序。
【讨论】:
不错的解决方案,但它有一个错误:d.items()
不能保证以特定顺序返回元素。您应该执行tuple(sorted(d.items()))
以确保您不会为相同的键值对获得不同的元组。
@alexis 我做了一些测试,你确实是对的。如果在两者之间添加了很多键并稍后删除,那么可能就是这种情况。非常感谢您的评论。
注意,如果你像我一样从json
模块加载字典列表,这将不起作用
在这种情况下这是一个有效的解决方案,但在嵌套字典的情况下不起作用
它说“TypeError: unhashable type: 'list'”对于步骤“if t not in seen:”【参考方案2】:
另一个基于列表理解的单行:
>>> d = ['a': 123, 'b': 123, 'a': 123]
>>> [i for n, i in enumerate(d) if i not in d[n + 1:]]
['b': 123, 'a': 123]
这里因为我们可以使用dict
比较,我们只保留不在初始列表其余部分的元素(这个概念只能通过索引n
访问,因此使用enumerate
)。
【讨论】:
这也适用于字典列表,其中包含与第一个答案相比的列表 这也适用于您的字典中可能有不可散列的类型作为值,这与最佳答案不同。 这里,目的是删除重复值,而不是键,看这个答案的代码 这是非常低效的代码。if i not in d[n + 1:]
遍历整个字典列表(来自n
,但这只是操作总数的一半)并且您正在检查字典中的每个元素,所以这段代码是 O(n^2) 时间复杂性
不适用于以字典为值的字典【参考方案3】:
如果使用第三方包没问题,那么你可以使用iteration_utilities.unique_everseen
:
>>> from iteration_utilities import unique_everseen
>>> l = ['a': 123, 'b': 123, 'a': 123]
>>> list(unique_everseen(l))
['a': 123, 'b': 123]
它保留了原始列表的顺序,并且 ut 还可以通过使用较慢的算法来处理字典等不可散列的项目(O(n*m)
其中n
是原始列表中的元素,m
是列表中的唯一元素原始列表而不是 O(n)
)。如果键和值都是可散列的,您可以使用该函数的 key
参数为“唯一性测试”创建可散列项(以便它在 O(n)
中工作)。
对于字典(独立于顺序进行比较),您需要将其映射到另一个类似这样比较的数据结构,例如frozenset
:
>>> list(unique_everseen(l, key=lambda item: frozenset(item.items())))
['a': 123, 'b': 123]
请注意,您不应该使用简单的 tuple
方法(没有排序),因为相等的字典不一定具有相同的顺序(即使在 Python 3.7 中 插入顺序 - 不是绝对顺序- 保证):
>>> d1 = 1: 1, 9: 9
>>> d2 = 9: 9, 1: 1
>>> d1 == d2
True
>>> tuple(d1.items()) == tuple(d2.items())
False
如果键不可排序,甚至对元组进行排序也可能不起作用:
>>> d3 = 1: 1, 'a': 'a'
>>> tuple(sorted(d3.items()))
TypeError: '<' not supported between instances of 'str' and 'int'
基准测试
我认为比较这些方法的性能可能会有用,因此我做了一个小型基准测试。基准图是基于不包含重复项的列表的时间与列表大小(这是任意选择的,如果我添加一些或大量重复项,运行时不会发生显着变化)。这是一个对数图,因此涵盖了整个范围。
绝对次数:
相对于最快方法的时间:
thefourtheye 的第二种方法在这里最快。带有key
函数的unique_everseen
方法排在第二位,但它是保持顺序的最快方法。 jcollado 和 thefourtheye 的其他方法几乎一样快。使用不带密钥的unique_everseen
的方法以及来自Emmanuel 和Scorpil 的解决方案对于较长的列表非常慢,并且表现得更差O(n*n)
而不是O(n)
。 stpks 与 json
的方法不是 O(n*n)
,但它比类似的 O(n)
方法慢得多。
重现基准的代码:
from simple_benchmark import benchmark
import json
from collections import OrderedDict
from iteration_utilities import unique_everseen
def jcollado_1(l):
return [dict(t) for t in tuple(d.items()) for d in l]
def jcollado_2(l):
seen = set()
new_l = []
for d in l:
t = tuple(d.items())
if t not in seen:
seen.add(t)
new_l.append(d)
return new_l
def Emmanuel(d):
return [i for n, i in enumerate(d) if i not in d[n + 1:]]
def Scorpil(a):
b = []
for i in range(0, len(a)):
if a[i] not in a[i+1:]:
b.append(a[i])
def stpk(X):
set_of_jsons = json.dumps(d, sort_keys=True) for d in X
return [json.loads(t) for t in set_of_jsons]
def thefourtheye_1(data):
return OrderedDict((frozenset(item.items()),item) for item in data).values()
def thefourtheye_2(data):
return frozenset(item.items()):item for item in data.values()
def iu_1(l):
return list(unique_everseen(l))
def iu_2(l):
return list(unique_everseen(l, key=lambda inner_dict: frozenset(inner_dict.items())))
funcs = (jcollado_1, Emmanuel, stpk, Scorpil, thefourtheye_1, thefourtheye_2, iu_1, jcollado_2, iu_2)
arguments = 2**i: ['a': j for j in range(2**i)] for i in range(2, 12)
b = benchmark(funcs, arguments, 'list size')
%matplotlib widget
import matplotlib as mpl
import matplotlib.pyplot as plt
plt.style.use('ggplot')
mpl.rcParams['figure.figsize'] = '8, 6'
b.plot(relative_to=thefourtheye_2)
为了完整起见,这里是仅包含重复项的列表的时间:
# this is the only change for the benchmark
arguments = 2**i: ['a': 1 for j in range(2**i)] for i in range(2, 12)
除了没有key
函数的unique_everseen
之外,时间没有显着变化,在这种情况下这是最快的解决方案。然而,这只是具有不可散列值的该函数的最佳情况(因此不具有代表性),因为它的运行时间取决于列表中唯一值的数量:O(n*m)
,在这种情况下仅为 1,因此它在O(n)
中运行。
免责声明:我是iteration_utilities
的作者。
【讨论】:
【参考方案4】:如果您对嵌套字典(例如反序列化 JSON 对象)进行操作,其他答案将不起作用。对于这种情况,您可以使用:
import json
set_of_jsons = json.dumps(d, sort_keys=True) for d in X
X = [json.loads(t) for t in set_of_jsons]
【讨论】:
太棒了!诀窍是dict对象不能直接添加到集合中,需要通过dump()将其转换为json对象。【参考方案5】:有时旧式循环仍然有用。这段代码比 jcollado 的略长,但非常容易阅读:
a = ['a': 123, 'b': 123, 'a': 123]
b = []
for i in range(0, len(a)):
if a[i] not in a[i+1:]:
b.append(a[i])
【讨论】:
0
in range(0, len(a))
不是必需的。【参考方案6】:
如果您在工作流程中使用 Pandas,一种选择是将字典列表直接提供给 pd.DataFrame
构造函数。然后使用drop_duplicates
和to_dict
方法获得所需的结果。
import pandas as pd
d = ['a': 123, 'b': 1234, 'a': 3222, 'b': 1234, 'a': 123, 'b': 1234]
d_unique = pd.DataFrame(d).drop_duplicates().to_dict('records')
print(d_unique)
['a': 123, 'b': 1234, 'a': 3222, 'b': 1234]
【讨论】:
【参考方案7】:如果您想保留订单,那么您可以这样做
from collections import OrderedDict
print OrderedDict((frozenset(item.items()),item) for item in data).values()
# ['a': 123, 'b': 1234, 'a': 3222, 'b': 1234]
如果顺序无关紧要,那么你可以这样做
print frozenset(item.items()):item for item in data.values()
# ['a': 3222, 'b': 1234, 'a': 123, 'b': 1234]
【讨论】:
注意:在 python 3 中,您的第二种方法提供了不可序列化的dict_values
输出而不是列表。您必须再次将整个内容放入列表中。 list(frozen.....)
【参考方案8】:
不是一个通用的答案,但如果您的列表恰好按某个键排序,如下所示:
l=['a': 'b': 31, 't': 1,
'a': 'b': 31, 't': 1,
'a': 'b': 145, 't': 2,
'a': 'b': 25231, 't': 2,
'a': 'b': 25231, 't': 2,
'a': 'b': 25231, 't': 2,
'a': 'b': 112, 't': 3]
那么解决方法就这么简单:
import itertools
result = [a[0] for a in itertools.groupby(l)]
结果:
['a': 'b': 31, 't': 1,
'a': 'b': 145, 't': 2,
'a': 'b': 25231, 't': 2,
'a': 'b': 112, 't': 3]
适用于嵌套字典并(显然)保持顺序。
【讨论】:
这甚至适用于带有列表的字典。【参考方案9】:你可以使用一个集合,但是你需要把字典变成一个可散列的类型。
seq = ['a': 123, 'b': 1234, 'a': 3222, 'b': 1234, 'a': 123, 'b': 1234]
unique = set()
for d in seq:
t = tuple(d.iteritems())
unique.add(t)
现在唯一等于
set([(('a', 3222), ('b', 1234)), (('a', 123), ('b', 1234))])
要恢复字典:
[dict(x) for x in unique]
【讨论】:
d.iteritems()
的顺序无法保证 - 因此您可能会在 unique
中出现“重复”。【参考方案10】:
最简单的方法是将列表中的每个项目转换为字符串,因为字典不可散列。然后您可以使用 set 删除重复项。
list_org = ['a': 123, 'b': 123, 'a': 123]
list_org_updated = [ str(item) for item in list_org]
print(list_org_updated)
["'a': 123", "'b': 123", "'a': 123"]
unique_set = set(list_org_updated)
print(unique_set)
"'b': 123", "'a': 123"
您可以使用该集合,但如果您确实想要一个列表,请添加以下内容:
import ast
unique_list = [ast.literal_eval(item) for item in unique_set]
print(unique_list)
['b': 123, 'a': 123]
【讨论】:
【参考方案11】:通过自定义键删除重复项:
def remove_duplications(arr, key):
return list(key(x): x for x in arr.values())
【讨论】:
【参考方案12】:这是一个具有双重嵌套列表理解的快速单行解决方案(基于 @Emmanuel 的解决方案)。
这使用每个字典中的单个键(例如,a
)作为主键,而不是检查整个字典是否匹配
[i for n, i in enumerate(list_of_dicts) if i.get(primary_key) not in [y.get(primary_key) for y in list_of_dicts[n + 1:]]]
这不是 OP 要求的,但正是它把我带到了这个帖子,所以我想我会发布我最终得到的解决方案
【讨论】:
【参考方案13】:很多搜索重复值和键的好例子,下面是我们过滤列表中整个字典重复数据的方法。如果您的源数据由 EXACT 格式的字典组成并查找重复项,请使用 dupKeys = []。否则将 dupKeys = 设置为您希望没有重复条目的数据的键名,可以是 1 到 n 个键。它不是优雅的,但可以工作并且非常灵活
import binascii
collected_sensor_data = ["sensor_id":"nw-180","data":"XXXXXXX",
"sensor_id":"nw-163","data":"ZYZYZYY",
"sensor_id":"nw-180","data":"XXXXXXX",
"sensor_id":"nw-97", "data":"QQQQQZZ"]
dupKeys = ["sensor_id", "data"]
def RemoveDuplicateDictData(collected_sensor_data, dupKeys):
checkCRCs = []
final_sensor_data = []
if dupKeys == []:
for sensor_read in collected_sensor_data:
ck1 = binascii.crc32(str(sensor_read).encode('utf8'))
if not ck1 in checkCRCs:
final_sensor_data.append(sensor_read)
checkCRCs.append(ck1)
else:
for sensor_read in collected_sensor_data:
tmp = ""
for k in dupKeys:
tmp += str(sensor_read[k])
ck1 = binascii.crc32(tmp.encode('utf8'))
if not ck1 in checkCRCs:
final_sensor_data.append(sensor_read)
checkCRCs.append(ck1)
return final_sensor_data
final_sensor_data = ["sensor_id":"nw-180","data":"XXXXXXX",
"sensor_id":"nw-163","data":"ZYZYZYY",
"sensor_id":"nw-97", "data":"QQQQQZZ"]
【讨论】:
【参考方案14】:不是那么短,但很容易阅读:
list_of_data = ['a': 123, 'b': 123, 'a': 123]
list_of_data_uniq = []
for data in list_of_data:
if data not in list_of_data_uniq:
list_of_data_uniq.append(data)
现在,列表list_of_data_uniq
将具有唯一的字典。
【讨论】:
以上是关于在 Python 中删除列表中的重复字典的主要内容,如果未能解决你的问题,请参考以下文章