将列表拆分为两个列表的所有可能性
Posted
技术标签:
【中文标题】将列表拆分为两个列表的所有可能性【英文标题】:All possibilities to split a list into two lists 【发布时间】:2017-04-04 05:13:17 【问题描述】:我有一个包含一些元素的列表,并且想要遍历所有可能的方法来将该列表分成两个列表。我的意思是所有组合,所以顺序无关紧要(即元素 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)的方法来做到这一点?
【问题讨论】:
【参考方案1】:itertools
有 product()
可以用来生成掩码,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]])
【讨论】:
【参考方案2】:只需使用filter
s 扩展@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】:第一部分可能是单行嵌套列表推导式,如下所示:
patterns = [ [ i//(2**j)%2 for j in range(len(facs)-1) ] for i in range(2**(len(facs)-1)) ]
对于第二部分,由于有 2 个列表,您无法进行列表推导,但您可以使用三元表达式来选择要附加到的列表。
您可以同时zip
pattern
和facs
列表来避免使用索引:
for pattern in patterns:
l1 = [facs[-1]]
l2 = []
for fac,pat in zip(facs,pattern):
(l1 if pat == 1 else l2).append(fac)
当然你必须在迭代过程中使用l1
和l2
,因为你每次都重置它们。
【讨论】:
【参考方案4】:为了完整起见,您还可以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))
【讨论】:
以上是关于将列表拆分为两个列表的所有可能性的主要内容,如果未能解决你的问题,请参考以下文章