【问题标题】:Cartesian product of a dictionary of lists列表字典的笛卡尔积
【发布时间】:2011-07-10 20:00:14
【问题描述】:

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

我查看了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 的了解还不够好,无法回答。我猜想列表推导是诀窍。
  • 我在问是否有现成的生成器可以很容易地适应做这样的事情。列表推导完全不相关。

标签: python generator combinatorics


【解决方案1】:

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

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

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

【讨论】:

  • 我试图解释为什么查找“排列”没有帮助。我记得这实际上是什么:它是笛卡尔积。我将从查看 itertools.product() 开始。
  • 是的,完成了,感谢您的指点。但是,仍然欢迎来到 Stack Overflow:答案应该是实际提供问题答案的答案。这属于对问题的评论。
  • @user470379 不是真的,原版没有说明笛卡尔积
  • 除了我自己的答案,我似乎还没有能力评论任何事情。如果可以的话,我会把它放在那里。很高兴我的回答让您找到了解决方案。
  • 啊,明白了。好吧,再次感谢您帮助我走上正轨。
【解决方案2】:

好的,感谢@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 之前是这样。

【讨论】:

  • 字典条目无序存储这一事实是否会影响这一点?
  • 这是一个非常简洁的代码,可以快速生成单元测试用例(交叉验证集风格!)
  • 适用于 Python 3 用户。我有更新版本here
  • @Phani 我会说这没关系,因为键和值即使无序,但仍然分别一致地相互排序。
  • @Phani 如果您使用这个字典列表作为**kwargs 的列表通过map 发送到函数,那么它类似于很多嵌套的for 循环。不同之处在于您无法保证哪个循环在外面,哪个循环在里面。
【解决方案3】:

Seth's answer 的 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()))

【讨论】:

  • 为了清楚起见,可以在左侧添加.keys()(dict(zip(dicts.keys(), x))
【解决方案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}
]

【讨论】:

  • 我认为 Python 保证 keys() 和 values() 及其对应的 iter* 将以相同的顺序返回。见docs.python.org/library/stdtypes.html#dict.items
  • @Seth:太好了!谢谢,这困扰了我一段时间。
  • 不客气。它非常方便,尤其是对于这种情况。如果您查看我的回答,您会发现 iterkeys/itervalues 方法也可以让您免于创建一堆临时对象。
猜你喜欢
  • 2018-11-09
  • 1970-01-01
  • 2017-10-16
  • 2019-07-23
  • 1970-01-01
  • 2012-03-24
  • 2023-03-17
  • 2018-04-04
  • 2017-08-24
相关资源
最近更新 更多