【问题标题】:How to zip three lists into a nested dict如何将三个列表压缩到嵌套字典中
【发布时间】:2018-12-20 14:55:27
【问题描述】:

我有三个列表:

z1 = ['A', 'A', 'B', 'B']
z2 = ['k1', 'k2', 'k1', 'k2']
z3 = ['v1', 'v2', 'v3', 'v4']

当我写的时候:

print(dict(zip(z2, z3)))

这是我的输出:

{'k2': 'v4', 'k1': 'v3'}

我希望这样:

{'A':{'k1': 'v1', 'k2': 'v2'} , 'B':{'k1': 'v3', 'k2': 'v4'}}

您能告诉我如何获得预期的结果吗?

【问题讨论】:

  • @akozi 我猜这是因为 OP 的“尝试”并不是真正的解决方案尝试。
  • 您的问题是“为什么dict(zip(z2, z3)) 没有产生所需的输出”还是“如何产生所需的输出”?
  • 这个问题在最后一行很明显地说明了。我愿意假设 OP 知道可以以某种方式将列表压缩在一起以创建 dict,但无法弄清楚如何处理嵌套方面。
  • 如果是重复的就很难找到了。
  • @chepner 是的,我知道我可以压缩列表,但我只是认为它只适用于 2 个列表。

标签: python list dictionary


【解决方案1】:

函数zip() 可以接受两个以上的迭代。所以你可以使用zip(z1, z2, z3) 而不是zip(z2, z3)。但是,您仍然需要对项目进行分组,因为简单地包装 dict() 将不起作用,因为它无法处理 3 元组所需的嵌套字典。

要正确分组项目,我会使用collections.defaultdict()

from collections import defaultdict

z1 = ['A', 'A', 'B', 'B']
z2 = ['k1', 'k2', 'k1', 'k2']
z3 = ['v1', 'v2', 'v3', 'v4']

d = defaultdict(dict)
for x, y, z in zip(z1, z2, z3):
    d[x][y] = z

print(d)
# defaultdict(<class 'dict'>, {'A': {'k1': 'v1', 'k2': 'v2'}, 'B': {'k1': 'v3', 'k2': 'v4'}})

上面的工作是因为defaultdict(dict) 为不存在的键初始化了一个字典。它为您处理键的字典创建。

另外,如果您使用dict 包装最终结果:

print(dict(d))
# {'A': {'k1': 'v1', 'k2': 'v2'}, 'B': {'k1': 'v3', 'k2': 'v4'}}

注意: defaultdict 只是dict 的子类,因此您可以将其视为普通字典。

【讨论】:

  • 可能有人过得很糟糕。如果这个人觉得这是一个骗局,不应该回答,他们应该说出来,我会关闭
【解决方案2】:

为了完整起见,您可以使用dict.setdefault,避免导入,但每次迭代都会产生创建和返回空字典的微小开销。

d = {}
for x, y, z in zip(z1, z2, z3):
    d.setdefault(x,{})[y] = z

print(d)
# {'A': {'k1': 'v1', 'k2': 'v2'}, 'B': {'k1': 'v3', 'k2': 'v4'}}

另一种解决方案(不推荐)是使用itertools.groupby

d = {}
for k, g in groupby(enumerate(zip(z2, z3)), key=lambda x: z1[x[0]]):
    _, b = zip(*g)
    d[k] = dict(b)

print(d)
# {'A': {'k1': 'v1', 'k2': 'v2'}, 'B': {'k1': 'v3', 'k2': 'v4'}}

【讨论】:

  • 好老dict.setdefault(),忘记那个了。
  • @RoadRunner 谢谢,虽然我更喜欢 defaultdict(即使它确实需要导入......!)
  • d.setdefault 不只是返回值吗?为什么可以对返回的值进行操作并将其保存到字典中?我认为我缺乏底层结构来理解为什么会这样。这是可变与不可变 python 类型的情况吗?
  • @akozi 如果存在则返回值(字典)。但无论哪种方式,它都会创建一个空字典,然后在返回实际值之前检查键是否存在。因此提到了“微小的开销”。无论哪种方式,都会返回一个字典,您可以为其分配键值。
【解决方案3】:

这是一个使用 itertools.groupby 的单行表达式,但除了是一个表达式之外,它并没有真正提供与 RoadRunner 提供的默认字典解决方案相比的任何好处。

>>> from itertools import groupby
>>> from operator import itemgetter
>>> keyf = itemgetter(0)
>>> dict((k, dict(v2 for _,v2 in v)) for k, v in groupby(zip(z1, zip(z2,z3)), key=keyf))
{'A': {'k2': 'v2', 'k1': 'v1'}, 'B': {'k2': 'v4', 'k1': 'v3'}}

这只是因为它利用了z1 已经排序的事实。如果不是,则需要在将zip 的输出传递给groupby 之前使用相同的键函数对输出进行排序。

dict((k, dict(v2 for _,v2 in v))
       for k, v in groupby(sorted(zip(z1, zip(z2,z3)),
                                  key=keyf),
                           key=keyf))

分解它的工作原理......

  1. zip(z1, zip(z2, ze)) 为外部字典创建键值对:

    [('A', ('k1', 'v1')),
     ('A', ('k2', 'v2')),
     ('B', ('k1', 'v3')),
     ('B', ('k2', 'v4'))]
    
  2. groupby 有效地将每个键(AB)与其元组配对:

    [('A', <itertools._grouper object at 0x100f656d0>),
     ('B', <itertools._grouper object at 0x100f655d0>)]
    

    每个_grouper 都是一个包含所有具有相同键的键/值对的可迭代对象。

  3. dict(v2 for _,v2 in v) 仅从 _groupers 中提取键/值对,留下键,我们已经可以从 groupby 返回的元组的第一个元素中得到它。

【讨论】:

    猜你喜欢
    • 2021-02-10
    • 1970-01-01
    • 2018-06-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-31
    • 2021-04-21
    相关资源
    最近更新 更多