Respuesta corta: not set(a).isdisjoint(b)
utilizar, por lo general es el más rápido.
Existen cuatro formas comunes de probar si dos listas a
y b
comparten algún elemento. La primera opción es convertir tanto a los conjuntos y comprobar su intersección, como tal:
bool(set(a) & set(b))
Debido conjuntos se almacenan utilizando una tabla hash en Python, buscando ellos es O(1)
(ver here para obtener más información acerca de la complejidad de operadores en Python). Teóricamente, esto es O(n+m)
en promedio para los objetos n
y m
en las listas a
y b
. Pero 1) primero debe crear conjuntos de las listas, lo que puede tomar una cantidad de tiempo no despreciable, y 2) supone que las colisiones hash son escasas entre sus datos.
La segunda manera de hacerlo es usando una expresión generadora realizar iteración en las listas, como por ejemplo:
any(i in a for i in b)
Esto permite buscar en el lugar, así que no hay nueva se asigna memoria para las variables intermedias. También rescata en el primer hallazgo. Pero el operador es siempre in
O(n)
en las listas (ver here).
Otra opción propuesta es un iterate hybridto través de uno de la lista, convertir el otro en un conjunto y la prueba para formar parte de este conjunto, así:
a = set(a); any(i in a for i in b)
Un cuarto enfoque es aprovechar el método isdisjoint()
de los conjuntos (congelados) (véase here), por ejemplo:
not set(a).isdisjoint(b)
Si los elementos que realizan búsquedas están cerca del comienzo de una matriz (por ejemplo, se ordena), la expresión del generador se ve favorecida, como se los conjuntos me intersecan DTO tienen que 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í es un gráfico del tiempo de ejecución para este ejemplo en función del tamaño de la lista:
Tenga en cuenta que los dos ejes son logarítmicas. Esto representa el mejor caso para la expresión del generador. Como se puede ver, el método isdisjoint()
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 para 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), la combinación y el conjunto los enfoques de intersección son mucho más rápidos 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 notar que la expresión generadora es mucho más lento para grandes tamaños de la lista. Esto es solo para 1000 repeticiones, en lugar de 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 conjuntos.
Aquí hay dos análisis utilizando números aleatorios (en lugar de manipular la configuración para favorecer una técnica u otra):
alta posibilidad de compartir: los elementos se toman al azar de [1, 2*len(a)]
. Poca posibilidad de compartir: los elementos se toman aleatoriamente del [1, 1000*len(a)]
.
Hasta ahora, este análisis supone 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()
es siempre más rápido:
Asegúrese de que la lista a
es la más pequeña, de lo contrario el rendimiento disminuye. En este experimento, el tamaño de la lista a
se estableció constante en 5
.
En resumen:
- Si las listas son muy pequeñas (< 10 elementos),
not set(a).isdisjoint(b)
es siempre el más rápido.
- 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 los tamaños de lista grandes;
- Pruebe la intersección del conjunto con
not set(a).isdisjoint(b)
, que siempre es más rápido que bool(set(a) & set(b))
.
- El híbrido "iterar a través de la lista, prueba en el 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 elementos compartidos.
En la mayoría de los casos, utilizando el método isdisjoint()
es el mejor enfoque como la expresión generador se necesita mucho más tiempo para ejecutar, ya que es muy ineficiente cuando se comparten ningún elemento.
Las únicas optimizaciones que se me ocurren es descartar 'len (...)> 0' porque' bool (set ([])) 'produce False. Y, por supuesto, si mantuvieras tus listas como conjuntos para empezar, ahorrarías la sobrecarga de creación de conjuntos. – msw
Relevante: https://stackoverflow.com/a/44786707/1959808 –
Tenga en cuenta que no puede distinguir 'True' de' 1' y 'False' de' 0'. 'not set ([1]). isdisjoint ([True])' gets 'True', lo mismo con otras soluciones. – Dimali