Il modo migliore per trovare l'intersezione di più insiemi?


267

Ho un elenco di set:

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

Voglio s1 ∩ s2 ∩ s3 ...

Posso scrivere una funzione per farlo eseguendo una serie di coppie s1.intersection(s2), ecc.

Esiste un modo consigliato, migliore o integrato?

Risposte:


454

Da Python versione 2.6 in poi puoi usare più argomenti per set.intersection(), come

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

Se i set sono in un elenco, questo si traduce in:

u = set.intersection(*setlist)

dove *a_listè l'espansione della lista

Si noti che nonset.intersection è un metodo statico, ma utilizza la notazione funzionale per applicare l'intersezione del primo set con il resto dell'elenco. Quindi, se l'elenco degli argomenti è vuoto, ciò fallirà.


65

A partire da 2.6, set.intersectionprende arbitrariamente molti iterabili.

>>> 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])

24

Chiaramente set.intersectionè quello che vuoi qui, ma nel caso in cui tu abbia mai bisogno di una generalizzazione di "prendi la somma di tutti questi", "prendi il prodotto di tutti questi", "prendi il xor di tutti questi", quello che stai cercando è il reducefunzione:

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

o

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

12

Se non hai Python 2.6 o versioni successive, l'alternativa è scrivere un esplicito per loop:

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])

Puoi anche usare reduce:

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

Tuttavia, a molti programmatori di Python non piace, incluso lo stesso Guido :

Circa 12 anni fa, Python ha acquisito lambda, reduce (), filter () e map (), per gentile concessione di (credo) un hacker Lisp che li ha persi e ha inviato patch funzionanti. Ma, nonostante il valore di PR, penso che queste funzionalità dovrebbero essere tagliate da Python 3000.

Quindi ora riduci (). Questo è in realtà quello che ho sempre odiato di più, perché, a parte alcuni esempi che coinvolgono + o *, quasi ogni volta che vedo una chiamata ridurre () con un argomento di funzione non banale, devo prendere carta e penna per diagramma cosa viene effettivamente immesso in quella funzione prima di capire cosa dovrebbe fare il riduttore (). Quindi, nella mia mente, l'applicabilità di reduce () è praticamente limitata agli operatori associativi, e in tutti gli altri casi è meglio scrivere esplicitamente il ciclo di accumulazione.


8
Nota che Guido afferma che l'utilizzo reduceè "limitato agli operatori associativi", che è applicabile in questo caso. reduceè molto spesso difficile da capire, ma &non è poi così male.
Mike Graham,


Dai un'occhiata a python.org/doc/essays/list2str per utili ottimizzazioni che implicano ridurre. In generale può essere usato abbastanza bene per costruire liste, set, stringhe ecc. Vale la pena dare un'occhiata anche a github.com/EntilZha/PyFunctional
Andreas

Nota che puoi ottimizzare interrompendo il ciclo quando resultè vuoto.
bfontaine,

1

Qui sto offrendo una funzione generica per intersezioni di più insiemi cercando di sfruttare il miglior metodo disponibile:

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:])

A Guido potrebbe non piacere reduce, ma mi piace un po ':)


Dovresti controllare la lunghezza setsinvece di provare ad accedere sets[0]e catturare il file IndexError.
bfontaine,

Questo non è un semplice controllo; a_setviene utilizzato al ritorno finale.
dal

Non puoi farlo return reduce(sets[0], sets[1:]) if sets else set()?
bfontaine,

Ah sì, grazie. Il codice dovrebbe cambiare perché fare affidamento su try/ exceptdovrebbe essere evitato, se possibile. È un odore di codice, è inefficiente e può nascondere altri problemi.
bfontaine,

0

Jean-François Fabre set.intesection (* list_of_sets) la risposta è sicuramente la più pitonica ed è giustamente la risposta accettata.

Per coloro che vogliono utilizzare la riduzione, funzionerà anche:

reduce(set.intersection, list_of_sets)

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.