Risposta breve: usare not set(a).isdisjoint(b)
, è generalmente il più veloce.
Esistono quattro metodi comuni per verificare se due elenchi a
e condividono qualsiasi elemento. La prima opzione è quella di convertire sia ai set e verificare la loro intersezione, come ad esempio:
bool(set(a) & set(b))
Perché set sono memorizzati utilizzando una tabella hash in Python, cercando loro è O(1)
(vedi here per ulteriori informazioni sulla complessità operatori in Python). Teoricamente, questo è O(n+m)
in media per gli oggetti n
e m
negli elenchi a
e b
. Ma 1) deve prima creare degli insiemi dagli elenchi, che possono richiedere un tempo non trascurabile, e 2) suppone che le collisioni di hash siano sparse tra i tuoi dati.
Il secondo modo per farlo utilizza un generatore di espressione eseguire iterazione sugli elenchi, ad esempio:
any(i in a for i in b)
Questo permette di verificare sul posto, quindi non nuova memoria viene allocata per le variabili intermedie. Si salva anche sul primo ritrovamento. Ma l'operatore in
è sempre sugli elenchi (vedere here).
Un'altra opzione proposta è un iterare hybridto attraverso uno della lista, convertire l'altro in un set e di test per l'adesione su questo set, in questo modo:
a = set(a); any(i in a for i in b)
Un quarto approccio è quello di approfittare di il metodo isdisjoint()
dei set (congelati) (vedi here), ad esempio:
not set(a).isdisjoint(b)
Se gli elementi di ricerca sono vicino all'inizio di un array (ad esempio, è ordinato), il generatore di espressione è favorita, come gli insiemi si incrociano THOD devono allocare nuova memoria per le variabili intermedie:
from timeit import timeit
>>> timeit('bool(set(a) & set(b))', setup="a=list(range(1000));b=list(range(1000))", number=100000)
26.077727576019242
>>> timeit('any(i in a for i in b)', setup="a=list(range(1000));b=list(range(1000))", number=100000)
0.16220548999262974
Ecco un grafico del tempo di esecuzione di questo esempio in funzione della dimensione della lista:
noti che entrambi gli assi sono logaritmica. Questo rappresenta il caso migliore per l'espressione del generatore. Come si può vedere, il metodo isdisjoint()
è migliore per elenchi di dimensioni molto piccole, mentre l'espressione del generatore è migliore per elenchi di dimensioni più grandi.
D'altra parte, poiché la ricerca inizia con l'inizio per l'espressione ibrida e generatore, se l'elemento condiviso è sistematicamente alla fine dell'array (o entrambi gli elenchi non condividono alcun valore), disgiunti e imposta gli approcci di intersezione sono quindi molto più veloci rispetto all'espressione del generatore e all'approccio ibrido.
>>> timeit('any(i in a for i in b)', setup="a=list(range(1000));b=[x+998 for x in range(999,0,-1)]", number=1000))
13.739536046981812
>>> timeit('bool(set(a) & set(b))', setup="a=list(range(1000));b=[x+998 for x in range(999,0,-1)]", number=1000))
0.08102107048034668
E 'interessante notare che l'espressione generatore è il modo più lento per le taglie più grandi della lista. Questo è solo per 1000 ripetizioni, anziché 100000 per la figura precedente. Questa configurazione si approssima anche quando non sono condivisi elementi ed è il caso migliore per gli approcci di intersezione disgiunti e impostati.
Qui ci sono due analisi utilizzando numeri casuali (invece di brogli il setup di favorire una tecnica o di un altro):
un'alta probabilità di condivisione: elementi sono presi a caso da [1, 2*len(a)]
. Scarse possibilità di condivisione: gli elementi sono presi a caso da [1, 1000*len(a)]
.
Fino ad ora, questa analisi supponeva che entrambi gli elenchi abbiano le stesse dimensioni.Nel caso di due elenchi di diverse dimensioni, per esempio a
è molto più piccolo, isdisjoint()
è sempre più veloce:
Assicurarsi che l'elenco a
è il più piccolo, in caso contrario le prestazioni diminuisce. In questo esperimento, la dimensione dell'elenco a
è stata impostata su 5
.
In sintesi:
- Se le liste sono molto piccole (< 10 elementi),
not set(a).isdisjoint(b)
è sempre il più veloce.
- Se gli elementi negli elenchi sono ordinati o hanno una struttura regolare che è possibile sfruttare, l'espressione del generatore
any(i in a for i in b)
è la più veloce in elenchi di dimensioni grandi;
- Verificare l'intersezione impostata con
not set(a).isdisjoint(b)
, che è sempre più veloce di bool(set(a) & set(b))
.
- L'ibrido "iterate through list, test on set"
a = set(a); any(i in a for i in b)
è generalmente più lento di altri metodi.
- L'espressione del generatore e l'ibrido sono molto più lenti degli altri due approcci quando si tratta di elenchi senza elementi di condivisione.
Nella maggior parte dei casi, utilizzando il metodo isdisjoint()
è l'approccio migliore come espressione generatore vorrà molto più tempo per eseguire, in quanto è molto inefficiente quando non elementi sono condivisi.
Le uniche ottimizzazioni a cui riesco a pensare è l'eliminazione di 'len (...)> 0' perché' bool (set ([])) 'restituisce False. E, naturalmente, se si tenessero gli elenchi come set per cominciare, si risparmia il sovraccarico della creazione del set. – msw
Rilevante: https://stackoverflow.com/a/44786707/1959808 –
Si noti che non è possibile distinguere 'True' da' 1' e 'False' da' 0'. 'non impostato ([1]). isdisjoint ([True])' diventa 'True', lo stesso con altre soluzioni. – Dimali