Python, diferença na lista de computação


195

No Python, qual é a melhor maneira de calcular a diferença entre duas listas?

exemplo

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

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

Respostas:


206

Use setse você não se importa com ordem ou repetição de itens. Use a compreensão da lista se você:

>>> 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 o uso set(b)para assegurar o algoritmo é O (nlogn) em vez de teta (n ^ 2)
Neil G

8
@Pencilcheck - não se você se preocupa em solicitar ou duplicar em A. A setinscrição em B é inofensiva, mas aplicá-la Ae usar o resultado em vez do original Anão é.
Re

1
@NeilG Você considera o tempo consumido para construir o cenário? No meu caso (ambas as listas têm cerca de 10 milhões de strings), o tempo para criar dois conjuntos e subtraí-los é consideravelmente maior do que construir um conjunto e iterar sobre a lista.
dimril

@ Dimril, se é isso que você quer fazer, talvez deva implementar algo mais sofisticado. Você pode, por exemplo, classificar as duas listas O (n log n + m log m) e repetir a segunda lista, mas usar a pesquisa binária para encontrar os itens na primeira lista. Ele sairia para operações O (n log n + m log m + m log n) (em vez de operações O (n * m)), o que não parece muito ruim. Apenas verifique se os vizinhos também eliminam duplicatas em suas implementações de pesquisa binária. Pode até haver um pacote que já implemente isso, mas eu não verifiquei.
jaaq

365

Se o pedido não importa, você pode simplesmente calcular a diferença definida:

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

9
Esta é de longe a melhor solução. Cada caso de teste em listas com ~ 6000 seqüências mostrou que esse método era quase 100x mais rápido que as compreensões de lista.
precisa saber é o seguinte

15
Depende da aplicação: se a preservação da ordem ou duplicação é importante, Roman Bodnarchuk pode ter uma abordagem melhor. Para velocidade e comportamento puro, este parece melhor.
Bryan P

7
Se você tiver vários elementos iguais na lista, esta solução não funcionará.
karantan

Muito mais do que compreensão de lista.
Dawei

4
Esta solução parece tão óbvia, mas está incorreta. Eu sinto Muito. É claro que queremos dizer que uma lista pode ter elementos iguais repetidos. Caso contrário, perguntamos sobre a diferença entre conjuntos, não sobre a diferença de lista.
Sergzach 01/06

67

Você pode fazer um

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

e

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

7
Mas se A = [1,1,1] e B = [0], então isso retorna [1]
Mark Bell

1
@ Mark Bell: Isso é porque um conjunto é uma lista distinta. (remove duplicatas)
nublado

1
@cloudy Então isso não responde à pergunta.
samm82

@ samm82 se A = [1,1,1] do que o conjunto (A) for [1] porque o conjunto é uma lista distinta e remove duplicatas. É por isso que, se A = [1,1,1] e B = [0], ele retorna [1].
nublado

29

Um forro:

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

Ou:

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

14

Os exemplos acima banalizaram o problema de calcular diferenças. Supondo que a classificação ou desduplicação definitivamente facilite a computação da diferença, mas se sua comparação não puder permitir essas suposições, você precisará de uma implementação não trivial de um algoritmo diff. Veja difflib na biblioteca padrão do 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
você recebe +1 por difflib, o que eu não tinha visto antes. no entanto, não concordo que as respostas acima trivializem o problema, conforme indicado .
rbp

Obrigado por usar o difflib - eu estava procurando uma solução usando a biblioteca padrão. No entanto, este não está a funcionar em Python 3, como printmudou a partir de um comando para uma função, e reduce, filtere mapforam declarados unpythonic. (E eu acho Guido pode estar certo - Eu não entendo o que reducefaz, também.)
Post169

Não é uma grande mudança para fazê-lo funcionar no py3. Eu li o debate sobre filtrar, mapear, reduzir e concordar com a opção de empurrar reduzir e alternar impl do filtro nas funções. A natureza mista funcional, OO e processual do python sempre foi, na IMO, um dos seus pontos fortes.
Kevin

14

Python 2.7.3 (padrão, 27 de fevereiro 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'

A função de compreensão de lista roman-bodnarchuk def def diff (a, b) parece ser mais 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

Caso você queira que a diferença entre nos itens da sua lista recursivamente, escrevi um pacote para python: https://github.com/erasmose/deepdiff

Instalação

Instale a partir do PyPi:

pip install deepdiff

Se você é Python3, também precisa instalar:

pip install future six

Exemplo de uso

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

O mesmo objeto retorna vazio

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

O tipo de um item foi alterado

>>> 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'>"]}

O valor de um item foi alterado

>>> 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']}

Item adicionado e / ou removido

>>> 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']}

Diferença de cadeia

>>> 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!

Diferença de string 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

Alteração 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'>"]}

Diferença de lista

>>> 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]"]}

Diferença de lista 2: observe que NÃO leva em conta a ordem

>>> # 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 contém o dicionário:

>>> 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

maneira mais simples,

use set (). diferença (set ())

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

a resposta é set([1])


2

No caso de uma lista de dicionários , a solução completa de compreensão da lista funciona enquanto a setsolução gera

TypeError: unhashable type: 'dict'

Caso de teste

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 simples que fornece a diferença com vários itens, se você desejar:

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

Ao examinar o TimeComplexity do operador interno , na pior das hipóteses, ele funciona com O (n). Mesmo para conjuntos.

Portanto, ao comparar duas matrizes, teremos uma TimeComplexity de O (n) no melhor dos casos e O (n ^ 2) no pior dos casos.

Uma solução alternativa (mas infelizmente mais complexa), que funciona com O (n) na melhor e na pior das hipóteses, é 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

por exemplo

>>> a=[1,2,3]
>>> b=[2,4,6]
>>> diff(a, b, cmp)
[1, 3]
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.