【问题标题】:Remove duplicate dict in list in Python在 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}]

【问题讨论】:

  • 你能告诉我们更多关于你试图解决的实际问题吗?这似乎是一个奇怪的问题。
  • 我正在组合一些字典列表,并且有重复项。所以我需要删除那些重复项。
  • 我在stackoverflow.com/questions/480214/…的答案中找到了一个解决方案,没有使用set()
  • @gfortune 我在现实生活中遇到了这个问题,其中有一个大型 ETL 脚本,该脚本将要上传的数据作为字典列表进行排队。有时,Scope A 中的多条记录会从 Scope B 中引入相同的记录,但无需将冗余输出上传到外部系统。

标签: python list dictionary


【解决方案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}]

注意:正如@a​​lexis 所指出的,具有相同键和值的两个字典可能不会产生相同的元组。如果他们经历不同的添加/删除密钥历史,则可能会发生这种情况。如果您的问题属于这种情况,请考虑按照他的建议对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 方法排在第二位,但它是保持顺序的最快方法。 jcolladothefourtheye 的其他方法几乎一样快。使用不带密钥的unique_everseen 的方法以及来自EmmanuelScorpil 的解决方案对于较长的列表非常慢,并且表现得更差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])
    

    【讨论】:

    • 0in range(0, len(a)) 不是必需的。
    【解决方案6】:

    如果您在工作流程中使用 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}]
    

    【讨论】:

      【解决方案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 将具有唯一的字典。

              【讨论】:

                猜你喜欢
                • 2020-03-03
                • 2011-10-28
                • 2017-08-13
                • 1970-01-01
                • 2019-01-09
                • 2022-11-27
                • 2016-03-14
                • 2013-09-01
                • 2013-01-23
                相关资源
                最近更新 更多