在Python中删除列表中的重复字典

我有一个字典列表,我想删除字典具有相同的键和值对。

对于这个列表:[{'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}]

198986 次浏览

试试这个:

[dict(t) for t in {tuple(d.items()) for d in l}]

策略是将字典列表转换为元组列表,其中元组包含字典的项。由于元组可以散列,所以可以使用set(此处使用设置的理解,旧的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()

可以使用set,但需要将字典转换为可哈希类型。

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]

有时老式的循环仍然有用。这段代码比jcollado的稍长,但非常容易阅读:

a = [{'a': 123}, {'b': 123}, {'a': 123}]
b = []
for i in range(len(a)):
if a[i] not in a[i+1:]:
b.append(a[i])

另一个基于列表推导式的一行代码:

>>> 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)。

如果你想维护骑士团,那你可以这么做

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}]

如果您操作的是嵌套字典,例如反序列化的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]

不是一个普遍的答案,但如果你的列表恰好是排序的某个键,就像这样:

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}]

使用嵌套字典并(显然)保持顺序。

如果使用第三方包是可以的,那么你可以使用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'

基准

我认为比较一下这些方法的性能可能会有用,所以我做了一个小的基准测试。基准图是基于不包含重复项的列表的时间与列表大小(该列表是任意选择的,如果添加一些或大量重复项,运行时不会发生显著变化)。这是一个对数对数图,所以涵盖了整个范围。

绝对时间:

enter image description here

与最快方法相关的时间:

enter image description here

来自thefourtheye的第二个方法在这里是最快的。带有key函数的unique_everseen方法位于第二位,但它是保持顺序的最快方法。来自jcolladothefourtheye的其他方法几乎一样快。使用不带键的unique_everseen的方法以及来自key1和key2的解决方案对于较长的列表非常慢,并且O(n*n)O(n)表现得更差。使用jsonkey3s方法不是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)}

enter image description here

除了没有key函数的unique_everseen之外,计时没有显著变化,在这种情况下,这是最快的解决方案。然而,对于具有不可哈希值的函数来说,这只是最佳情况(所以不具有代表性),因为它的运行时间取决于列表中唯一值的数量:O(n*m),在这种情况下仅为1,因此它运行在O(n)中。


声明:我是iteration_utilities的作者。

如果你在工作流程中使用Pandas,一种选择是直接将字典列表提供给pd.DataFrame构造函数。然后使用drop_duplicatesto_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}]

不是很短,但很容易读:

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 list_of_data_uniq将拥有唯一的字典。

下面是一个带有双嵌套列表理解的快速单行解决方案(基于@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要求的,但这是让我来到这个帖子的原因,所以我想我应该发布我最终得到的解决方案

有很多搜索重复值和键的好例子,下面是我们在列表中过滤整个字典重复数据的方法。如果您的源数据是由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"}]
    

最简单的方法是将列表中的每一项转换为字符串,因为字典是不可哈希的。然后可以使用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}]

使用自定义键删除重复项:

def remove_duplications(arr, key):
return list({key(x): x for x in arr}.values())

如果你不关心规模和疯狂的性能,简单的func:

# Filters dicts with the same value in unique_key
# in: [{'k1': 1}, {'k1': 33}, {'k1': 1}]
# out: [{'k1': 1}, {'k1': 33}]
def remove_dup_dicts(list_of_dicts: list, unique_key) -> list:
unique_values = list()
unique_dicts = list()
for obj in list_of_dicts:
val = obj.get(unique_key)
if val not in unique_values:
unique_values.append(val)
unique_dicts.append(obj)
return unique_dicts

({'a': 123, 'b': 1234} input_list = {' a ': 3222, ' b ': 1234}, {'a': 123, 'b': 1234}]

# 输出要求 =比;[{'a': 123, 'b': 1234}, {'a': 3222, 'b': 1234}]

#代码

列表= [{' a ': 123, ' b ': 1234}, {' a ': 3222, ' b ': 1234}, {' a ': 123, ' b ': 1234}]

Empty_list = []

表内项目:

if item not in empty_list:


empty_list.append(item)

打印("previous list =",list)

print("Updated list =",empty_list)

#输出

之前的列表= [{' a ': 123, ' b ': 1234}, {' a ': 3222, ' b ': 1234}, {' a ': 123, ' b ': 1234}]

更新列表= [{' a ': 123, ' b ': 1234}, {' a ': 3222, ' b ': 1234}]