web-dev-qa-db-fra.com

Supprimer les combinaisons qui contiennent des valeurs avant même calculées

étant donné une liste et des exclusions, est-il possible d'ignorer le calcul des combinaisons contenant ces éléments?

Exemple 1

É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.

Exemple 2

suggéré par @Eric Duminil

le résultat pour l = [1, 2, 3, 4, 5, 6], size 4 et 

  • excluant (1, 2, 3) dans la deuxième colonne
  • excluant (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.

Solutions essayées

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)

MODIFIER:

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.

Réponses testées

@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

18
SyedElec

(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
1
tobias_k

(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)
5
tobias_k

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
4
Kasrâmvd

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)

1
Vikas Damodar

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)
1
CME64

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]
0
mikuszefski

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)]
0
Kasrâmvd