Python—从大量组合中构建满足特定条件的子列表
我已经读了很长时间了,这是我第一次没能找到我正在研究的问题的答案 我有一个93个字符串的列表,每个字符串都有6个字符长。从这93个字符串中,我想确定一组20个字符串,它们都符合集合中其他字符串的特定标准。虽然itertools.combinations将提供所有可能的组合,但并非所有条件都值得检查 例如,如果[list[0]、list[1]等失败,因为list[0]和list[1]不能在一起,那么不管其他18个字符串是什么,集合每次都会失败,这是一大堆浪费的检查 目前我有20个嵌套for循环,但似乎必须有更好/更快的方法Python—从大量组合中构建满足特定条件的子列表,python,for-loop,itertools,Python,For Loop,Itertools,我已经读了很长时间了,这是我第一次没能找到我正在研究的问题的答案 我有一个93个字符串的列表,每个字符串都有6个字符长。从这93个字符串中,我想确定一组20个字符串,它们都符合集合中其他字符串的特定标准。虽然itertools.combinations将提供所有可能的组合,但并非所有条件都值得检查 例如,如果[list[0]、list[1]等失败,因为list[0]和list[1]不能在一起,那么不管其他18个字符串是什么,集合每次都会失败,这是一大堆浪费的检查 目前我有20个嵌套for循环,但
for n1 in bclist:
building = [n1]
n2bclist = [bc for bc in bclist if bc not in building]
for n2 in n2bclist: #this is the start of what gets repeated 19 times
building.append(n2)
if test_function(building): #does set fail? (counter intuitive, True when fail, False when pass)
building.remove(n2)
continue
n3bclist = [bc for bc in bclist if bc not in building]
#insert the additional 19 for loops, with n3 in n3, n4 in n4, etc
building.remove(n2)
在第20个for循环中有print语句,用于在存在一组20时提醒我。for语句至少允许我在单次加法失败时提前跳过集合,但没有较大组合失败时的内存:
例如,[list[0],list[1]]
失败,因此跳到通过的[list[0],[list[2]]
。接下来是[list[0],list[2],list[1]
失败,因为0和1再次在一起,所以它将移动到可能通过或不通过的[list[0],list[2],list[3]
。我担心的是,它最终还会测试:
[列表[0]、列表[3]、列表[2]]
[列表[2],列表[0],列表[3]]
[列表[2],列表[3],列表[0]]
[列表[3]、列表[0]、列表[2]]
[列表[3],列表[2],列表[0]]
任何关于如何摆脱魔鬼的想法都将不胜感激。使用您当前的方法,但也要跟踪索引,以便在您的内部循环中可以跳过您已经检查过的元素:
bcenum = list(enumerate(bclist))
for i1, n1 in bcenum:
building = [n1]
for i2, n2 in bcenum[i1+1:]: #this is the start of what gets repeated 19 times
building.append(n2)
if test_function(building): #does set fail? (counter intuitive, True when fail, False when pass)
building.remove(n2)
continue
for i3, n3 in bcenum[i2+1:]:
# more nested loops
building.remove(n2)
这将从l
中选择基数n
的子集,以便test(subset)=False
它试图避免不必要的工作。但是,鉴于有120种方法可以从93个元素中选择20个元素,您可能需要重新考虑您的总体方法。您可以利用问题的两个方面:
test\u function(L)
为True
则L
的任何子列表的test\u function
也将为True
list[0]
-list[92]
-来简化一些事情-只有在test\u函数
中,我们才可能关心列表的内容
下面的代码首先找到可行的对,然后是四对集、八对集和十六对集。最后它找到十六和四的所有可行组合,得到20个列表。但是有100000多个八对集,所以速度仍然太慢,我放弃了。可能你可以按照同样的方法做一些事情但是用itertools
加快速度,但可能还不够
target = range(5, 25)
def test_function(L):
for i in L:
if not i in target:
return True
def possible_combos(A, B):
"""
Find all possible pairings of a list within A and a list within B
"""
R = []
for i in A:
for j in B:
if i[-1] < j[0] and not test_function(i + j):
R.append(i + j)
return R
def possible_doubles(A):
"""
Find all possible pairings of two lists within A
"""
R = []
for n, i in enumerate(A):
for j in A[n + 1:]:
if i[-1] < j[0] and not test_function(i + j):
R.append(i + j)
return R
# First, find all pairs that are okay
L = range(92)
pairs = []
for i in L:
for j in L[i + 1:]:
if not test_function([i, j]):
pairs.append([i, j])
# Then, all pairs of pairs
quads = possible_doubles(pairs)
print "fours", len(quads), quads[0]
# Then all sets of eight, and sixteen
eights = possible_doubles(quads)
print "eights", len(eights), eights[0]
sixteens = possible_doubles(eights)
print "sixteens", len(sixteens), sixteens[0]
# Finally check all possible combinations of a sixteen plus a four
possible_solutions = possible_combos(sixteens, fours)
print len(possible_solutions), possible_solutions[0]
您的解决方案应该基于
itertools.compositions
,因为这将解决订购问题;短路过滤相对容易解决
递归解
让我们快速回顾一下如何实现组合工作;最简单的方法是采用嵌套for循环方法并将其转换为递归样式:
def combinations(iterable, r):
pool = tuple(iterable)
for i in range(0, len(pool)):
for j in range(i + 1, len(pool)):
...
yield (i, j, ...)
转换为递归形式:
def combinations(iterable, r):
pool = tuple(iterable)
def inner(start, k, acc):
if k == r:
yield acc
else:
for i in range(start, len(pool)):
for t in inner(i + 1, k + 1, acc + (pool[i], )):
yield t
return inner(0, 0, ())
应用过滤器现在很容易:
def combinations_filterfalse(predicate, iterable, r):
pool = tuple(iterable)
def inner(start, k, acc):
if predicate(acc):
return
elif k == r:
yield acc
else:
for i in range(start, len(pool)):
for t in inner(i + 1, k + 1, acc + (pool[i], )):
yield t
return inner(0, 0, ())
让我们检查一下:
>>> list(combinations_filterfalse(lambda t: sum(t) % 2 == 1, range(5), 2))
[(0, 2), (0, 4), (2, 4)]
迭代解
中列出的itertools.compositions
的实际实现使用迭代循环:
def combinations(iterable, r):
pool = tuple(iterable)
n = len(pool)
if r > n:
return
indices = range(r)
yield tuple(pool[i] for i in indices)
while True:
for i in reversed(range(r)):
if indices[i] != i + n - r:
break
else:
return
indices[i] += 1
for j in range(i+1, r):
indices[j] = indices[j-1] + 1
yield tuple(pool[i] for i in indices)
为了优雅地适应谓词,有必要对循环稍微重新排序:
def combinations_filterfalse(predicate, iterable, r):
pool = tuple(iterable)
n = len(pool)
if r > n or predicate(()):
return
elif r == 0:
yield ()
return
indices, i = range(r), 0
while True:
while indices[i] + r <= i + n:
t = tuple(pool[k] for k in indices[:i+1])
if predicate(t):
indices[i] += 1
elif len(t) == r:
yield t
indices[i] += 1
else:
indices[i+1] = indices[i] + 1
i += 1
if i == 0:
return
i -= 1
indices[i] += 1
比较
结果表明,递归解决方案不仅更简单,而且速度更快:
In [33]: timeit list(combinations_filterfalse_rec(lambda t: False, range(20), 5))
10 loops, best of 3: 24.6 ms per loop
In [34]: timeit list(combinations_filterfalse_it(lambda t: False, range(20), 5))
10 loops, best of 3: 76.6 ms per loop
不重复元素。你认为组合
在做什么。组合(范围(4),3)-->012013023 123
顺序重要吗?例如[list[0],list[3],list[2]
是否可能通过,但[list[2],list[3],list[0]]
fail?@Stuart不,顺序不重要。您的两个示例都会失败,或者都会通过。@GP89对不起,我可能不够清楚。我的for循环目前模仿itertools.permutations
这是一个问题,因为顺序不重要,所以会测试许多不需要测试的东西。itertools.compositions
不重复元素,但将测试012和013,即使01是导致故障的原因,再次测试不需要测试的东西。目前为止喜欢这个解决方案。据我所知,这提供了非冗余测试(即不会同时测试[0,2,3,4]和[4,3,2,0]),并且不会浪费对故障集的测试(即[0,1]=失败,因此不测试[0,1,2])。仍在评估其他答案。进行了一些额外的改进,但这在合理的时间内完成了工作,并且对现有代码的更改最少。其他更改:if(len(building)+len(bcenum[i#::]<20:在测试功能测试之前添加中断
而不是附加、测试和删除:如果测试功能(构建+[n#]):继续
def combinations_filterfalse(predicate, iterable, r):
pool = tuple(iterable)
n = len(pool)
if r > n or predicate(()):
return
elif r == 0:
yield ()
return
indices, i = range(r), 0
while True:
while indices[i] + r <= i + n:
t = tuple(pool[k] for k in indices[:i+1])
if predicate(t):
indices[i] += 1
elif len(t) == r:
yield t
indices[i] += 1
else:
indices[i+1] = indices[i] + 1
i += 1
if i == 0:
return
i -= 1
indices[i] += 1
>>> list(combinations_filterfalse(lambda t: sum(t) % 2 == 1, range(5), 2))
[(0, 2), (0, 4), (2, 4)]
>>> list(combinations_filterfalse(lambda t: t == (1, 4), range(5), 2))
[(0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (2, 3), (2, 4), (3, 4)]
>>> list(combinations_filterfalse(lambda t: t[-1] == 3, range(5), 2))
[(0, 1), (0, 2), (0, 4), (1, 2), (1, 4), (2, 4)]
>>> list(combinations_filterfalse(lambda t: False, range(5), 2))
[(0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
>>> list(combinations_filterfalse(lambda t: False, range(5), 0))
[()]
In [33]: timeit list(combinations_filterfalse_rec(lambda t: False, range(20), 5))
10 loops, best of 3: 24.6 ms per loop
In [34]: timeit list(combinations_filterfalse_it(lambda t: False, range(20), 5))
10 loops, best of 3: 76.6 ms per loop