étant donné une liste et des exclusions, est-il possible d'ignorer le calcul des combinaisons contenant ces éléments?
Étant donné l = [1, 2, 3, 4, 5]
, je veux calculer toutes les combinaisons de size 4
et à l'exclusion des combinaisons qui contiennent (1, 3)
avant même calculées.
Les résultats seraient:
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]
Toutes les combinaisons contenant 1 et 3 ont été supprimées.
suggéré par @Eric Duminil
le résultat pour l = [1, 2, 3, 4, 5, 6]
, size 4
et
(1, 2, 3)
dans la deuxième colonneexcluant (1, 2)
dans la troisième colonne
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]
Toutes les combinaisons contenant 1 et 2 et 3 ont été supprimées des résultats souhaités 1
Toutes les combinaisons contenant 1 et 2 ont été supprimées des résultats souhaités 2
J'ai des combinaisons beaucoup plus grandes à calculer, mais cela prend beaucoup de temps et je veux réduire ce temps en utilisant ces exclusions.
Avec la méthode 1, les combinaisons sont toujours calculées
Avec la méthode 2, j’ai essayé de modifier la fonction combinaisons mais je n’ai pas trouvé le moyen approprié d’ignorer ma liste d’exclusion avant d’être calculée.
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)
Tout d’abord, merci à tous pour votre aide, j’ai oublié de donner plus de détails sur les contraintes.
L'ordre des sorties n'est pas pertinent, par exemple, si le résultat est [1, 2, 4, 5] [2, 3, 4, 5]
ou [2, 3, 4, 5] [1, 2, 4, 5]
, ce n'est pas important.
Les éléments des combinaisons doivent être (si possible) triés, [1, 2, 4, 5] [2, 3, 4, 5]
et non pas [2, 1, 5, 4] [3, 2, 4, 5]
, mais ce n'est pas important car les combinaisons pourraient être triées après.
La liste des exclusions est une liste de tous les éléments qui ne doivent pas apparaître dans les combinaisons together. Par exemple, si ma liste d'exclusion est (1, 2, 3)
, toutes les combinaisons contenant 1, 2 et 3 ne doivent pas être calculées. Cependant, les combinaisons avec 1 et 2 et non 3 sont autorisées. Dans ce cas, si j'exclus des combinaisons contenant (1, 2)
et (1, 2, 3)
, cela est totalement inutile car toutes les combinaisons qui seront filtrées par (1, 2, 3)
sont déjà filtrées par (1, 2)
Plusieurs listes d'exclusion doivent être possibles car j'utilise plusieurs contraintes pour mes combinaisons.
@tobias_k Cette solution considère la liste d'exclusion (1, 2, 3)
comme OR exclusion, ce qui signifie que (1, 2), (2, 3) and (1, 3)
sera exclu si j'ai bien compris, c'est utile dans un cas mais pas dans mon problème actuel, j'ai modifié la question pour donner plus de détails , Désolé pour la confusion. Dans votre réponse, je ne peux pas utiliser uniquement les listes (1, 2)
et (1, 3)
comme exclusion, comme vous l'avez spécifié. Cependant, le gros avantage de cette solution est de permettre plusieurs exclusions.
@Kasramvd et @mikuszefski Votre solution est vraiment proche de ce que je veux, si elle incluait plusieurs listes d'exclusions, ce serait la réponse.
Merci
(Comme il s'est avéré que ma réponse précédente ne répond pas vraiment aux contraintes de la question, en voici une autre. Je publie cette réponse séparément, car l'approche est très différente et la réponse d'origine peut toujours aider autres.)
Vous pouvez implémenter cela de manière récursive, avant chaque opération, pour ajouter un autre élément aux combinaisons en vérifiant si cela violerait l'un des ensembles d'exclusions. Cela ne génère pas de combinaisons non valides, et fonctionne avec des ensembles d'exclusions qui se chevauchent (comme (1,3), (1,5)
) et des ensembles d'exclusion avec plus de deux éléments (comme (2,4,5)
, permettant toutes les combinaisons sauf toutes ensemble).
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)
Exemple:
>>> 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]]
Eh bien, j'en ai pris le temps maintenant, et il s'avère que c'est beaucoup plus lent que d'utiliser naïvement itertools.combination
dans une expression génératrice avec un filtre, comme vous le faites déjà:
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))
Le calcul des combinaisons en Python est simplement plus lent que l'utilisation de la bibliothèque (qui est probablement implémentée en C) et le filtrage des résultats par la suite. En fonction du nombre de combinaisons pouvant être exclues, cette pourrait être plus rapide dans certains cas, mais pour être honnête, j'ai des doutes.
Vous pourriez obtenir de meilleurs résultats si vous pouvez utiliser itertools.combinations
pour les sous-problèmes, comme dans la réponse de Kasramvd , mais pour plusieurs ensembles d'exclusion non disjoints, c'est plus difficile. Une solution pourrait être de séparer les éléments de la liste en deux ensembles: ceux qui ont des contraintes et ceux qui ne les ont pas. Ensuite, utilisez itertoolc.combinations
pour les deux, mais vérifiez les contraintes uniquement pour les combinaisons des éléments où elles sont importantes. Vous devez toujours vérifier et filtrer les résultats, mais seulement une partie d'entre eux. (Une mise en garde, cependant: les résultats ne sont pas générés dans l'ordre, et l'ordre des éléments dans les combinaisons cédées est quelque peu altéré également.)
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))
C’est déjà bien mieux que la solution purement récursive en Python, mais pas aussi bon que l’approche "naïve" pour l’exemple ci-dessus:
>>> 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
Cependant, les résultats dépendent beaucoup de l'entrée. Pour une liste plus longue, avec des contraintes ne s'appliquant qu'à quelques-uns de ces éléments, cette approche est en fait plus rapide que la naïve:
>>> 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
(Il s'avère que cela ne fait pas exactement ce que veut OP. Laissons cela ici car cela pourrait aider les autres.)
Pour inclure des éléments mutuellement exclusifs, vous pouvez les insérer dans des listes de la liste, obtenir le combinations
de ceux-ci, puis le product
des combinaisons de sous-listes:
>>> 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)]
Un exemple plus complexe:
>>> 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)]
Cela ne génère aucune combinaison "indésirable" à filtrer par la suite. Cependant, cela suppose que vous souhaitiez au plus un élément de chaque groupe "exclusif", par exemple. dans le deuxième exemple, non seulement les combinaisons avec 2,4,5
, mais également celles avec 2,4
, 4,5
ou 2,5
, sont empêchées. De plus, il n'est pas possible (ou du moins pas facile) d'avoir exclusivement l'un des 1,3
et 1,5
, mais permet 3,5
. (Il serait peut-être possible de l'étendre à ces cas, mais je ne sais pas encore si et comment.)
Vous pouvez envelopper cette fonction dans une fonction, en déduisant le format d'entrée légèrement différent de votre format (présumé) et en renvoyant une expression génératrice correspondante. Ici, lst
est la liste des éléments, r
le nombre d’éléments par combinaisons et exclude_groups
une liste de groupes d’éléments qui s’excluent mutuellement:
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)
D'un point de vue algorithmique, vous pouvez séparer les éléments exclus et réinitialisés des éléments valides, calculer les combinaisons de chaque ensemble séparément et simplement concaténer le résultat en fonction de la longueur souhaitée. Cette approche refusera entièrement d'inclure tous les articles exclus en même temps, mais omettra la commande réelle.
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
"""
Démo:
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)]
Benckmark avec d'autres réponses:
Résultats: cette approche est plus rapide que les autres
# 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
J'ai tenté de modifier les combinaisons en fonction de vos besoins:
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)
Il va retourner quelque chose comme ça:
(1, 2, 4, 5) (2, 3, 4, 5)
J'ai fait l'exclusion lors de la combinaison en utilisant le code suivant pour économiser le temps de seconde boucle. il vous suffit de passer les indices des éléments exclus comme un ensemble.
update: violon en marche
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)
Je suppose que ma réponse est semblable à celle d’autres ici, mais c’est ce que j’ai trafiqué ensemble en parallèle.
from itertools import combinations, product
"""
with help from
https://stackoverflow.com/questions/374626/how-can-i-find-all-the-subsets-of-a-set-with-exactly-n-elements
https://stackoverflow.com/questions/32438350/python-merging-two-lists-with-all-possible-permutations
https://stackoverflow.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]
Algorithmiquement, vous devez calculer la combinaison des éléments de votre liste qui ne figurent pas parmi ceux exclus, puis ajouter les combinaisons respectives des éléments exclus à la combinaison du reste des éléments. Cette approche nécessite bien sûr de nombreuses vérifications et un suivi des index. Même si vous le faites en python, cela ne vous donnera pas une différence notable en termes de performances (connu sous le nom d’inconvénients de problème de satisfaction de contrainte ). (plutôt que de simplement les calculer en utilisant combination
et en filtrant les éléments indésirables).
Par conséquent, je pense que c'est la meilleure façon de faire dans la plupart des cas:
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)]