在python中生成列表的条件乘积(组合)

Posted

技术标签:

【中文标题】在python中生成列表的条件乘积(组合)【英文标题】:generating conditional product of lists in python (combinatorics) 【发布时间】:2013-03-01 02:54:09 【问题描述】:

我希望能够生成有条件的产品。与此答案非常相似: All combinations of a list of lists

我想使用itertools.product(*listOfLists)。但是,我的问题是从一个列表中包含一个元素意味着必须咨询其他列表以获取该产品。

例子:

colors = ['red', 'blue', 'green']
fruits = ['apple', 'orange', 'banana']
locations = ['indoors', 'outdoors']

indoor_choices = ['bathroom', 'bedroom', 'kitchen']
green_choices = ['forest', 'light', 'dark']

在这里,我们希望始终考虑颜色、服装和位置的所有可能选择。然而,在'indoor'的情况下,我们也想考虑indoor_choices,而在'green'是可能的选择的情况下,我们也想选择更具体的绿色。这是一种可能性树,其中一些分支继续分支,而其他分支则没有。

所以在上面这个愚蠢的例子中,你可以像这样做一个 for 循环:

for c in colors:
    for f in fruits:
        for l in locations:
            # etc

但是我们会遇到一个问题,即当两个不同的类别基于此选择可能出现分支时会发生什么。

一个简单(hacky)的解决方案就是手动编写条件代码并在其中放入 for 循环:

for c in colors:
    for f in fruits:
        for l in locations:

            if c == 'green' and l == 'indoor':
                for gc in green_choices:
                     for ic in indoor_choices:
                         # output

            elif c == 'green':
                for gc in green_choices:
                    # output

            elif l == 'indoor':
                for gc in green_choices:
                    # output

            else:
                # output

但是想象一下当有 N 个列表中的 M 个有额外分支时的恐怖。或者更糟糕的是,还有 nested 额外的分支......基本上这种 hack 无法扩展。

有什么想法吗?事实证明,这个问题本身就很困难!

【问题讨论】:

首先,您应该使您的代码可重现(colors = ["red", "blue", "green"] 而不是colors = [red, blue, green])。此外,听起来您为这个问题使用了错误的数据结构。 indoorsindoor_choices 是如何关联的?它不应该是一个嵌套列表吗? 完成。不,不是嵌套列表。仍然重要的是要知道,在特定的 选择中,我们选择了“green”和“indoors”,这迫使我们为特定类型的室内做出额外的选择组合和特定类型的绿色。您是否理解我所说的问题?也许我不是最清楚的...... 如果不是嵌套列表,则至少 indoor_choicesgreen_choices 应该在字典中,由字符串 indoorgreen 索引。这将使您不必遇到使用不同变量的特殊情况。 不确定您的建议是什么?我想重申这是一个愚蠢的例子,这个概念是我有一个 N 个列表。在这些 N 中,它们的子集总是从 A =(颜色、水果、位置)中选择。另一个不相交的子集是有条件的,只有在包含 A 的特定元素时才会考虑这些选择。我意识到字典可能是可视化代码的更好方法,但我唯一的目标是枚举这些条件组合。或者您对 dicts 如何启用此类枚举有什么建议? 【参考方案1】:

我会这样做,使用递归生成器。

def prod(terms, expansions):
    if not terms: # base case
        yield ()
        return

    t = terms[0] # take the first term

    for v in expansions[t]: # expand the term, to get values
        if v not in expansions: # can the value can be expanded?
            gen = prod(terms[1:], expansions) # if not, we do a basic recursion
        else:
            gen = prod(terms[1:] + [v], expansions) # if so, we add it to terms

        for p in gen: # now we get iterate over the results of the recursive call
            yield (v,) + p # and add our value to the start

以下是您如何调用它来生成示例中所需的产品:

expansions = 
        'colors':['red', 'blue', 'green'],
        'fruits':['apple', 'orange', 'banana'],
        'locations':['indoors', 'outdoors'],
        'indoors':['bathroom', 'bedroom', 'kitchen'],
        'green':['forest', 'light', 'dark']
    

terms = ["colors", "locations"] # fruits omitted, to reduce the number of lines

for p in prod(terms, expansions):
    print(p)

输出:

('red', 'indoors', 'bathroom')
('red', 'indoors', 'bedroom')
('red', 'indoors', 'kitchen')
('red', 'outdoors')
('blue', 'indoors', 'bathroom')
('blue', 'indoors', 'bedroom')
('blue', 'indoors', 'kitchen')
('blue', 'outdoors')
('green', 'indoors', 'forest', 'bathroom')
('green', 'indoors', 'forest', 'bedroom')
('green', 'indoors', 'forest', 'kitchen')
('green', 'indoors', 'light', 'bathroom')
('green', 'indoors', 'light', 'bedroom')
('green', 'indoors', 'light', 'kitchen')
('green', 'indoors', 'dark', 'bathroom')
('green', 'indoors', 'dark', 'bedroom')
('green', 'indoors', 'dark', 'kitchen')
('green', 'outdoors', 'forest')
('green', 'outdoors', 'light')
('green', 'outdoors', 'dark')

【讨论】:

不错。确保你不要给它任何相互扩展的字符串:-) 由于它是一个生成器,无限递归的扩展集可能不会有问题。你只需要使用itertools.islice 或其他东西来限制你消耗的数量。 @Blckknght:如果你有一个无限递归集合,它可能会中断,因为prods 在产生单个值之前会相互调用。 无论如何,自动检查很容易,例如,列表的字典将每个键列出不超过一次。 这适用于嵌套条件选择吗?就好像我们在另一个字典里面有一本字典一样?【参考方案2】:

如果你真正的问题真的和你的例子一样,那么你可以将组合分析成四个产品:

is_green = ['green']
not_green = ['red', 'blue']
is_indoors = ['indoors']
not_indoors = ['outdoors']

p1 = itertools.product([not_green, fruits, not_indoors])
...
p2 = itertools.product([is_green, fruits, not_indoors, green_choices])
...
p3 = itertools.product([not_green, fruits, is_indoors, indoor_choices])
...
p4 = itertools.product([is_green, fruits, is_indoors, green_choices, indoor_choices])

就是这样!

现在,如果我们想进行概括,因此我们不必做出四种“特殊”情况,我们可以捕捉某些值与它们打开的其他选择之间的关系,正如@DavidRobinson 所建议的那样。

import itertools

colors = ['red', 'blue', 'green']
fruits = ['apple', 'orange', 'banana']
locations = ['indoors', 'outdoors']

indoor_choices = ('bathroom', 'bedroom', 'kitchen')
green_choices = ('forest', 'light', 'dark')

choices = [colors, fruits, locations]
more_choices =  'indoors': indoor_choices, 'green': green_choices 
for p in itertools.product(*choices):
    m = [more_choices[k] for k in p if k in more_choices]
    for r in itertools.product([p],*m):
        print list(r[0]) + list(r[1:])

请注意,当choice 和more_choices 很大时,难免会遇到困难。

【讨论】:

当然。但这与我编写的代码相同 - for 循环中有 4 个案例。当我有 N 个列表时会发生什么,其中有 M 个列表以包含 N 中列表中的某些元素为条件?案件数量激增。如果我误解了你所做的非常聪明的事情,请原谅我。 你对我的第一个例子是正确的,所以我根据@DavidRobinson 的建议添加了一个概括。 (对不起,我不知道我是否正确关联了他的ID。) 太棒了!完全有效。好想法。另外,快速提问:我将interior_choices 和green_choices 更改为列表而不是元组,性能下降了很多(慢得多)。为什么会这样? 我不确定在这种特定情况下的确切性能影响。一般来说,与列表不同,元组允许 Python 及其库在内容和大小永远不会改变的假设下进行优化。 也 - 这适用于嵌套条件选择吗?就好像我们在另一个里面有一本字典一样?【参考方案3】:

这是一个使用yield 的递归实现。我认为它不像@Blckknght 的解决方案那么简洁,但它可能会有所帮助。

colors = ["red","blue","green"]
fruits = ["apple","orange", "banana"]
locations = ["indoors","outdoors"]

green_subtypes = ["forest", "light", "dark"]
indoor_locations = ["bathroom","bedroom","kitchen"]

def gen(state):
  if len(state)==0:
    for c in colors:
       s = [c]
       for y in gen(s):
         yield y
  elif len(state)==1:
    for x in fruits:
      s = state + [x]
      for y in gen(s):
        yield y
  elif len(state)==2:
    for x in locations:
      s = state + [x]
      for y in gen(s):
        yield y
  else:
    # If we're green and we haven't looped through the green options already 
    # (the check is a bit dodgy and could do with being moved into a flag inside state)
    if state[0]=='green' and len(set(state).intersection(set(green_subtypes)))==0:
      for x in green_subtypes:
        s = state + [x]
        for y in gen(s):
          yield y
    # If we're indoors and we haven't looped through the indoor options already 
    # (the check is a bit dodgy and could do with being moved into a flag inside state)
    elif state[2]=='indoors' and len(set(state).intersection(set(indoor_locations)))==0:
      for x in indoor_locations:
        s = state + [x]
        for y in gen(s):
          yield y
    else:
      yield state

for x in gen([]):
  print(x)

【讨论】:

【参考方案4】:

我们可以在事后添加“额外”选项(Python 3 语法):

def choice_product(choices, *iterables):
    for v in itertools.product(*iterables):
        ks = set(v) & choices.keys()
        if ks:
            choice_iters = [choices[k] for k in ks]
            for p in choice_product(choices, *choice_iters):
                yield v + p
        else:
            yield v

这使用itertools.product 来提高效率。

choices定义为

choices = 'indoors' : ['bathroom', 'bedroom', 'kitchen'],
           'green': ['forest', 'light', 'dark']

这会递归:

>>> for i in choice_product('c': 'de', 'e': 'fg', 'ab', 'cd'):
...     print(i)
... 
('a', 'c', 'd')
('a', 'c', 'e', 'f')
('a', 'c', 'e', 'g')
('a', 'd')
('b', 'c', 'd')
('b', 'c', 'e', 'f')
('b', 'c', 'e', 'g')
('b', 'd')

【讨论】:

以上是关于在python中生成列表的条件乘积(组合)的主要内容,如果未能解决你的问题,请参考以下文章

如何从所有排列中生成所有可能的组合?

Hive UDF 从列表中生成所有可能的有序组合

在 Java 中生成所有列表 n 级深度的组合

从给定的单词列表中生成具有“N”长度的所有可能组合(寻找不重复)

在目标c中生成组合的有效方法[重复]

快速的独特组合(来自有重复的列表),无需查找