从包含不同长度子列表的列表中构造所有组合

Posted

技术标签:

【中文标题】从包含不同长度子列表的列表中构造所有组合【英文标题】:Constructing all combinations from a list containing sublists of different length [duplicate] 【发布时间】:2019-06-06 11:22:01 【问题描述】:

我想解决以下问题:给定一个包含不同长度子列表的列表,比如L = [ [ 'a', 'b' ], [ 'c' ], [ 'd', 'e', 'f' ] ],构造所有组合,只包含每个子列表中的一个元素。所以[ 'a', 'c', 'd' ] 是正确的组合,但不是[ 'a', 'b', 'e' ]。有与子列表长度的乘积一样多的组合,这里有六个组合,简而言之(顺序不重要):cda, cdb, cea, ceb, cfa, cfb

这个想法是采用一个任意子列表,而不是单例,采用补码并根据子列表长度从补码和所选子列表的一个成员中创建尽可能多的新子列表。现在继续获取另一个长度大于 1 的子列表,并递归执行此操作,直到所有子列表仅包含单例。在构造了所有寻找的组合之后,该递归必须结束。代码如下:

def break_bipartite_chain( var_list ):
    print('a')
    bbc = []
    def recursive_break( var_list ):
        print('b')
        bipartite_chain = sorted( var_list , key=len , reverse=True )
        LC = len( bipartite_chain )
        LCF = len( bipartite_chain[ 0 ] ) 
        complement_chain = []
        for i in range( 0, LCF ):
            complement_chain.append( bipartite_chain[ 1 : ] )
        if LCF == 1:
            print('c')
            bbc.append( bipartite_chain )
            return None
        else:
            print('d')
            for i in range( 0 , LCF ):
                complement_chain[i].append([ bipartite_chain[ 0 ][ i ] ])
        LCC = len( complement_chain )
        for i in range( 0 , LCC ):
            print('e')
            recursive_break( complement_chain[ i ] )
        return None
    bbc.append( recursive_break( var_list ) )
    return bbc[:-1]

现在,代码可以正常工作并完成应有的工作(至少在我尝试的示例中),但我仍然对它并不完全满意,而且由于我是一个相对的 python 新手,我会建议任何 cmets,包括风格,否则关注以下问题:

    它之所以有效,是因为 if 部分中最里面的“返回”。我不明白当我删除它时会发生什么,但它给出了一个 错误代码“IndexError:列表索引超出范围”?我想用这种风格保留 if 部分并不是一个很好的解决方案,但我在这个论坛上发现了一些其他人,他们建议使用“return”命令过早地脱离这种声明。有更好的解决方案吗?

    最中间的'return' 似乎是不必要的,但无论我是否写它(它在两种情况下都有效),它都会写入一个'None' 作为结果,我通过在最外层的return 中切片将其删除。有没有办法完全抑制 None ?

    虽然它有效,但我仍然不确定递归操作。我逐步跟踪了上面给出的示例(这就是打印命令存在的原因),这是合乎逻辑的。我可以预测解决方案的顺序,他们同意了。但递归通常看起来像魔术。有更清洁的解决方案吗?

    第一个 for 循环可能不是必需的,我尝试了(i)不使用补码的这种乘法(并且稍后在第二个 for 循环中使用补码链.append),或者这样做(ii)使用bipartite_chain[1:]*LCF,要么根本不工作(第一种情况),要么产生奇怪的行为,因为我要附加的条目也出现了 LCF 时间(第二种情况)。

最后,我认为可能有更好的解决方案?或者至少有一些小事情需要改进?

已经感谢您的任何建议。

【问题讨论】:

如果代码正常工作,那么更合适的发布位置是Code Review 【参考方案1】:

您可以为此使用itertools.product

>>> list(itertools.product(*L))
[('a', 'c', 'd'), ('a', 'c', 'e'), ('a', 'c', 'f'), ('b', 'c', 'd'), ('b', 'c', 'e'), ('b', 'c', 'f')]

因为您实际上要构建的是Cartesian product

【讨论】:

这些不是 OP 想要的组合,虽然我还没有弄清楚“cda、cdb、cea、ceb、cfa、cfb”背后的原因...... 根据他们的描述“构造所有组合,仅涉及每个子列表中的一个元素” 我认为他们没有给出正确的示例输出,但他们必须确认那 请注意,OP 说 order is not important 所以这是正确的 天啊...多么简单。我之前遇到过笛卡尔积,但我没有建立联系。谢谢!【参考方案2】:

您可以使用生成器创建一个更简单的递归函数。此外,您的解决方案似乎正在考虑[['a', 'b'], ['c'], ['d', 'e', 'f']] 的子列表的所有可能排序,因此,可以实施额外的递归方法来查找l 的所有可能位置:

l = [['a', 'b'], ['c'], ['d', 'e', 'f']]
from collections import Counter
def placement(d, _c = []):
  if len(_c) == len(d):
    yield _c
  else:
    for i in d:
      _c1, _c2 = Counter(map(tuple, d)), Counter(map(tuple, _c))
      if _c2[tuple(i)] < _c1[tuple(i)]:
         yield from placement(d, _c+[i])

def combos(d, _c = []):
  if len(_c) == len(l):
    yield _c
  else:
    for i in d[0]:
      yield from combos(d[1:], _c+[i])

final_results = [''.join(i) for b in placement(l) for i in combos(b)]

输出:

['acd', 'ace', 'acf', 'bcd', 'bce', 'bcf', 'adc', 'aec', 'afc', 'bdc', 'bec', 'bfc', 'cad', 'cae', 'caf', 'cbd', 'cbe', 'cbf', 'cda', 'cdb', 'cea', 'ceb', 'cfa', 'cfb', 'dac', 'dbc', 'eac', 'ebc', 'fac', 'fbc', 'dca', 'dcb', 'eca', 'ecb', 'fca', 'fcb']

【讨论】:

我也喜欢你的解决方案,但我认为我还不熟悉生成器。事实上,我只想要一个排序所有可能的排序,但这是目前,也许在其他时候我觉得有必要生成它们,所以有这个解决方案也很好。谢谢! @Iridium 如果你只想要一个排序,那么你只需要使用combos作为list(combos(l))

以上是关于从包含不同长度子列表的列表中构造所有组合的主要内容,如果未能解决你的问题,请参考以下文章

如何将列表切成不同长度的子列表

Python - 从大量组合中构建满足某些标准的子列表

满足特定条件的列表的所有组合

Python每个列表一项的所有组合[重复]

如何从具有子元组的元组创建列表?

在python中合并具有不同长度和列的数据框列表