Resposta curta : use not set(a).isdisjoint(b)
, geralmente é o mais rápido.
Existem quatro maneiras comuns de testar se há duas listas a
e b
compartilhar itens. A primeira opção é converter ambos em conjuntos e verificar sua interseção, como tal:
bool(set(a) & set(b))
Como os conjuntos são armazenados usando uma tabela de hash no Python, é possível pesquisá-losO(1)
(consulte aqui para obter mais informações sobre a complexidade dos operadores no Python). Teoricamente, isso é, O(n+m)
em média, para n
e m
objetos nas listas a
e b
. Mas 1) ele deve primeiro criar conjuntos das listas, o que pode levar um tempo não negligenciável, e 2) supõe que as colisões de hash sejam escassas entre seus dados.
A segunda maneira de fazer isso é usar uma expressão de gerador executando iteração nas listas, como:
any(i in a for i in b)
Isso permite pesquisar no local, para que nenhuma nova memória seja alocada para variáveis intermediárias. Também se destaca na primeira descoberta. Mas o in
operador está sempre O(n)
nas listas (veja aqui ).
Outra opção proposta é um híbrido para iterar através de uma das listas, converter a outra em um conjunto e testar a associação nesse conjunto, da seguinte forma:
a = set(a); any(i in a for i in b)
Uma quarta abordagem é aproveitar o isdisjoint()
método dos conjuntos (congelados) (veja aqui ), por exemplo:
not set(a).isdisjoint(b)
Se os elementos pesquisados estiverem perto do início de uma matriz (por exemplo, ela é classificada), a expressão do gerador é favorecida, pois o método de interseção de conjuntos precisa alocar nova memória para as variáveis intermediárias:
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
Aqui está um gráfico do tempo de execução para este exemplo em função do tamanho da lista:
Observe que os dois eixos são logarítmicos. Isso representa o melhor caso para a expressão do gerador. Como pode ser visto, o isdisjoint()
método é melhor para tamanhos de lista muito pequenos, enquanto a expressão do gerador é melhor para tamanhos de lista maiores.
Por outro lado, como a pesquisa começa com o início da expressão híbrida e geradora, se o elemento compartilhado estiver sistematicamente no final da matriz (ou as duas listas não compartilharem nenhum valor), as abordagens de interseção separada e definida serão então muito mais rápido que a expressão do gerador e a abordagem híbrida.
>>> 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 notar que a expressão do gerador é muito mais lenta para tamanhos de lista maiores. Isso é apenas para 1000 repetições, em vez de 100000 para a figura anterior. Essa configuração também se aproxima bem quando quando nenhum elemento é compartilhado e é o melhor caso para as abordagens de interseção separada e definida.
Aqui estão duas análises usando números aleatórios (em vez de manipular a configuração para favorecer uma técnica ou outra):
Grande chance de compartilhamento: os elementos são retirados aleatoriamente [1, 2*len(a)]
. Baixa chance de compartilhamento: os elementos são retirados aleatoriamente [1, 1000*len(a)]
.
Até agora, essa análise supunha que as duas listas eram do mesmo tamanho. No caso de duas listas de tamanhos diferentes, por exemplo, a
é muito menor, isdisjoint()
é sempre mais rápido:
Verifique se a a
lista é menor, caso contrário, o desempenho diminui. Nesta experiência, o a
tamanho da lista foi definido como constante 5
.
Em suma:
- Se as listas são muito pequenas (<10 elementos),
not set(a).isdisjoint(b)
é sempre a mais rápida.
- Se os elementos nas listas forem classificados ou tiverem uma estrutura regular da qual você possa tirar proveito, a expressão do gerador
any(i in a for i in b)
será a mais rápida em tamanhos de lista grandes;
- Teste a interseção definida com
not set(a).isdisjoint(b)
, que é sempre mais rápida que bool(set(a) & set(b))
.
- O híbrido "iterar através da lista, testar no set"
a = set(a); any(i in a for i in b)
é geralmente mais lento que outros métodos.
- A expressão do gerador e o híbrido são muito mais lentos do que as outras duas abordagens quando se trata de listas sem compartilhar elementos.
Na maioria dos casos, o uso do isdisjoint()
método é a melhor abordagem, pois a expressão do gerador levará muito mais tempo para ser executada, pois é muito ineficiente quando nenhum elemento é compartilhado.
len(...) > 0
porquebool(set([]))
produz False. E, é claro, se você mantivesse suas listas como conjuntos, você economizaria sobrecarga na criação de conjuntos.