【问题标题】:Compare two large dictionaries and create lists of values for keys they have in common比较两个大字典并为它们共同的键创建值列表
【发布时间】:2016-08-06 07:56:36
【问题描述】:

我有两个字典,例如:

dict1 = { (1,2) : 2, (2,3): 3, (1,3): 3}
dict2 = { (1,2) : 1, (1,3): 2}

我想要输出的是两个字典中存在的项目的两个值列表:

[2,3]
[1,2]

我现在正在做的事情是这样的:

list1 = []
list2 = []

for key in dict1.keys():
    if key in dict2.keys():
        list1.append(dict1.get(key))
        list2.append(dict2.get(key))

这段代码运行时间过长,这不是我所期待的。我想知道是否有更有效的方法?

【问题讨论】:

    标签: python dictionary


    【解决方案1】:

    这应该使用 python3 中的 keys 和 python2 中的 viewkeys 来完成。这些是行为类似于集合的视图对象,构建它们不需要额外的努力......它们只是底层字典键的“视图”。这样您就可以节省 set 对象的构造。

    common = dict1.viewkeys() & dict2.viewkeys()
    list1 = [dict1[k] for k in common]
    list2 = [dict2[k] for k in common]
    

    dict_views 对象可以直接与字典相交,因此下面的代码也可以工作。不过我更喜欢以前的示例。

    common = dict1.viewkeys() & dict2
    

    【讨论】:

      【解决方案2】:

      您可以在zip() 函数中使用列表推导:

      >>> vals1, vals2 = zip(*[(dict1[k], v) for k, v in dict2.items() if k in dict1])
      >>> 
      >>> vals1
      (2, 3)
      >>> vals2
      (1, 2)
      

      或者作为使用视图对象和operator.itemgetter() 的更实用的方法,您可以这样做:

      >>> from operator import itemgetter
      >>> intersect = dict1.viewkeys() & dict2.viewkeys()
      >>> itemgetter(*intersect)(dict1)
      (2, 3)
      >>> itemgetter(*intersect)(dict2)
      (1, 2)
      

      接受答案的基准测试:

      from timeit import timeit
      
      
      inp1 = """
      commons = set(dict1).intersection(set(dict2))
      list1 = [dict1[k] for k in commons]
      list2 = [dict2[k] for k in commons]
         """
      
      inp2 = """
      zip(*[(dict1[k], v) for k, v in dict2.items() if k in dict1])
         """
      inp3 = """
      intersect = dict1.viewkeys() & dict2.viewkeys()
      itemgetter(*intersect)(dict1)
      itemgetter(*intersect)(dict2)
      """
      dict1 = {(1, 2): 2, (2, 3): 3, (1, 3): 3}
      dict2 = {(1, 2): 1, (1, 3): 2}
      print 'inp1 ->', timeit(stmt=inp1,
                              number=1000000,
                              setup="dict1 = {}; dict2 = {}".format(dict1, dict2))
      print 'inp2 ->', timeit(stmt=inp2,
                              number=1000000,
                              setup="dict1 = {}; dict2 = {}".format(dict1, dict2))
      print 'inp3 ->', timeit(stmt=inp3,
                              number=1000000,
                              setup="dict1 = {}; dict2 = {};from operator import itemgetter".format(dict1, dict2))
      

      输出:

      inp1 -> 0.000132083892822
      inp2 -> 0.000128984451294
      inp3 -> 0.000160932540894
      

      对于长度为 10000 的字典和随机生成的项目,在 100 循环中:

      inp1 -> 1.18336105347
      inp2 -> 1.00519990921
      inp3 -> 1.52266311646
      

      编辑:

      正如@Davidmh 在评论中提到拒绝为第二种方法引发异常,您可以将代码包装在try-except 表达式中:

      try:
          intersect = dict1.viewkeys() & dict2.viewkeys()
          vals1 = itemgetter(*intersect)(dict1)
          vals2 = itemgetter(*intersect)(dict2)
      except TypeError:
          vals1 = vals2 = []
      

      【讨论】:

      • 我认为接受答案的可读性超过了约 10% 的时间差异;-)
      • 您从不测试第三个选项,只测试第二个选项两次。另外,如果没有公共键,第三个选项会崩溃。
      • @Davidmh 是的,我只是更新答案。感谢您的评论。
      【解决方案3】:

      不要使用dict.keys。在 python2.x 上,它每次被调用时都会创建一个新列表(这是一个 O(N) 操作——平均而言,list.__contains__ 是另一个 O(N) 操作)。只需依赖字典是直接可迭代的容器这一事实(使用O(1) 查找):

      list1 = []
      list2 = []
      
      for key in dict1:
          if key in dict2:
              list1.append(dict1.get(key))
              list2.append(dict2.get(key))
      

      注意在python2.7上,可以使用viewkeys直接获取交集:

      >>> a = {'foo': 'bar', 'baz': 'qux'}
      >>> b = {'foo': 'bar'}
      >>> a.viewkeys() & b
      set(['foo'])
      

      (在 python3.x 上,您可以在此处使用keys 而不是viewkeys

      for key in dict1.viewkeys() & dict2:
          list1.append(dict1[key]))
          list2.append(dict2[key]))
      

      【讨论】:

      • 我真的更喜欢这种方法,因为它避免使用额外的数据结构并很好地利用了dict的对象方法。
      • 在比较两个以上的词典时非常有用。谢谢
      • 为什么还需要使用.keys(),为什么只用一本字典就足够了?
      • @PiotrDobrogost -- 在我看来,在 python2.x 上,几乎没有充分的理由使用 .keys()。如果你想要一个字典键的列表,那么你可以使用list(d),因为它也适用于 python3.x。如果您想遍历字典的键,只需执行for key in d: ...。在 python3.x 上,d.keys 与 python2.7 中的d.viewkeys 做同样的事情。该方法 很有用,因为它更高效,并且在很多方面表现得像 set。 (请参阅上面的示例以计算 2 个字典之间的公共键)。
      • @mgilson 我在问你为什么打电话给viewkeys()/keys() dict1 而不是dict2& 运算符也不应该自动为每个容器工作,这样就可以只调用dict1 & dict2
      【解决方案4】:
      commons = set(dict1).intersection(set(dict2))
      list1 = [dict1[k] for k in commons]
      list2 = [dict2[k] for k in commons]
      

      【讨论】:

      • 我正要回答这样的问题。打败我吧! :)
      • 领先我一秒。但是您不需要按键调用或第二组调用。
      • @BlackBear -- 您实际上可以在不构造额外集合的情况下获得交叉点。 common = dict1.viewkeys() & dict2 在 python2.x 上,或 common - dict1.keys() & dict2 在 python3.x 上
      • @BlackBear 谢谢,工作就像一个魅力。您可能希望将“common”重命名为“commons”或反之。
      • @mgilson 也感谢您的提示 :)(我不会编辑,因为您的答案中已经包含所有内容)
      猜你喜欢
      • 2019-01-03
      • 2015-01-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-18
      相关资源
      最近更新 更多