Python, oblicz listę różnic

195

W Pythonie, jaki jest najlepszy sposób obliczenia różnicy między dwiema listami?

przykład

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

A - B = [1,3,4]
B - A = [5]
Mikrofon
źródło

Odpowiedzi:

206

Użyj, setjeśli nie obchodzi Cię kolejność lub powtarzalność przedmiotów. Użyj rozumienia listy, jeśli:

>>> 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]
>>> 
Roman Bodnarchuk
źródło
31
Zastanów się nad użyciem, set(b)aby upewnić się, że algorytm to O (nlogn) zamiast Theta (n ^ 2)
Neil G
8
@ Pencilcheck - nie, jeśli zależy ci na zamawianiu lub powielaniu w A. Stosowanie się setdo B jest nieszkodliwe, ale stosowanie go do Ai używanie wyniku zamiast oryginału Anie jest.
Mark Reed
1
@NeilG Czy uważasz, że potrzeba czasu na zbudowanie zestawu? W moim przypadku (obie listy mają około 10 milionów ciągów) czas na zbudowanie dwóch zestawów i odjęcie ich jest znacznie większy niż zbudowanie jednego zestawu i iteracja po liście.
dimril
@dimril, jeśli to właśnie chcesz zrobić, może powinieneś wdrożyć coś bardziej wyrafinowanego. Możesz na przykład posortować obie listy O (n log n + m log m), a następnie przejść do drugiej listy, ale użyć wyszukiwania binarnego, aby znaleźć elementy na pierwszej liście. Wyszłoby to na operacje O (n log n + m log m + m log n) (zamiast operacji O (n * m)), co nie wydaje się takie złe. Wystarczy sprawdzić, czy sąsiedzi, aby wyeliminować duplikaty w implementacjach wyszukiwania binarnego. Może nawet istnieje pakiet, który już to implementuje, ale nie sprawdziłem.
jaaq,
366

Jeśli kolejność nie ma znaczenia, możesz po prostu obliczyć ustawioną różnicę:

>>> set([1,2,3,4]) - set([2,5])
set([1, 4, 3])
>>> set([2,5]) - set([1,2,3,4])
set([5])
phihag
źródło
9
To zdecydowanie najlepsze rozwiązanie. Przypadek testowy na listach zawierających po około 6000 ciągów pokazał, że ta metoda była prawie 100 razy szybsza niż interpretacja list.
perrygeo
15
Zależy od zastosowania: jeśli ważne jest zachowanie porządku lub powielania, Roman Bodnarchuk może mieć lepsze podejście. Jeśli chodzi o szybkość i zachowanie przypominające zestaw, wydaje się to lepsze.
Bryan P.
7
Jeśli masz wiele równych elementów na liście, to rozwiązanie nie będzie działać.
karantan
Znacznie lepiej niż zrozumienie listy.
Dawei
4
To rozwiązanie wydaje się takie oczywiste, ale jest nieprawidłowe. Przykro mi. Oczywiście mamy na myśli, że lista może mieć powtarzające się równe elementy. W przeciwnym razie pytamy o różnicę między zestawami, a nie o różnicę listy.
sergzach
67

Możesz zrobić

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

i

list(set(B)-set(A))
Senthil Kumaran
źródło
7
Ale jeśli A = [1,1,1] i B = [0], to zwraca [1]
Mark Bell
1
@Mark Bell: To dlatego, że zestaw jest odrębną listą. (usuwa duplikaty)
zachmurzenie
1
@cloudy To nie odpowiada na pytanie.
samm82,
@ samm82, jeśli A = [1,1,1] niż zestaw (A) to [1], ponieważ zestaw jest odrębną listą i usuwa duplikaty. Dlatego jeśli A = [1,1,1] i B = [0] zwraca [1].
pochmurno
29

Jedna wkładka:

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

Lub:

diff = lambda l1,l2: filter(lambda x: x not in l2, l1)
diff(A,B)
diff(B,A)
Artsiom Rudzenka
źródło
14

Powyższe przykłady strywializowały problem obliczania różnic. Zakładając, że sortowanie lub usuwanie duplikatów zdecydowanie ułatwi obliczenie różnicy, ale jeśli twoje porównanie nie może pozwolić sobie na te założenia, będziesz potrzebować nietrywialnej implementacji algorytmu różnicowego. Zobacz difflib w standardowej bibliotece Pythona.

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

Kevin
źródło
1
dostajesz +1 za difflib, którego wcześniej nie widziałem. nie zgadzam się jednak, że powyższe odpowiedzi pomniejszają problem, jak stwierdzono .
rbp
Dzięki za korzystanie z difflib - szukałem rozwiązania przy użyciu standardowej biblioteki. Jednak to nie działa w Pythonie 3, jak printzmieniła się z poleceniem do funkcji, i reduce, filteri mapzostały uznane unpythonic. (I myślę, że Guido może mieć rację - też nie rozumiem, co to reducerobi.)
Post169,
Nie jest to duża zmiana, aby działało w przypadku py3. Przeczytałem debatę na temat filtrowania, mapowania, zmniejszania i zgadzam się z wyborem, aby przesunąć redukcję i na przemian zaimplementować filtr w funools. Mieszana funkcjonalna, OO i proceduralna natura Pythona zawsze była, IMO, jedną z jego mocnych stron.
Kevin,
14

Python 2.7.3 (domyślnie, 27 lutego 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())))

Wyniki:

# 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 lista pojęć funkcja def diff (a, b) wydaje się być szybsza.

Moreno
źródło
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 
Saksham Varma
źródło
8

Chcesz użyć setzamiast list.

Kaczka komunistyczna
źródło
5

Jeśli chcesz rekurencyjnie sięgnąć głęboko w elementy listy, napisałem pakiet dla pythona: https://github.com/erasmose/deepdiff

Instalacja

Zainstaluj z PyPi:

pip install deepdiff

Jeśli jesteś Python3, musisz także zainstalować:

pip install future six

Przykładowe użycie

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

Ten sam obiekt zwraca pusty

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

Rodzaj przedmiotu zmienił się

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

Wartość przedmiotu uległa zmianie

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

Element został dodany i / lub usunięty

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

Różnica w strunach

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

Różnica w strunach 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

Zmiana typu

>>> 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 różnic

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

Różnica w liście 2: Należy pamiętać, że NIE bierze ono pod uwagę kolejności

>>> # 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 zawierająca słownik:

>>> 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"]}
Seperman
źródło
5

najprostszy sposób,

użyj set (). różnica (set ())

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

odpowiedź to set([1])

Mohideen bin Mohammed
źródło
2

W przypadku listy słowników rozwiązanie pełnego zrozumienia listy działa, gdy setrozwiązanie się podnosi

TypeError: unhashable type: 'dict'

Przypadek testowy

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}]
joao
źródło
0

Prosty kod, który daje różnicę w przypadku wielu elementów, jeśli chcesz:

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)
JESTEM
źródło
-1

Gdy przyjrzymy się czasowi złożoności operatora, w najgorszym przypadku działa on z O (n). Nawet dla zestawów.

Porównując dwie tablice, będziemy mieli TimeComplexity O (n) w najlepszym przypadku i O (n ^ 2) w najgorszym przypadku.

Alternatywne (ale niestety bardziej złożone) rozwiązanie, które w najlepszym i najgorszym przypadku działa z O (n), to:

# 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

na przykład

>>> a=[1,2,3]
>>> b=[2,4,6]
>>> diff(a, b, cmp)
[1, 3]
DerKnorr
źródło