La meilleure façon de trouver l'intersection de plusieurs ensembles?

267

J'ai une liste d'ensembles:

setlist = [s1,s2,s3...]

Je veux s1 ∩ s2 ∩ s3 ...

Je peux écrire une fonction pour le faire en effectuant une série de paires s1.intersection(s2), etc.

Existe-t-il une méthode recommandée, meilleure ou intégrée?

user116293
la source

Réponses:

454

À partir de Python version 2.6, vous pouvez utiliser plusieurs arguments pour set.intersection(), comme

u = set.intersection(s1, s2, s3)

Si les ensembles sont dans une liste, cela se traduit par:

u = set.intersection(*setlist)

*a_listest l' expansion de la liste

Notez que ce set.intersectionn'est pas une méthode statique, mais cela utilise la notation fonctionnelle pour appliquer l'intersection du premier ensemble avec le reste de la liste. Donc, si la liste d'arguments est vide, cela échouera.

qch
la source
65

Depuis 2.6, set.intersectionprend arbitrairement de nombreux itérables.

>>> s1 = set([1, 2, 3])
>>> s2 = set([2, 3, 4])
>>> s3 = set([2, 4, 6])
>>> s1 & s2 & s3
set([2])
>>> s1.intersection(s2, s3)
set([2])
>>> sets = [s1, s2, s3]
>>> set.intersection(*sets)
set([2])
Mike Graham
la source
24

C'est clairement set.intersectionce que vous voulez ici, mais au cas où vous auriez besoin d'une généralisation de "prendre la somme de tout cela", "prendre le produit de tout cela", "prendre le xor de tout cela", ce que vous cherchez c'est le reducefonction:

from operator import and_
from functools import reduce
print(reduce(and_, [{1,2,3},{2,3,4},{3,4,5}])) # = {3}

ou

print(reduce((lambda x,y: x&y), [{1,2,3},{2,3,4},{3,4,5}])) # = {3}
Thomas Ahle
la source
12

Si vous n'avez pas Python 2.6 ou supérieur, l'alternative est d'écrire une boucle for explicite:

def set_list_intersection(set_list):
  if not set_list:
    return set()
  result = set_list[0]
  for s in set_list[1:]:
    result &= s
  return result

set_list = [set([1, 2]), set([1, 3]), set([1, 4])]
print set_list_intersection(set_list)
# Output: set([1])

Vous pouvez également utiliser reduce:

set_list = [set([1, 2]), set([1, 3]), set([1, 4])]
print reduce(lambda s1, s2: s1 & s2, set_list)
# Output: set([1])

Cependant, de nombreux programmeurs Python ne l'aiment pas, y compris Guido lui - même :

Il y a environ 12 ans, Python a acquis lambda, Reduce (), filter () et map (), gracieuseté (je crois) d'un pirate Lisp qui les a manqués et a soumis des correctifs de travail. Mais, malgré la valeur PR, je pense que ces fonctionnalités devraient être coupées de Python 3000.

Alors maintenant, réduisez (). C'est en fait celui que j'ai toujours détesté le plus, car, à l'exception de quelques exemples impliquant + ou *, presque chaque fois que je vois un appel Reduce () avec un argument de fonction non trivial, je dois saisir un stylo et du papier pour diagramme ce qui est réellement introduit dans cette fonction avant de comprendre ce que la fonction de réduction () est censée faire. Donc, dans mon esprit, l'applicabilité de Reduce () est à peu près limitée aux opérateurs associatifs, et dans tous les autres cas, il est préférable d'écrire explicitement la boucle d'accumulation.

Ayman Hourieh
la source
8
Notez que Guido dit que l'utilisation reduceest "limitée aux opérateurs associatifs", ce qui est applicable dans ce cas. reduceest très souvent difficile à comprendre, mais ce &n'est pas si mal.
Mike Graham
Consultez python.org/doc/essays/list2str pour des optimisations utiles impliquant réduire. Il peut en général être utilisé assez bien pour créer des listes, des ensembles, des chaînes, etc. Vaut le coup d'œil également sur github.com/EntilZha/PyFunctional
Andreas
Notez que vous pouvez optimiser en interrompant votre boucle lorsqu'elle resultest vide.
bfontaine
1

Ici, je propose une fonction générique pour l'intersection de plusieurs ensembles en essayant de tirer parti de la meilleure méthode disponible:

def multiple_set_intersection(*sets):
    """Return multiple set intersection."""
    try:
        return set.intersection(*sets)
    except TypeError: # this is Python < 2.6 or no arguments
        pass

    try: a_set= sets[0]
    except IndexError: # no arguments
        return set() # return empty set

    return reduce(a_set.intersection, sets[1:])

Guido pourrait ne pas aimer reduce, mais je l'aime un peu :)

tzot
la source
Vous devriez vérifier la longueur de setsau lieu d'essayer d'accéder sets[0]et d'attraper le IndexError.
bfontaine
Ce n'est pas un simple chèque; a_setest utilisé lors du retour final.
tzot
Tu ne peux pas return reduce(sets[0], sets[1:]) if sets else set()?
bfontaine
Ha oui, merci. Le code doit changer car il est préférable d'éviter de s'appuyer sur un try/ except. C'est une odeur de code, est inefficace et peut cacher d'autres problèmes.
bfontaine
0

La réponse de Jean-François Fabre set.intesection (* list_of_sets) est certainement la plus pyhtonique et est à juste titre la réponse acceptée.

Pour ceux qui souhaitent utiliser réduire, les éléments suivants fonctionneront également:

reduce(set.intersection, list_of_sets)

Minas
la source