在计算之前删除包含某些值的组合
Posted
技术标签:
【中文标题】在计算之前删除包含某些值的组合【英文标题】:Remove combinations that contains some values before even calculated 【发布时间】:2018-07-14 23:05:47 【问题描述】:给定一个列表和排除元素,是否可以忽略包含这些元素的组合计算?
示例 1
给定l = [1, 2, 3, 4, 5]
,我想在计算之前计算size 4
的所有组合,并排除包含(1, 3)
的组合。
结果是:
All results: Wanted results:
[1, 2, 3, 4] [1, 2, 4, 5]
[1, 2, 3, 5] [2, 3, 4, 5]
[1, 2, 4, 5]
[1, 3, 4, 5]
[2, 3, 4, 5]
所有包含 1 和 3 的组合均已删除。
示例 2
@Eric Duminil 建议
l = [1, 2, 3, 4, 5, 6]
、size 4
和
(1, 2, 3)
不包括第三列中的(1, 2)
All results: Wanted results 1 Wanted results 2
(Excluding [1, 2, 3]): (Excluding [1, 2])
[1, 2, 3, 4] [1, 2, 4, 5] [1, 3, 4, 5]
[1, 2, 3, 5] [1, 2, 4, 6] [1, 3, 4, 6]
[1, 2, 3, 6] [1, 2, 5, 6] [1, 3, 5, 6]
[1, 2, 4, 5] [1, 3, 4, 5] [1, 4, 5, 6]
[1, 2, 4, 6] [1, 3, 4, 6] [2, 3, 4, 5]
[1, 2, 5, 6] [1, 3, 5, 6] [2, 3, 4, 6]
[1, 3, 4, 5] [1, 4, 5, 6] [2, 3, 5, 6]
[1, 3, 4, 6] [2, 3, 4, 5] [2, 4, 5, 6]
[1, 3, 5, 6] [2, 3, 4, 6] [3, 4, 5, 6]
[1, 4, 5, 6] [2, 3, 5, 6]
[2, 3, 4, 5] [2, 4, 5, 6]
[2, 3, 4, 6] [3, 4, 5, 6]
[2, 3, 5, 6]
[2, 4, 5, 6]
[3, 4, 5, 6]
所有包含 1 和 2 和 3 的组合都已从想要的结果 1 中删除
所有包含 1 和 2 的组合都已从想要的结果 2 中删除
我有更大的组合要计算,但这需要很多时间,我想使用这些排除项来减少这个时间。
尝试过的解决方案
使用方法1,仍然计算组合
使用方法2,我尝试修改combinations function,但在计算之前我找不到合适的方法来忽略我的排除列表。
Method 1 | Method 2
|
def main(): | def combinations(iterable, r):
l = list(range(1, 6)) | pool = tuple(iterable)
comb = combinations(l, 4) | n = len(pool)
| if r > n:
for i in comb: | return
if set([1, 3]).issubset(i): | indices = list(range(r))
continue | yield tuple(pool[i] for i in indices)
else | while True:
process() | 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)
编辑:
首先,谢谢大家的帮助,我忘了提供更多关于约束的细节。
输出的顺序不相关,例如,如果结果是[1, 2, 4, 5] [2, 3, 4, 5]
或[2, 3, 4, 5] [1, 2, 4, 5]
,则不重要。
组合的元素应该(如果可能的话)排序,[1, 2, 4, 5] [2, 3, 4, 5]
而不是[2, 1, 5, 4] [3, 2, 4, 5]
,但这并不重要,因为组合可以在之后排序。
排除列表是不应出现组合一起的所有项目的列表。例如,如果我的排除列表是 (1, 2, 3)
,则不应计算包含 1 和 2 和 3 的所有组合。但是,允许使用 1 和 2 而不是 3 的组合。在这种情况下,如果我排除包含 (1, 2)
和 (1, 2, 3)
的组合,那将完全没有用,因为将由 (1, 2, 3)
过滤的所有组合都已由 (1, 2)
过滤
多个排除列表必须是可能的,因为我对我的组合使用了多个约束。
测试答案
@tobias_k
此解决方案将排除列表 (1, 2, 3)
视为 OR 排除含义,如果我理解良好,将排除 (1, 2), (2, 3) and (1, 3)
,这在一个案例中很有用,但不适用于我当前的问题,我修改了问题以提供更多详细信息,抱歉造成混淆。在您的回答中,我不能仅使用列表 (1, 2)
和 (1, 3)
作为您指定的排除项。然而,这种解决方案的一大优势是允许多重排除。
@Kasramvd 和 @mikuszefski 您的解决方案非常接近我想要的,如果它确实包含多个排除列表,那就是答案。
谢谢
【问题讨论】:
您是否需要按照您在问题中描述的方式对输出元素进行排序? 你能补充一点关于排他性约束的内容吗?如果你有一组三个独占号码,这是否意味着你只能拥有这三个中的一个,或者你可以拥有两个?你能有像“只有 1 和 3 中的一个,只有 1 和 5 中的一个,但 3 和 5 还可以”这样的约束吗?l = [1, 2, 3, 4, 5, 6]
、size 4
和不包括(1, 2, 3)
的结果应该是什么?
【参考方案1】:
(事实证明这并不完全符合 OP 的要求。仍然将其留在这里,因为它可能对其他人有所帮助。)
要包含互斥元素,您可以将它们包装在列表中的列表中,获取其中的combinations
,然后获取子列表组合的product
:
>>> from itertools import combinations, product
>>> l = [[1, 3], [2], [4], [5]]
>>> [c for c in combinations(l, 4)]
[([1, 3], [2], [4], [5])]
>>> [p for c in combinations(l, 4) for p in product(*c)]
[(1, 2, 4, 5), (3, 2, 4, 5)]
一个更复杂的例子:
>>> l = [[1, 3], [2, 4, 5], [6], [7]]
>>> [c for c in combinations(l, 3)]
[([1, 3], [2, 4, 5], [6]),
([1, 3], [2, 4, 5], [7]),
([1, 3], [6], [7]),
([2, 4, 5], [6], [7])]
>>> [p for c in combinations(l, 3) for p in product(*c)]
[(1, 2, 6),
(1, 4, 6),
... 13 more ...
(4, 6, 7),
(5, 6, 7)]
这不会生成任何以后要过滤掉的“垃圾”组合。但是,它假定您最多需要每个“独占”组中的一个元素,例如在第二个示例中,它不仅阻止与2,4,5
的组合,而且还阻止与2,4
、4,5
或2,5
的组合。此外,不可能(或至少不容易)只拥有1,3
和1,5
之一,但允许3,5
。 (也许可以将其扩展到这些情况,但我还不确定是否以及如何。)
您可以将其包装在一个函数中,从您的(假定的)格式派生出稍微不同的输入格式并返回一个一致的生成器表达式。这里,lst
是元素列表,r
是每个组合的项目数,exclude_groups
是互斥元素组的列表:
from itertools import combinations, product
def comb_with_excludes(lst, r, exclude_groups):
ex_set = e for es in exclude_groups for e in es
tmp = exclude_groups + [[x] for x in lst if x not in ex_set]
return (p for c in combinations(tmp, r) for p in product(*c))
lst = [1, 2, 3, 4, 5, 6, 7]
excludes = [[1, 3], [2, 4, 5]]
for x in comb_with_excludes(lst, 3, excludes):
print(x)
【讨论】:
这个解决方案甚至没有改变问题本身,而且根本没有效率。查看我对这种方法的更多 Pythonic 版本的回答。 @Kasramvd 你能解释一下吗?在什么意义上它不是有效的?正如我所说,它不会产生任何要过滤的“垃圾”,但由于开销可能它仍然较慢?我没有计时。另外,“甚至不改变问题”是什么意思?您的意思是“不仅改变问题”吗?如果是这样,在什么意义上?只需稍微改变输入格式? 是的,更改输入格式是另一个问题。问题的重点是如何处理这些输入。关于您使用嵌套循环并同时使用products
和combinations
的性能。想象一下,如果排除和列表的数量更大,这种方法将如何执行。此外,组合使用列表对象也不是内存效率高(由于不变性和缺乏兑现等)。
@Kasramvd Ehrm... 是的,它有嵌套循环,但它仍然生成完全 OP 想要的组合(前提是我正确猜到了那些不清楚的排除列表是关于什么的确切地说,请参阅我的 cmets)。当然,如果这是问题所在,列表理解可以轻松地更改为生成器。并且可以预先对输入进行转换。
@Kasramvd 我认为 OP 的问题仅通过一个示例并没有得到很好的定义,这就是我要求澄清的原因。但是,我的回答确实为该确切问题提供了一个有效的解决方案(请参阅我对转换输入的编辑)以及我认为是“一般”情况的情况。【参考方案2】:
(事实证明,我的previous answer 并没有真正满足问题的限制,这是另一个问题。我将其作为单独的答案发布,因为方法大不相同,原始答案可能仍然有帮助其他人。)
您可以递归地实现这一点,每次在递归之前将另一个元素添加到组合中,检查这是否会违反其中一个排除集。这不会生成无效的组合,它适用于重叠的排除集(如(1,3), (1,5)
),以及具有两个以上元素的排除集(如(2,4,5)
,允许除所有元素之外的任何组合)。
def comb_with_excludes(lst, n, excludes, i=0, taken=()):
if n == 0:
yield taken # no more needed
elif i <= len(lst) - n:
t2 = taken + (lst[i],) # add current element
if not any(e.issubset(t2) for e in excludes):
yield from comb_with_excludes(lst, n-1, excludes, i+1, t2)
if i < len(lst) - n: # skip current element
yield from comb_with_excludes(lst, n, excludes, i+1, taken)
例子:
>>> lst = [1, 2, 3, 4, 5, 6]
>>> excludes = [1, 3, 1, 5, 2, 4, 5]
>>> list(comb_with_excludes(lst, 4, excludes))
[[1, 2, 4, 6], [2, 3, 4, 6], [2, 3, 5, 6], [3, 4, 5, 6]]
好吧,我现在已经计时了,结果证明这比在带有过滤器的生成器表达式中天真地使用 itertools.combination
慢得多,就像你已经做的那样:
def comb_naive(lst, r, excludes):
return (comb for comb in itertools.combinations(lst, r)
if not any(e.issubset(comb) for e in excludes))
在 Python 中计算组合只是比使用库(可能在 C 中实现)并随后过滤结果要慢。根据可以排除的组合数量,这可能在某些情况下会更快,但老实说,我对此表示怀疑。
如果您可以将itertools.combinations
用于子问题(如Kasramvd's answer),您可以获得更好的结果,但对于多个非分离的排除集则更加困难。一种方法可能是将列表中的元素分成两组:有约束的,没有约束的。然后,对两者都使用itertoolc.combinations
,但只检查那些重要元素的组合的约束。您仍然需要检查和过滤结果,但只是其中的一部分。 (不过需要注意的是:结果不是按顺序生成的,并且生成的组合中元素的顺序也有些混乱。)
def comb_with_excludes2(lst, n, excludes):
wout_const = [x for x in lst if not any(x in e for e in excludes)]
with_const = [x for x in lst if any(x in e for e in excludes)]
k_min, k_max = max(0, n - len(wout_const)), min(n, len(with_const))
return (c1 + c2 for k in range(k_min, k_max)
for c1 in itertools.combinations(with_const, k)
if not any(e.issubset(c1) for e in excludes)
for c2 in itertools.combinations(wout_const, n - k))
这已经比递归纯 Python 解决方案好得多,但仍不如上述示例的“幼稚”方法:
>>> lst = [1, 2, 3, 4, 5, 6]
>>> excludes = [1, 3, 1, 5, 2, 4, 5]
>>> %timeit list(comb_with_excludes(lst, 4, excludes))
10000 loops, best of 3: 42.3 µs per loop
>>> %timeit list(comb_with_excludes2(lst, 4, excludes))
10000 loops, best of 3: 22.6 µs per loop
>>> %timeit list(comb_naive(lst, 4, excludes))
10000 loops, best of 3: 16.4 µs per loop
但是,结果很大程度上取决于输入。对于更大的列表,约束仅适用于其中的几个元素,这种方法实际上比简单的方法更快:
>>> lst = list(range(20))
>>> %timeit list(comb_with_excludes(lst, 4, excludes))
10 loops, best of 3: 15.1 ms per loop
>>> %timeit list(comb_with_excludes2(lst, 4, excludes))
1000 loops, best of 3: 558 µs per loop
>>> %timeit list(comb_naive(lst, 4, excludes))
100 loops, best of 3: 5.9 ms per loop
【讨论】:
感谢您的回答,这正是我想要的!你之前的回答也很有趣,我也可以用它来解决另一个问题!再次感谢! 经过测试,这个解决方案似乎比计算所有组合要慢得多:/你知道为什么吗? pyfiddle.io/fiddle/a1f2b4f3-16d5-47b5-8bee-4984b3502931/?i=true @SyedElec 好吧,我猜itertools.combinations
效率太高(可能用 C 和所有其他语言实现),所以在纯 Python 中重新创建它时,它总是会慢得多。根据要排除的组合数量,在某些情况下可能仍然会更快,但总的来说,您可能确实最好只使用带有 itertools.combinations
和过滤器的生成器表达式......【参考方案3】:
从算法的角度来看,您可以将有效项目的排除和重置分开,并分别计算每个集合的组合,然后根据期望长度连接结果。这种方法将完全拒绝同时包含所有排除的项目,但会省略实际的顺序。
from itertools import combinations
def comb_with_exclude(iterable, comb_num, excludes):
iterable = tuple(iterable)
ex_len = len(excludes)
n = len(iterable)
if comb_num < ex_len or comb_num > n:
yield from combinations(iterable, comb_num)
else:
rest = [i for i in iterable if not i in excludes]
ex_comb_rang = range(0, ex_len)
rest_comb_range = range(comb_num, comb_num - ex_len, -1)
# sum of these pairs is equal to the comb_num
pairs = zip(ex_comb_rang, rest_comb_range)
for i, j in pairs:
for p in combinations(excludes, i):
for k in combinations(rest, j):
yield k + p
"""
Note that instead of those nested loops you could wrap the combinations within a product function like following:
for p, k in product(combinations(excludes, i), combinations(rest, j)):
yield k + p
"""
演示:
l = [1, 2, 3, 4, 5, 6, 7, 8]
ex = [2, 5, 6]
print(list(comb_with_exclude(l, 6, ex)))
[(1, 3, 4, 7, 8, 2), (1, 3, 4, 7, 8, 5), (1, 3, 4, 7, 8, 6), (1, 3, 4, 7, 2, 5), (1, 3, 4, 8, 2, 5), (1, 3, 7, 8, 2, 5), (1, 4, 7, 8, 2, 5), (3, 4, 7, 8, 2, 5), (1, 3, 4, 7, 2, 6), (1, 3, 4, 8, 2, 6), (1, 3, 7, 8, 2, 6), (1, 4, 7, 8, 2, 6), (3, 4, 7, 8, 2, 6), (1, 3, 4, 7, 5, 6), (1, 3, 4, 8, 5, 6), (1, 3, 7, 8, 5, 6), (1, 4, 7, 8, 5, 6), (3, 4, 7, 8, 5, 6)]
l = [1, 2, 3, 4, 5]
ex = [1, 3]
print(list(comb_with_exclude(l, 4, ex)))
[(2, 4, 5, 1), (2, 4, 5, 3)]
以其他答案为基准:
结果:这种方法比其他方法更快
# this answer
In [169]: %timeit list(comb_with_exclude(lst, 3, excludes[0]))
100000 loops, best of 3: 6.47 µs per loop
# tobias_k
In [158]: %timeit list(comb_with_excludes(lst, 3, excludes))
100000 loops, best of 3: 13.1 µs per loop
# Vikas Damodar
In [166]: %timeit list(combinations_exc(lst, 3))
10000 loops, best of 3: 148 µs per loop
# mikuszefski
In [168]: %timeit list(sub_without(lst, 3, excludes[0]))
100000 loops, best of 3: 12.52 µs per loop
【讨论】:
不错。但不是真正可比的,因为函数计算的东西略有不同。这并不意味着任何一个都是错误的,只是 OP 的问题并不完全清楚。另外,您忘记在第一个%timeit
中使用生成器;-)(对不起)
@tobias_k 是的,刚刚修复;))。他们都试图返回相同的结果,但关于您接受多个排除的方法,我只是将一个传递给它。
这不仅仅是多个排除集:在[2,4,5]
的情况下,我只允许三个数字中的一个,而您允许其中两个,只要不是全部三个即可。不过,对于[1,3]
,两者都应该产生相同的结果。但正如我所说,我们真的不知道它应该是哪一个。【参考方案4】:
我已尝试根据您的要求编辑组合:
def combinations(iterable, r):
# combinations('ABCD', 2) --> AB AC AD BC BD CD
# combinations(range(4), 3) --> 012 013 023 123
pool = tuple(iterable)
n = len(pool)
if r > n:
return
indices = list(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
# print(tuple(pool[i] for i in indices ), "hai")
if 1 in tuple(pool[i] for i in indices ) and 3 in tuple(pool[i] for i in indices ):
pass
else:
yield tuple(pool[i] for i in indices)
d = combinations(list(range(1, 6)),4)
for i in d:
print(i)
它将返回如下内容:
(1, 2, 4, 5) (2, 3, 4, 5)
【讨论】:
true,但这是问题中的示例 1,它仍然计算所有组合。 我知道这不是一个好办法,但是如果他想继续我给他的第一个解决方案,可能是一个临时解决方案。 我看不到建议在哪里。这是 OPs 代码,增加了创建列表的开销。 @hiroprotagonist 我已经尝试按照 OP 要求的方式去做,请教你更好的建议 @Matthias 我已经进行了编辑,我不确定这是不是一个好方法。【参考方案5】:我在组合过程中使用以下代码进行了排除以节省第二次循环时间。您只需要将排除元素的索引作为一组传递。
更新:working fiddle
from itertools import permutations
def combinations(iterable, r, combIndeciesExclusions=set()):
pool = tuple(iterable)
n = len(pool)
for indices in permutations(range(n), r):
if ( len(combIndeciesExclusions)==0 or not combIndeciesExclusions.issubset(indices)) and sorted(indices) == list(indices):
yield tuple(pool[i] for i in indices)
l = list(range(1, 6))
comb = combinations(l, 4, set([0,2]))
print list(comb)
【讨论】:
你的解决方案不起作用,例如combinations([1, 2, 3], 2, set([1, 2]))
,谢谢
@SyedElec 你在说什么?您的查询给出了这个结果[(1, 2), (1, 3)]
,这是正确的。排除索引 1 和索引 2 意味着您不希望将值 2 和 3 放在同一个组合中。请注意,您正在设置索引而不是值。这将为您提供您想要的结果。
那么,接受元素集并在内部将它们转换为索引集可能会更简单。此外,这仍在计算所有组合,然后才检查它们是否有效。更糟糕的是,事实上:它生成所有排列,然后检查它们是否是组合(其中数量要少得多),然后 然后 检查它们是否有效。
@SyedElec 实际上这个方法来自于你的实现,查看下一个替代实现块docs.python.org/3/library/itertools.html#itertools.combinations 我只是调整了过滤过程以排除通过的索引集。你可以做基准测试,它不会有很大的不同。
@CME64 抱歉,我没有正确阅读您的答案,我正在测试所有答案的同一过程中,我没有读到您正在使用索引而不是值,python3 doc 中的代码并不是真的针对我的问题进行了优化。【参考方案6】:
我想我的答案与这里的其他人相似,但这是我并行摆弄的
from itertools import combinations, product
"""
with help from
https://***.com/questions/374626/how-can-i-find-all-the-subsets-of-a-set-with-exactly-n-elements
https://***.com/questions/32438350/python-merging-two-lists-with-all-possible-permutations
https://***.com/questions/952914/making-a-flat-list-out-of-list-of-lists-in-python
"""
def sub_without( S, m, forbidden ):
out = []
allowed = [ s for s in S if s not in forbidden ]
N = len( allowed )
for k in range( len( forbidden ) ):
addon = [ list( x ) for x in combinations( forbidden, k) ]
if N + k >= m:
base = [ list( x ) for x in combinations( allowed, m - k ) ]
leveltotal = [ [ item for sublist in x for item in sublist ] for x in product( base, addon ) ]
out += leveltotal
return out
val = sub_without( range(6), 4, [ 1, 3, 5 ] )
for x in val:
print sorted(x)
>>
[0, 1, 2, 4]
[0, 2, 3, 4]
[0, 2, 4, 5]
[0, 1, 2, 3]
[0, 1, 2, 5]
[0, 2, 3, 5]
[0, 1, 3, 4]
[0, 1, 4, 5]
[0, 3, 4, 5]
[1, 2, 3, 4]
[1, 2, 4, 5]
[2, 3, 4, 5]
【讨论】:
【参考方案7】:从算法上讲,您必须计算列表中不属于排除项目的项目组合,然后将排除项目的相应组合添加到其余项目的组合中。这种方法当然需要大量检查并且需要跟踪索引,即使您在 python 中执行它也不会给您带来显着的性能差异(称为Constraint satisfaction problem 的缺点)。 (而不仅仅是使用combination
计算它们并过滤掉不需要的项目)。
因此,我认为在大多数情况下这是最好的方法:
In [77]: from itertools import combinations, filterfalse
In [78]: list(filterfalse(1, 3.issubset, combinations(l, 4)))
Out[78]: [(1, 2, 4, 5), (2, 3, 4, 5)]
【讨论】:
@tobias_k 我刚刚决定删除该解决方案。检查更新。 好吧,现在它基本上是 OP 已经在做的事情,只是在一行中,并且没有在每次迭代中重新创建集合。 (但并不是说这很糟糕;完全取决于实际更大的数据集中的“垃圾”数量。) @downvoter 你能解释一下你投反对票的原因吗? 这不是我的反对意见,但如果我不得不猜测,可能是因为“没有显着差异”的说法,这完全取决于组合的总数和比例那些不必生成的。 @tobias_k 我知道不是你。但不是!没那么简单。正如我在 in python 中提到的,如果您可以使用较低抽象的语言来执行此操作,它将给您带来显着的差异,但在 python 中,为了满足这些条件,您必须跟踪项目的索引,删除排除的一次并进行许多其他需要花费大量时间的操作。这被称为Constraint satisfaction problem 的缺点。以上是关于在计算之前删除包含某些值的组合的主要内容,如果未能解决你的问题,请参考以下文章
如何在mongodb中获取包含某些(字符串)值的集合的所有键