Python encuentra elementos en una lista que no están en la otra [duplicado]

135

Necesito comparar dos listas para crear una nueva lista de elementos específicos que se encuentran en una lista pero no en la otra. Por ejemplo:

main_list=[]
list_1=["a", "b", "c", "d", "e"]
list_2=["a", "f", "c", "m"] 

Quiero recorrer list_1 y agregar a main_list todos los elementos de list_2 que no se encuentran en list_1.

El resultado debería ser:

main_list=["f", "m"]

¿Cómo puedo hacerlo con Python?

CosimoCD
fuente
2
¿Está buscando elementos list_2que no aparecen en ninguna parte list_1o elementos list_2que no están presentes en el mismo índice list_1?
Patrick Haugh,

Respuestas:

96

TL; DR:
SOLUCIÓN (1)

import numpy as np
main_list = np.setdiff1d(list_2,list_1)
# yields the elements in `list_2` that are NOT in `list_1`

SOLUCIÓN (2) Quiere una lista ordenada

def setdiff_sorted(array1,array2,assume_unique=False):
    ans = np.setdiff1d(array1,array2,assume_unique).tolist()
    if assume_unique:
        return sorted(ans)
    return ans
main_list = setdiff_sorted(list_2,list_1)




EXPLICACIONES:
(1) Se puede utilizar NumPy de setdiff1d( array1, array2, assume_unique= False).

assume_uniquepregunta al usuario si las matrices YA SON ÚNICAS.
Si False, entonces los elementos únicos se determinan primero.
Si True, la función asumirá que los elementos ya son únicos Y la función omitirá la determinación de los elementos únicos.

Esto produce los valores únicos en array1que no están en array2. assume_uniquees Falsepor defecto

Si le preocupan los elementos únicos (basados ​​en la respuesta de Chinny84 ), simplemente use (where assume_unique=False=> el valor predeterminado):

import numpy as np
list_1 = ["a", "b", "c", "d", "e"]
list_2 = ["a", "f", "c", "m"] 
main_list = np.setdiff1d(list_2,list_1)
# yields the elements in `list_2` that are NOT in `list_1`


(2) Para aquellos que desean ordenar las respuestas, he creado una función personalizada:

import numpy as np
def setdiff_sorted(array1,array2,assume_unique=False):
    ans = np.setdiff1d(array1,array2,assume_unique).tolist()
    if assume_unique:
        return sorted(ans)
    return ans

Para obtener la respuesta, ejecute:

main_list = setdiff_sorted(list_2,list_1)

NOTAS LATERALES:
(a) La solución 2 (función personalizada setdiff_sorted) devuelve una lista (en comparación con una matriz en la solución 1).

(b) Si no está seguro de si los elementos son únicos, simplemente use la configuración predeterminada de NumPy's setdiff1den las soluciones A y B. ¿Qué puede ser un ejemplo de complicación? Ver nota (c).

(c) Las cosas serán diferentes si cualquiera de las dos listas no es única.
Por ejemplo list_2no es único: list2 = ["a", "f", "c", "m", "m"]. Mantener list1como está: list_1 = ["a", "b", "c", "d", "e"]
establecer el valor predeterminado de los assume_uniquerendimientos ["f", "m"](en ambas soluciones). SIN EMBARGO, si establece assume_unique=True, ambas soluciones dan ["f", "m", "m"]. ¿Por qué? Esto se debe a que el usuario asumió que los elementos son únicos). Por lo tanto, es mejor mantenerassume_uniquea su valor predeterminado. Tenga en cuenta que ambas respuestas están ordenadas.

jcoderepo
fuente
Si sus listas ya están ordenadas, esto también devolverá una lista ordenada. La solución nativa de convertir a conjuntos y luego obtener la diferencia (las soluciones que se muestran a continuación) devuelve una lista desordenada que puede dificultar el examen visual de los resultados.
Doubledown
1
¡Hola, @Doubledown! Su preocupación ha sido abordada en la publicación editada. ¡Espero que esto ayude!
jcoderepo
182

Puedes usar conjuntos:

main_list = list(set(list_2) - set(list_1))

Salida:

>>> list_1=["a", "b", "c", "d", "e"]
>>> list_2=["a", "f", "c", "m"]
>>> set(list_2) - set(list_1)
set(['m', 'f'])
>>> list(set(list_2) - set(list_1))
['m', 'f']

Según el comentario de @JonClements, aquí hay una versión más ordenada:

>>> list_1=["a", "b", "c", "d", "e"]
>>> list_2=["a", "f", "c", "m"]
>>> list(set(list_2).difference(list_1))
['m', 'f']
nrlakin
fuente
2
Esto es bueno si solo nos preocupamos por los uniqueelementos, pero ¿qué pasa si tenemos múltiples, m'spor ejemplo, esto no lo recogería?
Chinny84
Es verdad. Asumí que el cartel estaba buscando elementos únicos. Supongo que depende de lo que él quiere decir con "específico".
nrlakin
De hecho, ps. No rechacé su respuesta, especialmente para una pregunta original poco clara.
Chinny84
13
Podría escribir esto ya list(set(list_2).difference(list_1))que evita la setconversión explícita ...
Jon Clements
¡Sin preocupaciones! Gracias @leaf por la ayuda de formateo.
nrlakin
60

No estoy seguro de por qué las explicaciones anteriores son tan complicadas cuando tiene métodos nativos disponibles:

main_list = list(set(list_2)-set(list_1))
A.Kot
fuente
66
Preservar el orden podría ser la razón
Keith
57

Use una lista de comprensión como esta:

main_list = [item for item in list_2 if item not in list_1]

Salida:

>>> list_1 = ["a", "b", "c", "d", "e"]
>>> list_2 = ["a", "f", "c", "m"] 
>>> 
>>> main_list = [item for item in list_2 if item not in list_1]
>>> main_list
['f', 'm']

Editar:

Como se menciona en los comentarios a continuación, con grandes listas, lo anterior no es la solución ideal. Cuando ese es el caso, una mejor opción sería convertir list_1a una setprimera:

set_1 = set(list_1)  # this reduces the lookup time from O(n) to O(1)
main_list = [item for item in list_2 if item not in set_1]
ettanany
fuente
3
Nota: Para los más grandes list_1, querrá realizar una conversión previa a set/ frozenset, por ejemplo set_1 = frozenset(list_1), luego main_list = [item for item in list_2 if item not in set_1], reduciendo el tiempo de verificación de O(n)por elemento a (aproximadamente) O(1).
ShadowRanger
@ettanany Tenga cuidado si prueba la solución tal como la publicó ettanany. Probé la solución de Ettanany como está y de hecho es súper lenta para una lista más grande. ¿Puedes actualizar la respuesta para incorporar la sugerencia de shadowranger?
Doubledown
¿Sería posible obtener el índice, en lugar de la cadena?
JareBear
@JareBear Puedes usar enumerate()para eso:[index for (index, item) in enumerate(list_2) if item not in list_1]
ettanany
@ Ettanany muchas gracias !! Implementaré lo antes posible, lo había hecho. Pero su código se ve mucho más limpio.
JareBear
5

Si desea una solución de una línea (ignorando las importaciones) que solo requiere O(max(n, m))trabajo para entradas de longitud ny m, no O(n * m)trabajo, puede hacerlo con el itertoolsmódulo :

from itertools import filterfalse

main_list = list(filterfalse(set(list_1).__contains__, list_2))

Esto aprovecha las funciones funcionales que toman una función de devolución de llamada en la construcción, lo que le permite crear la devolución de llamada una vez y reutilizarla para cada elemento sin necesidad de almacenarla en algún lugar (porque la filterfalsealmacena internamente); las comprensiones de listas y las expresiones generadoras pueden hacer esto, pero es feo. †

Eso obtiene los mismos resultados en una sola línea que:

main_list = [x for x in list_2 if x not in list_1]

con la velocidad de:

set_1 = set(list_1)
main_list = [x for x in list_2 if x not in set_1]

Por supuesto, si las comparaciones están destinadas a ser posicionales, entonces:

list_1 = [1, 2, 3]
list_2 = [2, 3, 4]

debe producir:

main_list = [2, 3, 4]

(debido a que el valor en list_2tiene una coincidencia en el mismo índice en list_1), definitivamente debe ir con la respuesta de Patrick , que no implica una lists o sets temporal (incluso si sets es más o menos O(1), tienen un factor "constante" más alto por verificación que las simples verificaciones de igualdad ) e implica O(min(n, m))trabajo, menos que cualquier otra respuesta, y si su problema es sensible a la posición, es la única solución correcta cuando los elementos coincidentes aparecen en desplazamientos no coincidentes.

†: La forma de hacer lo mismo con una comprensión de la lista como una línea sería abusar del bucle anidado para crear y almacenar valores en el bucle "más externo", por ejemplo:

main_list = [x for set_1 in (set(list_1),) for x in list_2 if x not in set_1]

lo que también proporciona un beneficio de rendimiento menor en Python 3 (porque ahora set_1tiene un alcance local en el código de comprensión, en lugar de buscarlo desde el alcance anidado para cada verificación; en Python 2 eso no importa, porque Python 2 no usa cierres para enumere las comprensiones; operan en el mismo ámbito en el que se usan).

ShadowRanger
fuente
4
main_list=[]
list_1=["a", "b", "c", "d", "e"]
list_2=["a", "f", "c", "m"]

for i in list_2:
    if i not in list_1:
        main_list.append(i)

print(main_list)

salida:

['f', 'm']
Inconnu
fuente
Al igual que la solución basada en la comprensión de la lista equivalente , esta será lenta si list_1es grande y list_2no tiene un tamaño trivial, porque implica len(list_2) O(n)escaneos de list_1, haciéndolo O(n * m)(dónde ny mson las longitudes de list_2y list_1respectivamente). Si convierte list_1a set/ frozensetpor adelantado, las comprobaciones de contención se pueden realizar O(1), haciendo que el trabajo total sea O(n)de la longitud de list_2(técnicamente O(max(n, m)), ya que O(m)trabaja para hacer el set).
ShadowRanger
1

Yo haría ziplas listas juntas para compararlas elemento por elemento.

main_list = [b for a, b in zip(list1, list2) if a!= b]
Patrick Haugh
fuente
Si el OP quiere comparar elemento por elemento (no está claro, el ejemplo podría ir en cualquier dirección), esto es mucho más eficiente que las otras respuestas, ya que es un solo pase barato sobre ambos lists con un solo nuevo listconstruido, sin temporarios adicionales , sin costosos controles de contención, etc.
ShadowRanger
1
@ShadowRanger esto solo funcionaría para la diferencia entre elementos, que es un punto clave
prefecto ford
@fordprefect: Sí. Mi propia respuesta cubre las diferencias independientes de la posición.
ShadowRanger
1

Usé dos métodos y encontré un método útil sobre otro. Aquí está mi respuesta:

Mis datos de entrada:

crkmod_mpp = ['M13','M18','M19','M24']
testmod_mpp = ['M13','M14','M15','M16','M17','M18','M19','M20','M21','M22','M23','M24']

Método 1: np.setdiff1dme gusta este enfoque sobre otro porque conserva la posición

test= list(np.setdiff1d(testmod_mpp,crkmod_mpp))
print(test)
['M15', 'M16', 'M22', 'M23', 'M20', 'M14', 'M17', 'M21']

Método 2: aunque da la misma respuesta que en el Método 1 pero perturba el orden

test = list(set(testmod_mpp).difference(set(crkmod_mpp)))
print(test)
['POA23', 'POA15', 'POA17', 'POA16', 'POA22', 'POA18', 'POA24', 'POA21']

Method1 np.setdiff1dcumple mis requisitos perfectamente. Esta respuesta para información.

Msquare
fuente
0

Si se debe tener en cuenta el número de ocurrencias, probablemente necesite usar algo como collections.Counter:

list_1=["a", "b", "c", "d", "e"]
list_2=["a", "f", "c", "m"] 
from collections import Counter
cnt1 = Counter(list_1)
cnt2 = Counter(list_2)
final = [key for key, counts in cnt2.items() if cnt1.get(key, 0) != counts]

>>> final
['f', 'm']

Como se prometió, esto también puede manejar diferentes números de ocurrencias como "diferencia":

list_1=["a", "b", "c", "d", "e", 'a']
cnt1 = Counter(list_1)
cnt2 = Counter(list_2)
final = [key for key, counts in cnt2.items() if cnt1.get(key, 0) != counts]

>>> final
['a', 'f', 'm']
MSeifert
fuente
-1

De ser1 elimine los elementos presentes en ser2.

Entrada

ser1 = pd.Series ([1, 2, 3, 4, 5]) ser2 = pd.Series ([4, 5, 6, 7, 8])

Solución

ser1 [~ ser1.isin (ser2)]

adnan
fuente
Bienvenido a Stack Overflow. Esta pregunta tiene otras ocho respuestas, una de las cuales ha sido aceptada por el póster original. Describa cómo mejora su respuesta sobre lo que ya se ha presentado.
chb