将包含列表项的 dict 展开为 dict 对列表

Posted

技术标签:

【中文标题】将包含列表项的 dict 展开为 dict 对列表【英文标题】:Expand a dict containing list items into a list of dict pairs 【发布时间】: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,其他所有内容保持不变”给定起始字典。 老实说,我认为您想尝试用错误的解决方案解决问题。也许发布输入和期望的结果并描述问题区域。 【参考方案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) 维护。 可能是我弄错了你随机输出的原因,但它仍然是随机的。我认为你不能在这里避免硬编码

以上是关于将包含列表项的 dict 展开为 dict 对列表的主要内容,如果未能解决你的问题,请参考以下文章

将元组列表列表转换为 dict 未按预期工作

我有嵌套的dict变量列表,需要将其转换为Json对象的dict变量类型

将Dataframe转换为dict列表太慢[重复]

python 将多个列表分类为dicts.py

Django如何对dicts列表进行分页

将嵌套的 dict 列表展平为 Pandas Dataframe