Eliminar todos los elementos que ocurren en una lista de otra

367

Digamos que tengo dos listas, l1y l2. Quiero realizar l1 - l2, que devuelve todos los elementos de l1no en l2.

Puedo pensar en un enfoque de bucle ingenuo para hacer esto, pero eso será realmente ineficiente. ¿Cuál es una forma pitónica y eficiente de hacer esto?

Como ejemplo, si tengo l1 = [1,2,6,8] and l2 = [2,3,5,8], l1 - l2debería volver[1,6]

fandom
fuente
12
Solo un consejo: PEP8 establece que "L" en minúsculas no debe usarse porque se parece demasiado a un 1.
spelchekr
2
Estoy de acuerdo. Leí toda esta pregunta y las respuestas preguntándome por qué la gente seguía usando once y doce. Fue solo cuando leí el comentario de @spelchekr que tuvo sentido.
robline
@JimG. El marco de datos y la lista no son lo mismo.
reducción de la actividad el

Respuestas:

492

Python tiene una función de lenguaje llamada List Comprehensions que se adapta perfectamente para hacer que este tipo de cosas sea extremadamente fácil. La siguiente declaración hace exactamente lo que desea y almacena el resultado en l3:

l3 = [x for x in l1 if x not in l2]

l3contendrá [1, 6].

Rosquilla
fuente
8
Muy pitónico; ¡Me gusta! ¿Qué tan eficiente es?
fandom
2
Creo que es bastante eficiente, y tiene el beneficio de ser extremadamente legible y claro en cuanto a lo que estás tratando de lograr. Me encontré con una publicación de blog que podría encontrar interesante relacionada con la eficiencia: blog.cdleary.com/2010/04/efficiency-of-list-comprehensions
Donut
66
@fandom: la comprensión de la lista en sí es bastante eficiente (aunque la comprensión del generador podría ser más eficiente al no duplicar elementos en la memoria), pero el inoperador no es tan eficiente en una lista. inen una lista está O (n), mientras que inen un conjunto está O (1). Sin embargo, hasta que llegue a miles de elementos o más, es poco probable que note la diferencia.
Daniel Pryden
1
l3 = [x for x in l1 if x not in set(l2)]? Estoy seguro de set(l2)que se llamaría más de una vez.
Danosaure
55
También podría simplemente configurar l2s = set(l2)y luego decir l3 = [x for x in l1 if x not in l2s]. Ligeramente más fácil.
spelchekr
149

Una forma es usar conjuntos:

>>> set([1,2,6,8]) - set([2,3,5,8])
set([1, 6])
Arkku
fuente
58
Esto también eliminará los duplicados l1, lo que puede ser un efecto secundario no deseado.
poco
37
..y perder el orden de los elementos (si el orden es importante).
Danosaure
3
Sólo quiero añadir que lo cronometré este vs la respuesta aceptada y fue con más prestaciones en un factor de aproximadamente 3: timeit.timeit('a = [1,2,3,4]; b = [1,3]; c = [i for i in a if a not in b]', number=100000) -> 0.12061533199999985 timeit.timeit('a = {1,2,3,4}; b = {1,3}; c = a - b', number=100000) -> 0.04106225999998969. Entonces, si el rendimiento es un factor significativo, esta respuesta puede ser más apropiada (y también si no le importan los duplicados o el orden)
wfgeo
38

Como alternativa, también puede usar filtercon la expresión lambda para obtener el resultado deseado. Por ejemplo:

>>> l1 = [1,2,6,8]
>>> l2 = set([2,3,5,8])

#     v  `filter` returns the a iterator object. Here I'm type-casting 
#     v  it to `list` in order to display the resultant value
>>> list(filter(lambda x: x not in l2, l1))
[1, 6]

Comparación de rendimiento

Aquí estoy comparando el rendimiento de todas las respuestas mencionadas aquí. Como se esperaba, la set operación basada en Arkku es más rápida.

  • Diferencia establecida de Arkku - Primero (0.124 usec por ciclo)

    mquadri$ python -m timeit -s "l1 = set([1,2,6,8]); l2 = set([2,3,5,8]);" "l1 - l2"
    10000000 loops, best of 3: 0.124 usec per loop
    
  • Lista Comprensión de Daniel Pryden con setbúsqueda - Segundo (0.302 usec por ciclo)

    mquadri$ python -m timeit -s "l1 = [1,2,6,8]; l2 = set([2,3,5,8]);" "[x for x in l1 if x not in l2]"
    1000000 loops, best of 3: 0.302 usec per loop
    
  • Lista de donas Comprensión en lista simple - Tercero (0.552 usec por ciclo)

    mquadri$ python -m timeit -s "l1 = [1,2,6,8]; l2 = [2,3,5,8];" "[x for x in l1 if x not in l2]"
    1000000 loops, best of 3: 0.552 usec per loop
    
  • Moinuddin Quadri's usandofilter - Cuarto (0.972 usec por ciclo)

    mquadri$ python -m timeit -s "l1 = [1,2,6,8]; l2 = set([2,3,5,8]);" "filter(lambda x: x not in l2, l1)"
    1000000 loops, best of 3: 0.972 usec per loop
    
  • Akshay Hazari está usando la combinación de reduce+filter - Quinto (3.97 usec por ciclo)

    mquadri$ python -m timeit "l1 = [1,2,6,8]; l2 = [2,3,5,8];" "reduce(lambda x,y : filter(lambda z: z!=y,x) ,l1,l2)"
    100000 loops, best of 3: 3.97 usec per loop
    

PD: set no mantiene el orden y elimina los elementos duplicados de la lista. Por lo tanto, no use la diferencia establecida si necesita alguno de estos.

Moinuddin Quadri
fuente
32

Ampliando la respuesta de Donut y las otras respuestas aquí, puede obtener mejores resultados utilizando una comprensión de generador en lugar de una comprensión de lista, y utilizando una setestructura de datos (ya que el inoperador es O (n) en una lista pero O (1) en un set).

Así que aquí hay una función que funcionaría para usted:

def filter_list(full_list, excludes):
    s = set(excludes)
    return (x for x in full_list if x not in s)

El resultado será un iterable que va a buscar perezosamente la lista filtrada. Si necesita un objeto de lista real (por ejemplo, si necesita hacer un a len()en el resultado), puede crear fácilmente una lista como esta:

filtered_list = list(filter_list(full_list, excludes))
Daniel Pryden
fuente
29

Use el tipo de conjunto Python. Eso sería lo más pitónico. :)

Además, como es nativo, también debería ser el método más optimizado.

Ver:

http://docs.python.org/library/stdtypes.html#set

http://docs.python.org/library/sets.htm (para python anterior)

# Using Python 2.7 set literal format.
# Otherwise, use: l1 = set([1,2,6,8])
#
l1 = {1,2,6,8}
l2 = {2,3,5,8}
l3 = l1 - l2
nonot1
fuente
55
Cuando se utilizan conjuntos, debe tenerse en cuenta que la salida de está ordenada, es decir, {1,3,2} se convierte en {1,2,3} y {"A", "C", "B"} se convierte en {"A", "B", "C"} y es posible que no quieras tener eso.
Pablo Reyes
2
Este método no funcionará si la lista l1incluye elementos repetidos.
jdhao
10

use Set Comprehensions {x para x en l2} o set (l2) para configurar, luego use List Comprehensions para obtener la lista

l2set = set(l2)
l3 = [x for x in l1 if x not in l2set]

código de prueba de referencia:

import time

l1 = list(range(1000*10 * 3))
l2 = list(range(1000*10 * 2))

l2set = {x for x in l2}

tic = time.time()
l3 = [x for x in l1 if x not in l2set]
toc = time.time()
diffset = toc-tic
print(diffset)

tic = time.time()
l3 = [x for x in l1 if x not in l2]
toc = time.time()
difflist = toc-tic
print(difflist)

print("speedup %fx"%(difflist/diffset))

resultado de la prueba de referencia:

0.0015058517456054688
3.968189239501953
speedup 2635.179227x    
lbsweek
fuente
1
l2set = set( l2 )en lugar del2set = { x for x in l2 }
cz
1
Buena alma! Pero debe tenerse en cuenta, que solo funciona con objetos hashaable.
Eerik Sven Puudista
7

Solución alternativa

reduce(lambda x,y : filter(lambda z: z!=y,x) ,[2,3,5,8],[1,2,6,8])
Akshay Hazari
fuente
2
¿Hay alguna ventaja en usar este método? Parece que es más complejo y más difícil de leer sin mucho beneficio.
skrrgwasme
Eso puede parecer complejo. Reducir es muy flexible y puede usarse para muchos propósitos. Se le conoce como pliegue. reducir es en realidad foldl. Suponga que desea agregar cosas más complejas, entonces será posible en esta función, pero la comprensión de la lista, que es la mejor respuesta seleccionada, solo le dará una salida del mismo tipo, es decir, una lista y probablemente de la misma longitud, con pliegues que podría cambiar el tipo de salida también. en.wikipedia.org/wiki/Fold_%28higher-order_function%29 . Esta solución es n * mo menos complejidad. Sin embargo, otros pueden o no ser mejores.
Akshay Hazari
1
reducir (función, lista, acumulador inicial (que puede ser de cualquier tipo))
Akshay Hazari