Respuesta corta : uso not set(a).isdisjoint(b)
, generalmente es el más rápido.
Hay cuatro formas comunes de probar si dos listas a
y b
compartir algún elemento. La primera opción es convertir ambos a conjuntos y verificar su intersección, como tal:
bool(set(a) & set(b))
Debido a que los conjuntos se almacenan utilizando una tabla hash en Python, buscarlos esO(1)
(consulte aquí para obtener más información sobre la complejidad de los operadores en Python). Teóricamente, esto es O(n+m)
en promedio para n
y m
objetos en listas a
y b
. Pero 1) primero debe crear conjuntos de las listas, lo que puede llevar una cantidad de tiempo no despreciable, y 2) supone que las colisiones de hash son escasas entre sus datos.
La segunda forma de hacerlo es usar una expresión generadora que realiza iteraciones en las listas, como:
any(i in a for i in b)
Esto permite buscar en el lugar, por lo que no se asigna memoria nueva para variables intermedias. También se rescata en el primer hallazgo. Pero el in
operador siempre está O(n)
en las listas (ver aquí ).
Otra opción propuesta es un híbrido para iterar a través de una de la lista, convertir la otra en un conjunto y probar la membresía en este conjunto, así:
a = set(a); any(i in a for i in b)
Un cuarto enfoque es aprovechar el isdisjoint()
método de los conjuntos (congelados) (ver aquí ), por ejemplo:
not set(a).isdisjoint(b)
Si los elementos que busca están cerca del comienzo de una matriz (por ejemplo, está ordenada), se favorece la expresión del generador, ya que el método de intersección de conjuntos debe asignar nueva memoria para las variables intermedias:
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
Aquí hay un gráfico del tiempo de ejecución para este ejemplo en función del tamaño de la lista:
Tenga en cuenta que ambos ejes son logarítmicos. Esto representa el mejor caso para la expresión del generador. Como se puede ver, el isdisjoint()
método es mejor para tamaños de lista muy pequeños, mientras que la expresión del generador es mejor para tamaños de lista más grandes.
Por otro lado, como la búsqueda comienza con el comienzo de la expresión híbrida y generadora, si el elemento compartido está sistemáticamente al final de la matriz (o ambas listas no comparten ningún valor), los enfoques de intersección disjuntos y establecidos son entonces mucho más rápido que la expresión del generador y el enfoque híbrido.
>>> 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
Es interesante observar que la expresión del generador es mucho más lenta para tamaños de lista más grandes. Esto es solo para 1000 repeticiones, en lugar de las 100000 para la figura anterior. Esta configuración también se aproxima bien cuando no se comparten elementos, y es el mejor caso para los enfoques de intersección disjuntos y establecidos.
Aquí hay dos análisis utilizando números aleatorios (en lugar de manipular la configuración para favorecer una técnica u otra):
Alta probabilidad de compartir: los elementos se toman aleatoriamente [1, 2*len(a)]
. Baja posibilidad de compartir: los elementos se toman aleatoriamente [1, 1000*len(a)]
.
Hasta ahora, este análisis suponía que ambas listas son del mismo tamaño. En el caso de dos listas de diferentes tamaños, por ejemplo, a
es mucho más pequeño, isdisjoint()
siempre es más rápido:
Asegúrese de que la a
lista sea más pequeña, de lo contrario el rendimiento disminuye. En este experimento, el a
tamaño de la lista se estableció constante en 5
.
En resumen:
- Si las listas son muy pequeñas (<10 elementos),
not set(a).isdisjoint(b)
siempre es la más rápida.
- Si los elementos en las listas están ordenados o tienen una estructura regular que puede aprovechar, la expresión del generador
any(i in a for i in b)
es la más rápida en tamaños de lista grandes;
- Pruebe la intersección establecida con
not set(a).isdisjoint(b)
, que siempre es más rápida que bool(set(a) & set(b))
.
- El híbrido "iterar a través de la lista, probar en conjunto"
a = set(a); any(i in a for i in b)
es generalmente más lento que otros métodos.
- La expresión del generador y el híbrido son mucho más lentos que los otros dos enfoques cuando se trata de listas sin compartir elementos.
En la mayoría de los casos, usar el isdisjoint()
método es el mejor enfoque, ya que la expresión del generador tardará mucho más en ejecutarse, ya que es muy ineficiente cuando no se comparten elementos.
len(...) > 0
ya quebool(set([]))
producen False. Y, por supuesto, si mantuviste tus listas como conjuntos para empezar, guardarías la sobrecarga de creación de conjuntos.