Python, diferencia de la lista de cómputo


195

En Python, ¿cuál es la mejor manera de calcular la diferencia entre dos listas?

ejemplo

A = [1,2,3,4]
B = [2,5]

A - B = [1,3,4]
B - A = [5]

Respuestas:


206

Úselo setsi no le importa el orden o la repetición de los artículos. Use las comprensiones de la lista si lo hace:

>>> def diff(first, second):
        second = set(second)
        return [item for item in first if item not in second]

>>> diff(A, B)
[1, 3, 4]
>>> diff(B, A)
[5]
>>> 

31
Considere usar set(b)para asegurarse de que el algoritmo sea O (nlogn) en lugar de Theta (n ^ 2)
Neil G

8
@Pencilcheck: no si le importa ordenar o duplicar en A. Aplicar seta B es inofensivo, pero aplicarlo Ay utilizar el resultado en lugar del original Ano lo es.
Mark Reed

1
@NeilG ¿Considera el tiempo que consume para construir el set? En mi caso (ambas listas tienen aproximadamente 10 millones de cadenas), el tiempo para construir dos conjuntos y restarlos es considerablemente mayor que construir un conjunto e iterar sobre la lista.
dimril

@dimril si eso es lo que quieres hacer, tal vez deberías implementar algo más sofisticado. Podría, por ejemplo, ordenar ambas listas O (n log n + m log m) y luego iterar sobre la segunda lista, pero utilice la búsqueda binaria para encontrar los elementos en la primera lista. Saldría a operaciones O (n log n + m log m + m log n) (en lugar de operaciones O (n * m)), lo que no parece tan malo. Solo asegúrese de buscar vecinos para eliminar también duplicados en sus implementaciones de búsqueda binaria. Incluso puede haber un paquete que ya implemente esto, pero no lo comprobé.
jaaq

366

Si el orden no importa, simplemente puede calcular la diferencia establecida:

>>> set([1,2,3,4]) - set([2,5])
set([1, 4, 3])
>>> set([2,5]) - set([1,2,3,4])
set([5])

9
Esta es, con mucho, la mejor solución. El caso de prueba en listas con ~ 6000 cadenas cada una mostró que este método era casi 100 veces más rápido que las comprensiones de listas.
perrygeo 01 de

15
Depende de la aplicación: si la preservación del orden o la duplicación es importante, Roman Bodnarchuk puede tener un mejor enfoque. Para la velocidad y el comportamiento puro, parece mejor.
Bryan P

77
Si tiene varios elementos iguales en la lista, esta solución no funcionará.
karantan

Mucho mejor que la lista de comprensión.
Dawei

44
Esta solución parece tan obvia pero es incorrecta. Lo siento. Por supuesto, queremos decir que una lista puede tener elementos iguales repetidos. De lo contrario, preguntamos sobre la diferencia entre conjuntos, no sobre la diferencia de lista.
sergzach

67

Puedes hacer un

list(set(A)-set(B))

y

list(set(B)-set(A))

77
Pero si A = [1,1,1] y B = [0] entonces esto devuelve [1]
Mark Bell

1
@ Mark Bell: Eso es porque un conjunto es una lista distinta. (elimina duplicados)
nublado

1
@cloudy Entonces esto no responde la pregunta.
samm82

@ samm82 si A = [1,1,1] que el conjunto (A) es [1] porque el conjunto es una lista distinta y elimina duplicados. Por eso, si A = [1,1,1] y B = [0] devuelve [1].
nublado

29

Un trazador de líneas:

diff = lambda l1,l2: [x for x in l1 if x not in l2]
diff(A,B)
diff(B,A)

O:

diff = lambda l1,l2: filter(lambda x: x not in l2, l1)
diff(A,B)
diff(B,A)

14

Los ejemplos anteriores trivializaron el problema de calcular diferencias. Asumir que la ordenación o la desduplicación definitivamente hace que sea más fácil calcular la diferencia, pero si su comparación no puede permitirse esos supuestos, necesitará una implementación no trivial de un algoritmo diff. Ver difflib en la biblioteca estándar de python.

from difflib import SequenceMatcher 

squeeze=SequenceMatcher( None, A, B )

print "A - B = [%s]"%( reduce( lambda p,q: p+q, 
                               map( lambda t: squeeze.a[t[1]:t[2]], 
                                    filter(lambda x:x[0]!='equal', 
                                           squeeze.get_opcodes() ) ) ) )

A - B = [[1, 3, 4]]


1
obtienes +1 para difflib, que no había visto antes. sin embargo, no estoy de acuerdo con que las respuestas anteriores trivialicen el problema como se indicó .
rbp

Gracias por usar difflib. Estaba buscando una solución usando la biblioteca estándar. Sin embargo, esto no funciona en Python 3, ya que printha cambiado de un comando a una función reduce, filtery mapha sido declarado no pitónico. (Y creo que Guido puede estar en lo cierto , tampoco entiendo lo que reducehace.)
Post169

No es un gran cambio para que funcione para py3. He leído el debate sobre filtro, mapa, reducción y estoy de acuerdo con la opción de empujar reducir y alternar impl de filtro en functools. La naturaleza mixta funcional, OO y procesal de Python siempre ha sido, IMO, uno de sus puntos fuertes.
Kevin

14

Python 2.7.3 (predeterminado, 27 de febrero de 2014, 19:58:35) - IPython 1.1.0 - timeit: (github gist)

def diff(a, b):
  b = set(b)
  return [aa for aa in a if aa not in b]

def set_diff(a, b):
  return list(set(a) - set(b))

diff_lamb_hension = lambda l1,l2: [x for x in l1 if x not in l2]

diff_lamb_filter = lambda l1,l2: filter(lambda x: x not in l2, l1)

from difflib import SequenceMatcher
def squeezer(a, b):
  squeeze = SequenceMatcher(None, a, b)
  return reduce(lambda p,q: p+q, map(
    lambda t: squeeze.a[t[1]:t[2]],
      filter(lambda x:x[0]!='equal',
        squeeze.get_opcodes())))

Resultados:

# Small
a = range(10)
b = range(10/2)

timeit[diff(a, b)]
100000 loops, best of 3: 1.97 µs per loop

timeit[set_diff(a, b)]
100000 loops, best of 3: 2.71 µs per loop

timeit[diff_lamb_hension(a, b)]
100000 loops, best of 3: 2.1 µs per loop

timeit[diff_lamb_filter(a, b)]
100000 loops, best of 3: 3.58 µs per loop

timeit[squeezer(a, b)]
10000 loops, best of 3: 36 µs per loop

# Medium
a = range(10**4)
b = range(10**4/2)

timeit[diff(a, b)]
1000 loops, best of 3: 1.17 ms per loop

timeit[set_diff(a, b)]
1000 loops, best of 3: 1.27 ms per loop

timeit[diff_lamb_hension(a, b)]
1 loops, best of 3: 736 ms per loop

timeit[diff_lamb_filter(a, b)]
1 loops, best of 3: 732 ms per loop

timeit[squeezer(a, b)]
100 loops, best of 3: 12.8 ms per loop

# Big
a = xrange(10**7)
b = xrange(10**7/2)

timeit[diff(a, b)]
1 loops, best of 3: 1.74 s per loop

timeit[set_diff(a, b)]
1 loops, best of 3: 2.57 s per loop

timeit[diff_lamb_filter(a, b)]
# too long to wait for

timeit[diff_lamb_filter(a, b)]
# too long to wait for

timeit[diff_lamb_filter(a, b)]
# TypeError: sequence index must be integer, not 'slice'

@ roman-bodnarchuk la función de comprensión de listas def diff (a, b) parece ser más rápida.


9
A = [1,2,3,4]
B = [2,5]

#A - B
x = list(set(A) - set(B))
#B - A 
y = list(set(B) - set(A))

print x
print y 


5

En caso de que desee que la diferencia se profundice recursivamente en los elementos de su lista, he escrito un paquete para python: https://github.com/erasmose/deepdiff

Instalación

Instalar desde PyPi:

pip install deepdiff

Si eres Python3, también necesitas instalar:

pip install future six

Ejemplo de uso

>>> from deepdiff import DeepDiff
>>> from pprint import pprint
>>> from __future__ import print_function

El mismo objeto vuelve vacío

>>> t1 = {1:1, 2:2, 3:3}
>>> t2 = t1
>>> ddiff = DeepDiff(t1, t2)
>>> print (ddiff.changes)
    {}

El tipo de un artículo ha cambiado

>>> t1 = {1:1, 2:2, 3:3}
>>> t2 = {1:1, 2:"2", 3:3}
>>> ddiff = DeepDiff(t1, t2)
>>> print (ddiff.changes)
    {'type_changes': ["root[2]: 2=<type 'int'> vs. 2=<type 'str'>"]}

El valor de un artículo ha cambiado

>>> t1 = {1:1, 2:2, 3:3}
>>> t2 = {1:1, 2:4, 3:3}
>>> ddiff = DeepDiff(t1, t2)
>>> print (ddiff.changes)
    {'values_changed': ['root[2]: 2 ====>> 4']}

Artículo agregado y / o eliminado

>>> t1 = {1:1, 2:2, 3:3, 4:4}
>>> t2 = {1:1, 2:4, 3:3, 5:5, 6:6}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff.changes)
    {'dic_item_added': ['root[5, 6]'],
     'dic_item_removed': ['root[4]'],
     'values_changed': ['root[2]: 2 ====>> 4']}

Diferencia de cadena

>>> t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":"world"}}
>>> t2 = {1:1, 2:4, 3:3, 4:{"a":"hello", "b":"world!"}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff.changes, indent = 2)
    { 'values_changed': [ 'root[2]: 2 ====>> 4',
                          "root[4]['b']:\n--- \n+++ \n@@ -1 +1 @@\n-world\n+world!"]}
>>>
>>> print (ddiff.changes['values_changed'][1])
    root[4]['b']:
    --- 
    +++ 
    @@ -1 +1 @@
    -world
    +world!

Diferencia de cuerda 2

>>> t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":"world!\nGoodbye!\n1\n2\nEnd"}}
>>> t2 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":"world\n1\n2\nEnd"}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff.changes, indent = 2)
    { 'values_changed': [ "root[4]['b']:\n--- \n+++ \n@@ -1,5 +1,4 @@\n-world!\n-Goodbye!\n+world\n 1\n 2\n End"]}
>>>
>>> print (ddiff.changes['values_changed'][0])
    root[4]['b']:
    --- 
    +++ 
    @@ -1,5 +1,4 @@
    -world!
    -Goodbye!
    +world
     1
     2
     End

Cambio de tipo

>>> t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2, 3]}}
>>> t2 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":"world\n\n\nEnd"}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff.changes, indent = 2)
    { 'type_changes': [ "root[4]['b']: [1, 2, 3]=<type 'list'> vs. world\n\n\nEnd=<type 'str'>"]}

Lista de diferencia

>>> t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2, 3]}}
>>> t2 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2]}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff.changes, indent = 2)
    { 'list_removed': ["root[4]['b']: [3]"]}

Enumere la diferencia 2: tenga en cuenta que NO toma en cuenta el orden

>>> # Note that it DOES NOT take order into account
... t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2, 3]}}
>>> t2 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 3, 2]}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff.changes, indent = 2)
    { }

Lista que contiene el diccionario:

>>> t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2, {1:1, 2:2}]}}
>>> t2 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2, {1:3}]}}
>>> ddiff = DeepDiff(t1, t2)
>>> pprint (ddiff.changes, indent = 2)
    { 'dic_item_removed': ["root[4]['b'][2][2]"],
      'values_changed': ["root[4]['b'][2][1]: 1 ====>> 3"]}

5

de la manera más simple,

use set (). diferencia (set ())

list_a = [1,2,3]
list_b = [2,3]
print set(list_a).difference(set(list_b))

la respuesta es set([1])


2

En el caso de una lista de diccionarios , la solución de comprensión de la lista completa funciona mientras la setsolución aumenta

TypeError: unhashable type: 'dict'

Caso de prueba

def diff(a, b):
    return [aa for aa in a if aa not in b]

d1 = {"a":1, "b":1}
d2 = {"a":2, "b":2}
d3 = {"a":3, "b":3}

>>> diff([d1, d2, d3], [d2, d3])
[{'a': 1, 'b': 1}]
>>> diff([d1, d2, d3], [d1])
[{'a': 2, 'b': 2}, {'a': 3, 'b': 3}]

0

Código simple que le da la diferencia con varios elementos si lo desea:

a=[1,2,3,3,4]
b=[2,4]
tmp = copy.deepcopy(a)
for k in b:
    if k in tmp:
        tmp.remove(k)
print(tmp)

-1

Al echar un vistazo a TimeComplexity of In-operator, en el peor de los casos funciona con O (n). Incluso para conjuntos.

Entonces, al comparar dos matrices, tendremos una TimeComplexity de O (n) en el mejor de los casos y O (n ^ 2) en el peor de los casos.

Una solución alternativa (pero desafortunadamente más compleja), que funciona con O (n) en el mejor y el peor de los casos, es esta:

# Compares the difference of list a and b
# uses a callback function to compare items
def diff(a, b, callback):
  a_missing_in_b = []
  ai = 0
  bi = 0

  a = sorted(a, callback)
  b = sorted(b, callback)

  while (ai < len(a)) and (bi < len(b)):

    cmp = callback(a[ai], b[bi])
    if cmp < 0:
      a_missing_in_b.append(a[ai])
      ai += 1
    elif cmp > 0:
      # Item b is missing in a
      bi += 1
    else:
      # a and b intersecting on this item
      ai += 1
      bi += 1

  # if a and b are not of same length, we need to add the remaining items
  for ai in xrange(ai, len(a)):
    a_missing_in_b.append(a[ai])


  return a_missing_in_b

p.ej

>>> a=[1,2,3]
>>> b=[2,4,6]
>>> diff(a, b, cmp)
[1, 3]
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.