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

时间:2019-01-11 14:10:05

标签: python list recursion return

我想解决以下问题:给定一个包含不同长度子列表的列表(例如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新手,因此我建议您提出任何意见,包括样式,否则重点关注以下问题:

  1. 它仅由于if部分中最里面的“ return”而起作用。删除时我不知道会发生什么,但是它给出了 错误代码“ IndexError:列表索引超出范围”?我想将if部分保留为这种样式并不是一个很好的解决方案,但是我在该论坛上发现了其他一些人,他们建议使用“ return”命令过早地中断此类声明。有更好的解决方案吗?

  2. 最中间的“返回”似乎是不必要的,但是无论我是否编写(在两种情况下都有效),结果都写为“ None”,我通过在最外面的返回上切片将其删除。有没有一种方法可以完全抑制“无”?

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

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

最后,我认为可能有更好的解决方案?还是至少需要改进的地方?

已经感谢您提出任何建议。

2 个答案:

答案 0 :(得分:3)

您可以为此使用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

答案 1 :(得分:0)

您可以使用生成器创建一个更简单的递归函数。同样,您的解决方案中似乎考虑了[['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']