将列表拆分为两个列表的所有可能性

时间:2016-11-20 21:17:40

标签: python list combinations permutation

我有一个包含一些元素的列表,并希望迭代所有可能的方法将此列表分成两个列表。我的意思是所有组合,因此顺序无关紧要(即元素1和3可以在一个列表中,元素2在另一个列表中)。目前我这样做,其中facs是我的初始列表:

patterns = []
for i in range(2**(len(facs)-1)):
    pattern = []
    for j in range((len(facs)-1)):
        pattern.append(i//(2**j)%2)
    patterns.append(pattern)

for pattern in patterns:
    l1 = [facs[-1]]
    l2 = []
    for i in range(len(pattern)):
        if pattern[i] == 1:
            l1.append(facs[i])
        else:
            l2.append(facs[i])

所以我基本上创建了一个长度为2^(len(facs)-1)的列表,并用每个可能的1和0组合填充它。然后,我使用facs“覆盖”每个模式,但facs的最后一个元素除外,它始终位于l1中,因为我会在每个结果中获得两次,因为我处理了两个列表同样,无论列表是l1还是l2

是否有更快更优雅(更短/更pythonic)的方式来做到这一点?

4 个答案:

答案 0 :(得分:3)

itertoolsproduct()可用于生成掩码,izip()可以组合列表以便于过滤。作为奖励,因为他们返回迭代器,他们不会使用太多内存。

from itertools import *

facs = ['one','two','three']

l1 = []
l2 = []
for pattern in product([True,False],repeat=len(facs)):
    l1.append([x[1] for x in izip(pattern,facs) if x[0]])
    l2.append([x[1] for x in izip(pattern,facs) if not x[0]])

答案 1 :(得分:2)

第一部分可能是这样的嵌套列表理解:

patterns = [ [ i//(2**j)%2 for j in range(len(facs)-1) ] for i in range(2**(len(facs)-1)) ]

对于第二部分,由于有2个列表,因此无法进行列表推导,但您可以执行三元表达式来选择要追加的列表。

您可以zip patternfacs列表以避免使用索引:

for pattern in patterns:
    l1 = [facs[-1]]
    l2 = []
    for fac,pat in zip(facs,pattern):
        (l1 if pat == 1 else l2).append(fac)

当然,您必须在迭代期间使用l1l2,因为您每次都会重置它们。

答案 2 :(得分:2)

使用filter扩展@Ouroborus解决方案并将结果保持在一起:

import itertools as it

# itertools recipe
def partition(pred, iterable):
    t1, t2 = it.tee(iterable)
    return it.filterfalse(pred, t1), filter(pred, t2)

>>> facs = ['one','two','three']
>>> [[[x[1] for x in f] for f in partition(lambda x: x[0], zip(pattern, facs))]
...  for pattern in product([True, False], repeat=len(facs))]
[[[], ['one', 'two', 'three']],
 [['three'], ['one', 'two']],
 [['two'], ['one', 'three']],
 [['two', 'three'], ['one']],
 [['one'], ['two', 'three']],
 [['one', 'three'], ['two']],
 [['one', 'two'], ['three']],
 [['one', 'two', 'three'], []]]

答案 3 :(得分:1)

为了完整性,您还可以fold the powerset in half生成所需的结果。例如,根据每个子集的相应位掩码,考虑{A, B, C}的幂函数的联合编码:

{}, {A}, {B}, {A, B} | {C}, {A, C}, {B, C}, {A, B, C}

如果顺时针旋转前半部分90度,逆时针旋转后半部分90度,然后将它们对齐,则有两列子集,每行形成原始集合的分区。我们可以实现这个"折叠"通过自身的反向压缩powerset并获取一半生成的子集。取一半确保只生成唯一的分区(例如[['two', 'three'], ['one']][['one'], ['two', 'three']]是相同的分区),假设原始序列本身是不同的。

import itertools

def binary_splits(a):
    partitions = zip(powerset_colex(a), powerset_colex(a, reverse = True))
    return itertools.islice(partitions, 1 << len(a) >> 1)

def powerset_colex(a, reverse = False):
    n = len(a)
    bitmasks = range(1 << n)[::-1] if reverse else range(1 << n)
    return (list(itertools.compress(a, iter_bits(bits, n))) for bits in bitmasks)

def iter_bits(n, k):
    return (n >> i & 1 for i in range(k))

虽然它不是很有用,但却是一个可爱的解决方案。这里有几个变体 - 而不是反向运行两个powerset迭代器 - 只是直接生成每个子集的补码。

def binary_splits_1(a):
    n = len(a)

    for bitmask in range(1 << n >> 1):
        subset     = itertools.compress(a, iter_bits(+bitmask, n))
        complement = itertools.compress(a, iter_bits(~bitmask, n))
        yield list(subset), list(complement)

def binary_splits_2(a):
    n = len(a)

    def dual_compress(items, bitmask):
        buckets = [], []

        for item, bit in zip(items, iter_bits(bitmask, n)):
            buckets[1 - bit].append(item)

        return buckets

    return (dual_compress(a, bitmask) for bitmask in range(1 << n >> 1))