Risposta breve : usare not set(a).isdisjoint(b)
, è generalmente il più veloce.
Esistono quattro modi comuni per verificare se due elenchi a
e b
condividere elementi. La prima opzione è convertire entrambi in set e controllare la loro intersezione, come tale:
bool(set(a) & set(b))
Poiché i set vengono archiviati utilizzando una tabella hash in Python, la ricerca èO(1)
(vedere qui per ulteriori informazioni sulla complessità degli operatori in Python). Teoricamente, questo è O(n+m)
in media per n
e m
oggetti negli elenchi a
e b
. Ma 1) deve prima creare serie fuori dagli elenchi, il che può richiedere una quantità non trascurabile di tempo, e 2) supporre che le collisioni di hashing siano sparse tra i tuoi dati.
Il secondo modo per farlo è usare un'espressione del generatore che esegue l'iterazione sugli elenchi, come ad esempio:
any(i in a for i in b)
Ciò consente la ricerca sul posto, quindi non viene allocata nuova memoria per le variabili intermedie. Si salva anche alla prima scoperta. Ma l' in
operatore è sempre O(n)
in lista (vedi qui ).
Un'altra opzione proposta è un ibrido per scorrere l'elenco, convertire l'altro in un set e testare l'appartenenza a questo set, in questo modo:
a = set(a); any(i in a for i in b)
Un quarto approccio consiste nell'utilizzare il isdisjoint()
metodo degli insiemi (congelati) (vedere qui ), ad esempio:
not set(a).isdisjoint(b)
Se gli elementi ricercati sono vicini all'inizio di un array (ad es. È ordinato), viene favorita l'espressione del generatore, poiché il metodo di intersezione insiemi deve 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 per questo esempio in funzione della dimensione dell'elenco:
Si noti che entrambi gli assi sono logaritmici. Questo rappresenta il caso migliore per l'espressione del generatore. Come si può vedere, ilisdisjoint()
metodo è migliore per dimensioni di elenco molto piccole, mentre l'espressione del generatore è migliore per dimensioni di elenco più grandi.
D'altra parte, poiché la ricerca inizia con l'inizio dell'espressione ibrida e del generatore, se l'elemento condiviso è sistematicamente alla fine dell'array (o entrambi gli elenchi non condividono alcun valore), gli approcci di intersezione disgiunti e impostati sono quindi molto più veloce dell'espressione del generatore e dell'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
È interessante notare che l'espressione del generatore è molto più lenta per elenchi di dimensioni maggiori. Questo è solo per 1000 ripetizioni, anziché 100000 per la figura precedente. Questa impostazione si avvicina bene anche quando non sono condivisi elementi ed è il caso migliore per gli approcci di intersezione disgiunti e impostati.
Ecco due analisi usando numeri casuali (invece di attrezzare l'installazione per favorire una tecnica o un'altra):
Elevata possibilità di condivisione: gli elementi vengono presi casualmente [1, 2*len(a)]
. Bassa possibilità di condivisione: gli elementi vengono presi casualmente[1, 1000*len(a)]
.
Fino ad ora, questa analisi supponeva che entrambe le liste avessero le stesse dimensioni. Nel caso di due elenchi di dimensioni diverse, ad esempio a
è molto più piccolo, isdisjoint()
è sempre più veloce:
Assicurarsi che l' a
elenco sia più piccolo, altrimenti le prestazioni diminuiscono. In questo esperimento, la a
dimensione dell'elenco è stata impostata su costante5
.
In sintesi:
- Se gli elenchi sono molto piccoli (<10 elementi),
not set(a).isdisjoint(b)
è sempre il più veloce.
- Se gli elementi negli elenchi sono ordinati o hanno una struttura regolare di cui puoi trarre vantaggio, l'espressione del generatore
any(i in a for i in b)
è la più veloce su elenchi di grandi dimensioni;
- Prova l'intersezione impostata con
not set(a).isdisjoint(b)
, che è sempre più veloce dibool(set(a) & set(b))
.
- L'ibrido "scorre attraverso l'elenco, prova sul 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 rispetto agli altri due approcci quando si tratta di elenchi senza condividere elementi.
Nella maggior parte dei casi, l'utilizzo del isdisjoint()
metodo è l'approccio migliore in quanto l'esecuzione del generatore richiederà molto più tempo per l'esecuzione, poiché è molto inefficiente quando non sono condivisi elementi.
len(...) > 0
perchébool(set([]))
produce Falso. E, naturalmente, se hai tenuto le tue liste come set per cominciare, risparmierai il sovraccarico di creazione del set.