如果使用第三方包没问题,那么你可以使用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的作者。