在Python中删除列表中的重复dict

时间:2012-02-24 07:46:41

标签: python list dictionary

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

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

14 个答案:

答案 0 :(得分:172)

试试这个:

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

注意:正如@a​​lexis指出的那样,两个具有相同键和值的字典可能不会产生相同的元组。如果他们通过不同的添加/删除密钥历史记录,则可能发生这种情况如果您的问题就是这种情况,那么请考虑按​​照他的建议对d.items()进行排序。

答案 1 :(得分:37)

基于列表理解的另一个单行程序:

>>> 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访问,因此使用{ {1}})。

答案 2 :(得分:13)

有时旧式循环仍然有用。这段代码比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])

答案 3 :(得分:13)

如果您对嵌套字典(如反序列化的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]

答案 4 :(得分:9)

如果您想保留订单,那么您可以

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

答案 5 :(得分:4)

如果可以使用第三方软件包,则可以使用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是原始列表中的元素,而{{1 }}原始列表中的唯一元素,而不是m)。如果键和值都是可哈希的,则可以使用该函数的O(n)参数为“唯一性测试”创建可哈希的项(以便它在key中起作用)。

对于字典(其比较与顺序无关),您需要将其映射到另一个类似的数据结构,例如O(n)

frozenset

请注意,您不应该使用简单的>>> list(unique_everseen(l, key=lambda item: frozenset(item.items()))) [{'a': 123}, {'b': 123}] 方法(不进行排序),因为相等的字典不一定具有相同的顺序(即使在Python 3.7中,插入顺序-不是绝对订单-得到保证):

tuple

如果键不可排序,甚至对元组进行排序也可能不起作用:

>>> d1 = {1: 1, 9: 9}
>>> d2 = {9: 9, 1: 1}
>>> d1 == d2
True
>>> tuple(d1.items()) == tuple(d2.items())
False

基准

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

绝对时间:

enter image description here

相对于最快方法的时机:

enter image description here

thefourtheye中的第二种方法在这里最快。使用>>> d3 = {1: 1, 'a': 'a'} >>> tuple(sorted(d3.items())) TypeError: '<' not supported between instances of 'str' and 'int' 函数的unique_everseen方法排在第二位,但这是保留顺序的最快方法。 jcolladothefourtheye的其他方法几乎一样快。对于更长的列表,使用没有密钥的key的方法以及EmmanuelScorpil的解决方案速度很慢,并且对于unique_everseen而不是O(n*n)而言,其表现要差得多。 stpk使用O(n)的方法不是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)

enter image description here

除了没有# 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)} 功能的unique_everseen以外,时间没有明显变化,在这种情况下,这是最快的解决方案。但是,这是具有不可散列值的函数的最佳情况(因此不具有代表性),因为它的运行时取决于列表中唯一值的数量:key在这种情况下仅为1,因此它在{{ 1}}。


免责声明:我是O(n*m)的作者。

答案 6 :(得分:4)

如果您在工作流程中使用熊猫,一种选择是直接将字典列表提供给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}]

答案 7 :(得分:1)

不是通用答案,但是如果您的列表恰好被某个键排序,请执行以下操作:

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

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

答案 8 :(得分:1)

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

答案 9 :(得分:0)

您可以使用一组,但需要将dicts转换为可哈希类型。

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]

答案 10 :(得分:0)

不太短,但易于阅读:

list_of_data = [{'a': 123}, {'b': 123}, {'a': 123}]

list_of_data_uniq = []
    for dict in list_of_data:
        if dict not in list_of_data_uniq:
            list_of_data_uniq.append(dict)

现在列表“ list_of_data_uniq”将具有uniq字典。

答案 11 :(得分:-1)

我知道它可能不如其他答案那么优雅,但是尝试一下:

arts = list of dicts

arts_alt = []

arts_alt = [arts_alt.append(art) for art in arts if art not in arts_alt]

您需要的是arts_alt

答案 12 :(得分:-1)

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

答案 13 :(得分:-2)

这是一个快速的单行解决方案,具有双重嵌套的列表理解功能(基于@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所要的,但这是使我进入此线程的原因,所以我认为我应该发布最终得到的解决方案