Quiero verificar si alguno de los elementos de una lista está presente en otra lista. Puedo hacerlo simplemente con el código a continuación, pero sospecho que podría haber una función de biblioteca para hacer esto. Si no, ¿existe un método más pitónico para lograr el mismo resultado?
In [78]: a = [1, 2, 3, 4, 5]
In [79]: b = [8, 7, 6]
In [80]: c = [8, 7, 6, 5]
In [81]: def lists_overlap(a, b):
....: for i in a:
....: if i in b:
....: return True
....: return False
....:
In [82]: lists_overlap(a, b)
Out[82]: False
In [83]: lists_overlap(a, c)
Out[83]: True
In [84]: def lists_overlap2(a, b):
....: return len(set(a).intersection(set(b))) > 0
....:
list
python
intersection
fmark
fuente
fuente
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.True
de1
yFalse
de0
.not set([1]).isdisjoint([True])
consigueTrue
, lo mismo con otras soluciones.Respuestas:
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
yb
compartir algún elemento. La primera opción es convertir ambos a conjuntos y verificar su intersección, como tal:Debido a que los conjuntos se almacenan utilizando una tabla hash en Python, buscarlos es
O(1)
(consulte aquí para obtener más información sobre la complejidad de los operadores en Python). Teóricamente, esto esO(n+m)
en promedio paran
ym
objetos en listasa
yb
. 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:
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í:
Un cuarto enfoque es aprovechar el
isdisjoint()
método de los conjuntos (congelados) (ver aquí ), por ejemplo: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:
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.
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, ela
tamaño de la lista se estableció constante en5
.En resumen:
not set(a).isdisjoint(b)
siempre es la más rápida.any(i in a for i in b)
es la más rápida en tamaños de lista grandes;not set(a).isdisjoint(b)
, que siempre es más rápida quebool(set(a) & set(b))
.a = set(a); any(i in a for i in b)
es generalmente más lento que otros métodos.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.fuente
any
sale en el primer valor no falso. Usando una lista donde el único valor coincidente está al final, obtenemos esto: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
... y es solo con 1000 iteraciones.not set(a).isdisjoint(b)
para probar si dos listas comparten un miembro.set(a).isdisjoint(b)
devuelveTrue
si las dos listas no comparten un miembro. La respuesta debe ser editada?Nota: lo anterior supone que desea un booleano como respuesta. Si todo lo que necesita es una expresión para usar en una
if
declaración, simplemente useif set(a) & set(b):
fuente
O(n + m)
. Supongo que los conjuntos se implementan utilizando tablas hash y, por lo tanto, elin
operador puede trabajar aO(1)
tiempo (excepto en casos degenerados). ¿Es esto correcto? Si es así, dado que las tablas hash tienen un rendimiento de búsqueda en el peor de los casosO(n)
, ¿significa esto que en el peor de los casos tendráO(n * m)
rendimiento?O(n)
rendimiento de búsqueda;), consulte pastebin.com/Kn3kAW7u Solo para ver lafs.Esto es asintóticamente óptimo (peor caso O (n + m)), y podría ser mejor que el enfoque de intersección debido al
any
cortocircuito.P.ej:
devolverá True tan pronto como llegue
3 in sb
EDITAR: Otra variación (con agradecimiento a Dave Kirby):
Esto se basa en
imap
el iterador, que se implementa en C, en lugar de una comprensión del generador. También se usasb.__contains__
como la función de mapeo. No sé cuánta diferencia de rendimiento hace esto. Seguirá en cortocircuito.fuente
any(itertools.imap(sb.__contains__, a))
que debería ser más rápido, ya que evita usar una función lambda.También podría usar
any
con la comprensión de la lista:fuente
[]
) y se ejecutaría más rápido y usaría menos memoria, pero el tiempo aún sería O (n * m).En python 2.6 o posterior puedes hacer:
fuente
Puede usar cualquier expresión incorporada de función / generador de wa:
Como John y Lie han señalado, esto da resultados incorrectos cuando por cada i compartido por las dos listas bool (i) == False. Debería ser:
fuente
bool(x)
es False. En el ejemplo de Lie Ryan, x es 0. La única solución es laany(True for i in a if i in b)
que está mejor escrita como la que ya se vioany(i in b for i in a)
.x
en la intersección son tales quebool(x)
esFalse
.Esta pregunta es bastante antigua, pero me di cuenta de que mientras la gente discutía conjuntos versus listas, nadie pensaba en usarlos juntos. Siguiendo el ejemplo de Soravux,
El peor caso para las listas:
Y el mejor caso para las listas:
Entonces, incluso más rápido que iterar a través de dos listas es iterar a través de una lista para ver si está en un conjunto, lo que tiene sentido ya que verificar si un número está en un conjunto toma tiempo constante, mientras que la verificación iterando a través de una lista toma tiempo proporcional a la longitud de la lista.
Por lo tanto, mi conclusión es que recorre una lista y verifica si está en un conjunto .
fuente
isdisjoint()
método en un conjunto (congelado) como lo indica @Toughy es aún mejor:timeit('any(i in a for i in b)', setup="a= set(range(10000)); b=[x+9999 for x in range(10000)]", number=100000)
=> 0.00913715362548828Si no le importa cuál sea el elemento superpuesto, simplemente puede verificar la
len
lista combinada frente a las listas combinadas como un conjunto. Si hay elementos superpuestos, el conjunto será más corto:len(set(a+b+c))==len(a+b+c)
devuelve True, si no hay superposición.fuente
Lanzaré otro con un estilo de programación funcional:
Explicación:
devuelve una lista de booleanos donde
b
se encuentran elementos dea
. Esa lista se pasa aany
, que simplemente devuelveTrue
si hay algún elementoTrue
.fuente