【问题标题】:How to combine a nested dictionary with another nested dictionary, but only when each have a matching value?如何将嵌套字典与另一个嵌套字典组合,但仅当每个嵌套字典都有匹配值时?
【发布时间】:2019-01-09 19:21:46
【问题描述】:

描述我想要实现的最佳方式是参考 SQL 函数 INNER JOIN 如何用于显示两个表中的数据,由匹配的列名确定。

我想实现类似的功能,但通过使用 Python(最好是 3.x),而不是具有匹配列名的表,我想将两个字典的整体组合在一起,基于匹配 {k: v} 对。

例如...

lst_1 = [
    {
        'City'      :   'Boston',
        'State'     :   'Massechusets',
        'Name'      :   'Kim Tuttles',
        'Country'   :   'United State'
    },
    {
        'City'      :   'Portland',
        'Name'      :   'Larry Bird',
        'State'     :   'Oregon'
    },
    {
        'City'      :   'Chicago',
        'Name'      :   'John Jacobs',
        'State'     :   'Illinois'
    }
]

lst_2 = [
    {
        'Hobby'     :   'Tennis',
        'Build'     :   'Athletic',
        'Height'    :   'Six Feet, One Inch',
        'Name'      :   'Kim Tuttles',
        'Birthplace':   'Italy'
    },
    {
        'Name'      :   'John Jacobs',
        'Hobby'     :   'Baseball',
        'Build'     :   'Muscular',
        'Height'    :   'Five Feet, Eight Inches'
    }
]

我想找到一种方法来合并每个列表中的字典,但只能找到匹配的 {Key: Value} 对。结果应该是这样的......

merged_lst = [
    {
        'Hobby'     :   'Tennis',
        'Build'     :   'Athletic',
        'Height'    :   'Six Feet, One Inch',
        'Birthplace':   'Italy'
        'City'      :   'Boston',
        'State'     :   'Massechusets',
        'Name'      :   'Kim Tuttles', # Merge on matching name
        'Country'   :   'United State'
    },
    {
        'Name'      :   'John Jacobs', # Merge on matching name
        'Hobby'     :   'Baseball',
        'Build'     :   'Muscular',
        'Height'    :   'Five Feet, Eight Inches'
        'City'      :   'Chicago',
        'State'     :   'Illinois'
    }
]

我设法找到了一种使用dict.updatezip() 合并字典的方法,尽管这只是在处理两个独立的字典时,它仍然不太正确。感谢您的任何建议,并在此先感谢您。

【问题讨论】:

  • 你提前知道匹配键('Name'这里)吗?
  • 幸运的是,它在任一列表中的每个字典中都是一致的。因此,在上述情况下,如您所述,它将是 'Name'
  • 除了“名称”之外,还有其他重复的键吗?如果有,结果中哪个优先?
  • 不,匹配的 {Key: Value} 对的唯一出现将在 'Name' 上,但可以在列表中的任何深度、字典中的任何位置找到匹配项。有点像“嗯,它就在某处”之类的东西。
  • 是否可以假设每个列表中每个 'Name' 仅出现一次?

标签: python json python-3.x dictionary nested


【解决方案1】:

在 Python 3.5+ 中,我们可以避开以下问题,忽略其他键冲突的问题。

k = 'Name'
merged_lst = [{**a, **b} for a in lst_1 for b in lst_2 if a[k]==b[k]]
  • {**a, **b} 是一种巧妙的方法,可以将正在考虑的两个字典解包到一个组合字典中(我相信在冲突中它使用来自 b 的值而不是 a)。这是唯一需要 3.5+ 的步骤。在带有 string 键的 Python 2.x 中,类似的构造是 dict(a, **b),尽管 Guido 对此深表不满。其他选项更详细。
  • Python 列表推导式允许您通过使用两次for 轻松地遍历lst_1lst_2 的有效笛卡尔积。
  • 我们只关心具有相同'Name' 的字典,因此a[k]==b[k] 位。
  • 如果允许您破坏lst_1lst_2 中的任何字典,则涉及dict.update() 的方法可能更快。无论如何,它们可能是这样,尽管我认为语法没有那么好。

【讨论】:

  • 这个非常简洁。但是应该注意的是,在嵌套理解中,算法复杂度为O(m*n),而对于手头的问题,O(m+n) 是可能的。
  • @schwobaseggl 这肯定是个问题,但我很好奇你是怎么想到O(m+n)的?这是平均情况吗?在我的草稿工作中,我发现的最佳解决方案是O(m*log(m)+n*log(n)) 最坏的情况。在这里使用时间复杂度的最佳算法是什么?
  • 天哪,谢谢!这绝对完美!我的头撞墙了好几个小时!
  • @trjv 乐于助人:)
  • @HansMusgrave 没有数学上的可靠证明,但是将两个列表都转换为以名称为键的字典,然后迭代其中一个,检查另一个列表中是否存在键。当然,这忽略了要合并的字典的平均大小,这也是:)
【解决方案2】:

你可以这样做:

for l2 in lst_2:
   l2.update(next(l1 for l1 in lst_1 if l1["Name"] == l2["Name"]))

【讨论】:

    【解决方案3】:

    这类似于 MySQL 等 RDMS 中的 left join 和 MongoDB 的 $lookup(aggregation) 函数。您可以查看它们以获得进一步的说明。

    【讨论】:

      【解决方案4】:

      您可以创建一个函数来过滤重复名称的一个列表,将字典转移到结果列表中并在那里更新它们:

      功能:

      def mergeSameNameDicts(l1,l2):
      
          duplicateNames = set ( p["Name"] for p in l1) & set( p["Name"] for p in l2) 
      
          import copy 
          rv = []        # collects enriched dicts
          for d in l1:
              if d["Name"] in duplicateNames:
                  rv.append(copy.copy(d))           # copy dict over from l1
      
          for d in l2:                              # enhance with data from l2
              if (d["Name"] in duplicateNames):     # if name is a dupe. enhence all
                  for d1 in rv:                     # dicts with that name inside rv
                      if (d["Name"] == d1["Name"]): # the values of v2 will overwrite l1 if keys
                          d1.update(d)              # present in dicts of l1 and l2
          return rv
      
      print(mergeSameNameDicts(lst_1,lst_2))
      

      输出:

      [{'City': 'Boston',
        'State': 'Massechusets',
        'Name': 'Kim Tuttles',
        'Country': 'United State',
        'Hobby': 'Tennis',
        'Build': 'Athletic',
        'Height': 'Six Feett, One Inch',
        'Birthplace': 'Italy'},
      
       {'City': 'Chicago',
        'Name': 'John Jacobs',
        'State': 'Illinois',
        'Hobby': 'Baseball',
        'Build': 'Muscular',
        'Height': 'Five Feet, Eight Inches'}]
      

      【讨论】:

      • 有趣!你能解释一下set 在第 3 行做了什么吗?我还不熟悉那个功能。
      • @trjv set() 是一个无序的数据结构,本身没有重复。它允许快速的 set-theory-esc 操作,例如 union/differences/subset/superset/... 如果您有 2 个列表,每个列表有 200 个字典,并且两个列表中只有 20 个名称,这将返回那些确切的 20 个名称,因此以下循环只能循环 20 个 ppl 而不是全部 200 个,因为我们不需要它们。在此处阅读更多信息:SO Post about set()python set() doku
      猜你喜欢
      • 2018-01-29
      • 2019-04-06
      • 1970-01-01
      • 2021-10-20
      • 2020-07-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-13
      相关资源
      最近更新 更多