【问题标题】:Expand a dict containing list items into a list of dict pairs将包含列表项的 dict 展开为 dict 对列表
【发布时间】:2018-12-03 00:22:54
【问题描述】:

如果我有一本包含一个或多个值的列表的字典:

data = {
  'a':0,
  'b':1,
  'c':[0, 1, 2],
  'pair':['one','two']
}

如何获得由pair 配对并遍历c 的字典元组列表,而其他所有内容保持不变?例如

output = [
    ({
        'a':0,
        'b':1,
        'c':0,
        'pair':'one'
    },
    {
        'a':0,
        'b':1,
        'c':0,
        'pair':'two'
    }),
    ({
        'a':0,
        'b':1,
        'c':1,
        'pair':'one'
    },
    ...
]

【问题讨论】:

  • 为什么c的最后一个元素是1而不是2?
  • @user3483203 有一个省略号
  • 你知道要提前“扩展”的values的key吗?我是否正确理解结果列表类似于扩展值的笛卡尔积?
  • 您扩展 dict 的想法会提出一个有趣的问题。这个问题专门关于获取“由pair 配对的dict 元组列表并迭代c,其他所有内容保持不变”给定起始字典。
  • 老实说,我认为您想尝试用错误的解决方案解决问题。也许发布输入和期望的结果并描述问题区域。

标签: python python-2.7 dictionary list-comprehension dictionary-comprehension


【解决方案1】:

嗯,这感觉不是特别优雅,但您可以使用嵌套的 for 循环或列表推导:

output = []
for i in data['c']:
  output.append(tuple({'a': 0, 'b': 1, 'c': i, 'pair': p} for p in data))

或

output = [tuple({'a': 0, 'b': 1, 'c': i, 'pair': p} for p in data['pair']) for i in data['c']]

更简洁的解决方案可能会将组件 dict 的生成分离到一个函数中,如下所示:

def gen_output_dict(c, pair):
  return {'a': 0, 'b': 1, 'c': c, 'pair': pair}

output = []
for i in data['c']:
  output.append(tuple(gen_output_dict(i, p) for p in data['pair']))

【讨论】:

  • 如果 OP 有大量的键和值怎么办,他必须遍历整个字典并找到值是列表类型的键
  • 你知道@U8-Forward,你可能是对的,OP 询问的是更一般的迭代列表的情况。
【解决方案2】:

您可以在列表值上使用itertools.product 并跟踪每个元素的来源键。由于'pair'这个键有特殊含义,应该单独对待。

代码

from itertools import product

def unzip_dict(d):
    keys = [k for k, v in d.items() if isinstance(v, list) and k != 'pair']
    values = [d[k] for k in keys]

    for values in product(*values):
        yield tuple({**d, **dict(zip(keys, values)), 'pair': pair} for pair in d['pair'])

示例

data = {
    'a': 0,
    'c': [1, 2],
    'pair': ['one', 'two']
}

print(*unzip_dict(data))

输出

({'a': 0, 'c': 1, 'pair': 'one'}, {'a': 0, 'c': 1, 'pair': 'two'})
({'a': 0, 'c': 2, 'pair': 'one'}, {'a': 0, 'c': 2, 'pair': 'two'})

【讨论】:

  • 这也是我的直觉,但后来我注意到他们需要根据pair 键的值(实际上,基于c 键)对列表内的元组中的项目进行配对价值)。你仍然可以使用 itertools 产品,但它会不太通用。
  • @jedwards 有更新版本,谢谢提醒。
【解决方案3】:

以下是一个相当扩展的解决方案:

data = {
  'a':0,
  'b':1,
  'c':[0, 1, 2],
  'pair':['one','two']
}

# Get the length of the longest sequence
length = max(map(lambda x: len(x) if isinstance(x, list) else 1, data.values()))

# Loop through the data and change scalars to sequences
# while also making sure that smaller sequences are stretched to match
# or exceed the length of the longest sequence
for k, v in data.items():
    if isinstance(v, list):
        data[k] = v * int(round(length/len(v), 0))
    else:
        data[k] = [v] * length

# Create a dictionary to keep track of which outputs
# need to end up in which tuple
seen = dict.fromkeys(data.get('pair'), 0)
output = [tuple()] * len(seen)

# Loop through the data and place dictionaries in their
# corresponding tuples.
for v in zip(*data.values()):
        d = dict(zip(data, v))
        output[seen[d.get('pair')]] += (d,)
        seen[d.get('pair')] += 1

print(output)

这个想法是将数据中的标量转换为长度与原始数据中最长序列匹配的序列。因此,我做的第一件事就是将最长序列的大小分配给变量length。有了这些知识,我们遍历原始数据并扩展已经存在的序列以匹配最长序列的大小,同时将标量转换为序列。 完成后,我们开始生成output 变量。但首先,我们创建一个名为 seen 的字典,以帮助我们创建一个元组列表并跟踪哪组字典最终位于哪个元组中。 然后,这允许我们运行最后一个循环来将字典组放置到它们对应的元组中。

当前输出如下所示:

[({'a': 0, 'b': 1, 'c': 0, 'pair': 'one'},
  {'a': 0, 'b': 1, 'c': 1, 'pair': 'two'}),
 ({'a': 0, 'b': 1, 'c': 2, 'pair': 'one'},)]

如果您需要更多澄清细节,请告诉我。否则,我确实希望这能起到一些作用。

【讨论】:

    【解决方案4】:

    @r3robertson,你也可以试试下面的代码。代码基于 Python 中list comprehension, & deepcopy() operation 的概念。

    检查Shallow copy vs deepcopy in Python。

    import pprint;
    import copy;
    
    data = {
        'a': 0,
        'b': 1,
        'c': [0, 1, 2],
        'pair': ['one','two'],
    };
    
    def get_updated_dict(data, index, pair_name):
        d = copy.deepcopy(data);
        d.update({'c': index, 'pair': pair_name});
        return d;
    
    output = [tuple(get_updated_dict(data, index, pair_name) for pair_name in data['pair']) for index in data['c']];
    
    # Pretty printing the output list.
    pprint.pprint(output, indent=4);
    

    输出 »

    [   (   {   'a': 0, 'b': 1, 'c': 0, 'pair': 'one'},
            {   'a': 0, 'b': 1, 'c': 0, 'pair': 'two'}),
        (   {   'a': 0, 'b': 1, 'c': 1, 'pair': 'one'},
            {   'a': 0, 'b': 1, 'c': 1, 'pair': 'two'}),
        (   {   'a': 0, 'b': 1, 'c': 2, 'pair': 'one'},
            {   'a': 0, 'b': 1, 'c': 2, 'pair': 'two'})]
    

    使用 json 模块进行漂亮的打印 »

    注意:元组将在此处转换为列表,因为 JSON 不支持元组。

    import json;
    print(json.dumps(output, indent=4));
    

    输出 »

    [
        [
            {
                "a": 0,
                "c": 0,
                "b": 1,
                "pair": "one"
            },
            {
                "a": 0,
                "c": 0,
                "b": 1,
                "pair": "two"
            }
        ],
        [
            {
                "a": 0,
                "c": 1,
                "b": 1,
                "pair": "one"
            },
            {
                "a": 0,
                "c": 1,
                "b": 1,
                "pair": "two"
            }
        ],
        [
            {
                "a": 0,
                "c": 2,
                "b": 1,
                "pair": "one"
            },
            {
                "a": 0,
                "c": 2,
                "b": 1,
                "pair": "two"
            }
        ]
    ]
    

    【讨论】:

      【解决方案5】:

      不太完美,但这是我的解决方案。

      data = { 'a':0, 'b':1, 'c':[0, 1, 2], 'pair':['one','two'] }
      a,b = data['pair'], data['c']
      for t in range(0, len(b)):
        for u in range(0, len(a)):
          for h in a:
              data['c']=b[t]
              data['pair']=a[u]
          print(tuple([data]))
      

      【讨论】:

        【解决方案6】:

        你可以使用itertools:

        import itertools
        data = {
          'a':0,
          'b':1,
          'c':[0, 1, 2],
          'pair':['one','two']
        }
        def expand_dict(data):
           grouped = [a for a, b in data.items() if isinstance(b, list)]
           p = [[a, list(b)] for a, b in itertools.groupby(itertools.product(*[data[i] for i in grouped]), key=lambda x:x[0])]
           return [tuple({**data, **dict(zip(grouped, i))} for i in c) for _, c in p]
        
        print(expand_dict(data))
        

        输出:

        [({'a': 0, 'b': 1, 'c': 0, 'pair': 'one'}, {'a': 0, 'b': 1, 'c': 0, 'pair': 'two'}), 
         ({'a': 0, 'b': 1, 'c': 1, 'pair': 'one'}, {'a': 0, 'b': 1, 'c': 1, 'pair': 'two'}), 
         ({'a': 0, 'b': 1, 'c': 2, 'pair': 'one'}, {'a': 0, 'b': 1, 'c': 2, 'pair': 'two'})]
        

        此解决方案也适用于具有许多可能的值列表的输入:

        data = {'a':[5, 6, 1, 3], 'b':1, 'c':[0, 1, 2], 'pair':['one', 'two']}
        print(expand_dict(data))
        

        输出:

        [({'a': 5, 'b': 1, 'c': 0, 'pair': 'one'}, {'a': 5, 'b': 1, 'c': 0, 'pair': 'two'}, {'a': 5, 'b': 1, 'c': 1, 'pair': 'one'}, {'a': 5, 'b': 1, 'c': 1, 'pair': 'two'}, {'a': 5, 'b': 1, 'c': 2, 'pair': 'one'}, {'a': 5, 'b': 1, 'c': 2, 'pair': 'two'}), ({'a': 6, 'b': 1, 'c': 0, 'pair': 'one'}, {'a': 6, 'b': 1, 'c': 0, 'pair': 'two'}, {'a': 6, 'b': 1, 'c': 1, 'pair': 'one'}, {'a': 6, 'b': 1, 'c': 1, 'pair': 'two'}, {'a': 6, 'b': 1, 'c': 2, 'pair': 'one'}, {'a': 6, 'b': 1, 'c': 2, 'pair': 'two'}), ({'a': 1, 'b': 1, 'c': 0, 'pair': 'one'}, {'a': 1, 'b': 1, 'c': 0, 'pair': 'two'}, {'a': 1, 'b': 1, 'c': 1, 'pair': 'one'}, {'a': 1, 'b': 1, 'c': 1, 'pair': 'two'}, {'a': 1, 'b': 1, 'c': 2, 'pair': 'one'}, {'a': 1, 'b': 1, 'c': 2, 'pair': 'two'}), ({'a': 3, 'b': 1, 'c': 0, 'pair': 'one'}, {'a': 3, 'b': 1, 'c': 0, 'pair': 'two'}, {'a': 3, 'b': 1, 'c': 1, 'pair': 'one'}, {'a': 3, 'b': 1, 'c': 1, 'pair': 'two'}, {'a': 3, 'b': 1, 'c': 2, 'pair': 'one'}, {'a': 3, 'b': 1, 'c': 2, 'pair': 'two'})]
        

        【讨论】:

        • op 想要a list of dict tuples paired by pair and iterating over c,但您的脚本通过随机键 (data.items()) 配对并迭代另一个
        • @bobrobbob 是的,但是,请注意[data[i] for i in grouped]。虽然data.items() 是随机的,但[data[i] for i in grouped] 将按照data.items() 创建的顺序查找数据值。这个有效的配对仍然通过zip(grouped, i) 维护。
        • 可能是我弄错了你随机输出的原因,但它仍然是随机的。我认为你不能在这里避免硬编码
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2022-01-25
        • 1970-01-01
        • 2018-10-14
        • 1970-01-01
        • 2018-02-23
        • 2018-10-12
        • 2022-06-17
        相关资源
        最近更新 更多