Python, differenza elenco di calcolo


195

In Python, qual è il modo migliore per calcolare la differenza tra due elenchi?

esempio

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

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

Risposte:


206

Utilizzare setse non ti interessa l'ordine degli articoli o la ripetizione. Usa la comprensione dell'elenco se esegui:

>>> 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
Prendi in considerazione l'utilizzo set(b)per assicurarti che l'algoritmo sia O (nlogn) invece di Theta (n ^ 2)
Neil G

8
@Pencilcheck - non se ti interessa ordinare o duplicare in A. L'applicazione seta B è innocua, ma applicarla Ae utilizzare il risultato invece dell'originale Ano.
Mark Reed,

1
@NeilG Consideri il tempo impiegato per costruire il set? Nel mio caso (entrambe le liste hanno circa 10 milioni di stringhe) il tempo necessario per creare due serie e sottrarle è considerevolmente più grande della creazione di una serie e dell'iterazione dell'elenco.
Dimril,

@dimril se è quello che vuoi fare, forse dovresti implementare qualcosa di più sofisticato. Ad esempio, è possibile ordinare entrambi gli elenchi O (n log n + m log m) e quindi scorrere il secondo elenco ma utilizzare la ricerca binaria per trovare gli elementi nel primo elenco. Uscirebbe dalle operazioni O (n log n + m log m + m log n) (anziché dalle operazioni O (n * m)), che non sembra poi così male. Assicurati solo di cercare i vicini per eliminare anche i duplicati nelle tue implementazioni di ricerca binaria. Potrebbe esserci anche un pacchetto che lo implementa già, ma non ho verificato.
Jaaq,

366

Se l'ordine non ha importanza, puoi semplicemente calcolare la differenza impostata:

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

9
Questa è di gran lunga la soluzione migliore. Il test case su liste con ~ 6000 stringhe ciascuna ha mostrato che questo metodo era quasi 100 volte più veloce della comprensione delle liste.
perrygeo,

15
Dipende dall'applicazione: se la conservazione dell'ordine o della duplicazione è importante, Roman Bodnarchuk può avere un approccio migliore. Per velocità e puro comportamento simile a un set, questo sembra migliore.
Bryan P,

7
Se nell'elenco sono presenti più elementi uguali, questa soluzione non funzionerà.
Karantan,

Molto più meglio della comprensione dell'elenco.
Dawei,

4
Questa soluzione sembra così ovvia ma non è corretta. Mi dispiace. Ovviamente intendiamo che un elenco può avere ripetuti elementi uguali. Altrimenti chiediamo della differenza tra le serie, non della differenza della lista.
sergzach,

67

Puoi fare un

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

e

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

7
Ma se A = [1,1,1] e B = [0], questo restituisce [1]
Mark Bell,

1
@Mark Bell: Ecco perché un set è un elenco distinto. (rimuove i duplicati)
nuvoloso

1
@cloudy Quindi questo non risponde alla domanda.
samm82,

@ samm82 se A = [1,1,1] di set (A) è [1] perché set è un elenco distinto e rimuove i duplicati. Ecco perché, se A = [1,1,1] e B = [0] restituisce [1].
nuvoloso

29

Una fodera:

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

Gli esempi precedenti hanno banalizzato il problema del calcolo delle differenze. Supponendo che l'ordinamento o la deduplicazione semplifichi decisamente il calcolo della differenza, ma se il confronto non può permettersi tali presupposti, sarà necessaria un'implementazione non banale di un algoritmo diff. Vedi difflib nella libreria standard di 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
ottieni +1 per difflib, che non avevo mai visto prima. nondimeno, non sono d'accordo sul fatto che le risposte di cui sopra banalizzano il problema come affermato .
rbp,

Grazie per aver utilizzato difflib: stavo cercando una soluzione utilizzando la libreria standard. Tuttavia, questo non funziona in Python 3, poiché printè stato modificato da un comando a una funzione e reduce, filtere , mapè stato dichiarato non meccanico. (E penso che Guido possa avere ragione - non capisco neanche cosa reduce.)
Post169

Non è un grande cambiamento per farlo funzionare per py3. Ho letto il dibattito su filtro, mappa, riduzione e accordo con la scelta di spingere ridurre e alternare impl di filtro in funzioni. Il misto funzionale, OO e la natura procedurale di Python è sempre stato, IMO, uno dei suoi punti di forza.
Kevin,

14

Python 2.7.3 (impostazione predefinita, 27 febbraio 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())))

risultati:

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

La funzione di comprensione dell'elenco di roman-bodnarchuk def diff (a, b) sembra essere più veloce.


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

Nel caso in cui desideri che la differenza ricada in modo ricorsivo negli elementi del tuo elenco, ho scritto un pacchetto per Python: https://github.com/erasmose/deepdiff

Installazione

Installa da PyPi:

pip install deepdiff

Se sei Python3 devi installare anche:

pip install future six

Esempio di utilizzo

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

Lo stesso oggetto restituisce vuoto

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

Il tipo di un elemento è cambiato

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

Il valore di un articolo è cambiato

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

Articolo aggiunto e / o rimosso

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

Differenza di stringa

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

Differenza di stringa 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

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

Elenco differenza

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

Elenco differenza 2: Nota che NON tiene conto dell'ordine

>>> # 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)
    { }

Elenco che contiene il dizionario:

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

il modo più semplice,

usa set (). differenza (set ())

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

la risposta è set([1])


2

Nel caso di un elenco di dizionari , la soluzione di comprensione dell'elenco completo funziona mentre la setsoluzione aumenta

TypeError: unhashable type: 'dict'

Test Case

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

Codice semplice che ti dà la differenza con più elementi se lo desideri:

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

Quando si osserva TimeComplexity di In-operator, nel peggiore dei casi funziona con O (n). Anche per i set.

Quindi, confrontando due matrici avremo una TimeComplexity di O (n) nel migliore dei casi e O (n ^ 2) nel peggiore dei casi.

Una soluzione alternativa (ma purtroppo più complessa), che funziona con O (n) nel caso migliore e peggiore è questa:

# 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

per esempio

>>> a=[1,2,3]
>>> b=[2,4,6]
>>> diff(a, b, cmp)
[1, 3]
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.