列表字典的笛卡尔积


70

我正在尝试编写一些代码来测试一堆输入参数的笛卡尔积。

我看过了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
我在问是否有现成的发电机,可以很容易地适应做这样的事情。列表理解根本不相关。
塞斯·约翰逊

Answers:


68

好的,感谢@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。另外,我认为编写生成器函数(而不是返回生成器表达式的函数)会使代码更清晰。所以:

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

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


3
字典条目无序存储的事实是否会对此产生影响?
Phani 2014年

1
这是一个非常简洁的代码,可以快速生成单元测试用例(交叉验证集样式!)
令人讨厌的2015年

对于Python 3用户。我在这里
Tarrasch

1
@Phani我会说没关系,因为键和值(尽管是无序的)仍然始终保持一致的顺序。
ibizaman '16

@Phani如果您使用此词典列表作为通过列表**kwargs发送到函数的列表map,则它类似于许多嵌套的for循环。区别在于您无法保证哪个循环在外部,哪个循环在内部。
rudolfbyker

27

塞思答案的Python 3版本。

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()))

1
可能.keys()在左侧添加,以使内容更清晰(dict(zip(dicts.keys(), x))
安德鲁

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 2011年

是的,完成了,谢谢你的指导。但是,仍然欢迎您使用Stack Overflow:答案应该是实际上为问题提供答案的答案。这是对该问题的评论。
塞斯·约翰逊

1
@ user470379不是真的,原始版本
未说明

1
除我自己的答案外,我似乎没有能力发表其他任何评论。如果可以的话,我会把它放在那里的。我很高兴我的回答使您找到了解决方案。
dfan 2011年

嗯,明白了。好吧,再次感谢您为我提供的帮助。
塞斯·约翰逊

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}
]

2
我认为Python保证keys()和values()及其对应的iter *将以相同顺序返回。参见docs.python.org/library/stdtypes.html#dict.items
赛斯·约翰逊

@Seth:太好了!谢谢,这困扰了我一段时间。
休·博斯韦尔

你太客气了。这非常方便,尤其是在这种情况下。如果查看我的答案,您会发现iterkeys / itervalues方法也将使您免于创建大量临时对象。
塞斯·约翰逊
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.