字典列表的笛卡尔积

90
我正在尝试编写一些代码来测试一堆输入参数的笛卡尔积。
我看过了 'itertools',但它的 'product' 函数不完全符合我的要求。是否有一种简单明显的方法可以接受一个具有任意数量键和每个值中任意数量元素的字典,并产生下一个排列的字典?
输入:
options = {"number": [1,2,3], "color": ["orange","blue"] }
print list( my_product(options) )

示例输出:

[ {"number": 1, "color": "orange"},
  {"number": 1, "color": "blue"},
  {"number": 2, "color": "orange"},
  {"number": 2, "color": "blue"},
  {"number": 3, "color": "orange"},
  {"number": 3, "color": "blue"}
]

我非常确定你不需要任何库来完成这个任务,但是我对Python的了解还不够深入,无法回答。我猜测列表推导式可能是关键。 - Matt Ball
1
我想问是否存在一个现成的生成器,可以轻松地适应做这样的事情。列表推导式根本不相关。 - Seth Johnson
4个回答

96

好的,感谢 @dfan 告诉我我在错误的地方查找。现在我已经找到了:

from itertools import product
def my_product(inp):
    return (dict(zip(inp.keys(), values)) for values in product(*inp.values())

编辑:经过多年的Python编程经验,我认为更好的解决方案是接受kwargs而不是输入字典;调用方式更类似于原始的itertools.product。此外,我认为编写生成器函数而不是返回生成器表达式的函数可以使代码更清晰。因此:

import itertools
def product_dict(**kwargs):
    keys = kwargs.keys()
    for instance in itertools.product(*kwargs.values()):
        yield dict(zip(keys, instance))

如果你需要传递一个字典,可以使用 list(product_dict(**mydict))。使用 kwargs 而不是一个任意的输入类,唯一值得注意的变化是它防止键 / 值被排序,至少在 Python 3.6 之前。

4
字典词条无序存储对此有任何影响吗? - Phani
1
这是一个非常整洁的代码,可以快速生成单元测试用例(交叉验证集样式!) - gaborous
2
针对Python 3用户。我这里有一个更新的版本,请点击此处 - Tarrasch
2
@Phani,我认为这个没问题,即使键和值是无序的,它们仍然相对于彼此保持一致的顺序。 - ibizaman
如果您正在使用此字典列表作为要通过map发送到函数的**kwargs列表,则类似于许多嵌套的for循环。不同之处在于,您无法保证哪个循环在外部,哪个循环在内部。 - rudolfbyker
在保证本地 kwargs 上执行此操作的另一个好处是,用户在迭代期间无法修改(有意或无意地)字典键/值。 - Seth Johnson

39

Python 3版本的Seth's answer

import itertools

def dict_product(dicts):
    """
    >>> list(dict_product(dict(number=[1,2], character='ab')))
    [{'character': 'a', 'number': 1},
     {'character': 'a', 'number': 2},
     {'character': 'b', 'number': 1},
     {'character': 'b', 'number': 2}]
    """
    return (dict(zip(dicts, x)) for x in itertools.product(*dicts.values()))

6
可能在左侧加一个.keys()可以更清晰地表达意思:(dict(zip(dicts.keys(), x)) - andrew

8

顺便说一下,这不是一个排列。排列是列表的重新排列。这是从列表中可能选择的枚举。

编辑:在记起它被称为笛卡尔积后,我想到了这个:

import itertools
options = {"number": [1,2,3], "color": ["orange","blue"] }
product = [x for x in apply(itertools.product, options.values())]
print([dict(zip(options.keys(), p)) for p in product])

1
我试图解释为什么查找“排列”并没有帮助。我记得这实际上是一个笛卡尔积。我会从查看itertools.product()开始。 - dfan
好的,已经完成了,感谢您的指引。但是还是要欢迎您来到Stack Overflow:一个答案应该是真正提供问题答案的回答。这应该作为问题的评论。 - Seth Johnson
1
@user470379 不完全是,原始版本没有提到笛卡尔积。 - Daniel DiPaolo
1
我似乎还没有评论除了我的答案以外的任何东西的能力。如果我可以的话,我会把它放在那里的。我很高兴我的答案带你找到了解决方案。 - dfan
啊,明白了。好的,再次感谢您帮助我找到正确的方向。 - Seth Johnson
在Python 3中,您需要将apply(itertools.product, options.values())替换为itertools.product(*options.values()) - GitHunter0

4
# I would like to do
keys,values = options.keys(), options.values()
# but I am not sure that the keys and values would always
# be returned in the same relative order. Comments?
keys = []
values = []
for k,v in options.iteritems():
    keys.append(k)
    values.append(v)

import itertools
opts = [dict(zip(keys,items)) for items in itertools.product(*values)]

导致

opts = [
    {'color': 'orange', 'number': 1},
    {'color': 'orange', 'number': 2},
    {'color': 'orange', 'number': 3},
    {'color': 'blue', 'number': 1},
    {'color': 'blue', 'number': 2},
    {'color': 'blue', 'number': 3}
]

3
我认为Python保证keys()和values()及其对应的iter*返回顺序相同。请参见http://docs.python.org/library/stdtypes.html#dict.items。 - Seth Johnson
@Seth:太好了!谢谢你,这个问题困扰我有一段时间了。 - Hugh Bothwell
非常欢迎。这对于这种情况特别方便。如果您查看我的答案,您会发现iterkeys / itervalues方法也可以使您避免创建一堆临时变量。 - Seth Johnson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接