Python, різниця у списку обчислень


195

У Python, який найкращий спосіб обчислити різницю між двома списками?

приклад

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

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

Відповіді:


206

Використовуйте, setякщо вас не хвилює порядок чи повторення предметів. Використовуйте розуміння списку, якщо ви робите:

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

32
Розглянемо використання set(b)алгоритму O (nlogn) замість Theta (n ^ 2)
Neil G

8
@Pencilcheck - не якщо ви дбаєте про впорядкування чи дублювання в A. Застосування setдо B нешкідливо, але застосувати його до Aта використовувати результат замість оригіналу A- ні.
Марк Рід

1
@NeilG Ви вважаєте, що витрачено час на створення набору? У моєму випадку (обидва списки мають близько 10 М рядків) час на створення двох множин і віднімання їх значно більший, ніж на створення одного набору та повторення над списком.
димріл

@dimril, якщо це те, що ти хочеш зробити, можливо, ти повинен реалізувати щось більш складне. Наприклад, ви можете сортувати обидва списки O (n log n + m log m), а потім повторити другий список, але використовувати двійковий пошук для пошуку елементів у першому списку. Це вийде для операцій O (n log n + m log m + m log n) (замість операцій O (n * m)), що не здається занадто поганим. Просто переконайтеся, що сусіди також не усувають дублікатів у ваших бінарних пошуку. Можливо, навіть є пакет, який вже реалізує це, але я не перевірив.
jaaq

366

Якщо замовлення не має значення, ви можете просто розрахувати встановлену різницю:

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

9
Це, безумовно, найкраще рішення. Тестовий випадок у списках з ~ 6000 рядків показав, що цей метод був майже в 100 разів швидшим, ніж розуміння списку.
perrygeo

15
Залежить від заявки: якщо важливо збереження порядку чи дублювання, Роман Боднарчук може мати кращий підхід. Що стосується швидкості та чистої поведінки на зразок, це здається кращим.
Брайан П

7
Якщо у вас є кілька рівних елементів у списку, це рішення не працюватиме.
karantan

Набагато краще, ніж розуміння списку.
Давей

4
Це рішення видається настільки очевидним, але неправильним. Мені шкода. Звичайно, ми маємо на увазі, що список може мати повторні рівні елементи. Інакше ми запитуємо про різницю між множинами, а не про різницю списку.
серджач

67

Ви можете зробити

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

і

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

7
Але якщо A = [1,1,1] і B = [0], то це повертається [1]
Марк Белл

1
@ Марк Белл: Це тому, що набір є чітким списком. (видаляє дублікати)
хмарно

1
@cloudy Тоді це не відповідає на питання.
samm82

@ samm82, якщо A = [1,1,1], ніж set (A), є [1], тому що множина є виразним списком і видаляє дублікати. Ось чому, якщо A = [1,1,1] і B = [0], він повертається [1].
хмарно

29

Один вкладиш:

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

Або:

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

14

Python 2.7.3 (за замовчуванням, 27 лютого 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())))

Результати:

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

@ Роман-боднарчук у переліку списку функцій def diff (a, b) здається швидшими.


14

Наведені вище приклади тривілізували проблему обчислення різниць. Якщо припустити, що сортування чи дедуплікація, безумовно, полегшує обчислення різниці, але якщо ваше порівняння не може дозволити собі ці припущення, то вам знадобиться нетривіальна реалізація алгоритму diff. Дивіться дифліб у стандартній бібліотеці пітона.

#! /usr/bin/python2
from difflib import SequenceMatcher

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

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
ви отримуєте +1 за difflib, якого я раніше не бачив. проте я не погоджуюся, що наведені вище відповіді реалізують проблему, як заявлено .
rbp

Дякуємо за використання difflib - я шукав рішення за допомогою стандартної бібліотеки. Однак, це не працює в Python 3, як printзмінилося від команди до функції, і reduce, filterі mapбуло оголошено unpythonic. (І я думаю, що Гвідо може мати рацію Я також не розумію, що reduceробить.)
Post169

Не великий зсув, щоб він працював на py3. Я прочитав дискусію з приводу фільтра, картографування, зменшення та погодився з вибором натискати на зменшення та чергування імпульсу фільтра у фунікули. Змішаний функціональний, OO та процедурний характер пітона завжди був, IMO, однією з його сильних сторін.
Кевін



5

Якщо ви хочете, щоб різниця рекурсивно заглиблювалася в елементи вашого списку, я написав пакет для python: https://github.com/erasmose/deepdiff

Установка

Встановити з PyPi:

pip install deepdiff

Якщо ви Python3, вам також потрібно встановити:

pip install future six

Приклад використання

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

Цей же об’єкт повертається порожнім

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

Тип елемента змінився

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

Значення товару змінилося

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

Елемент додано та / або вилучено

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

Різна різниця

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

Різна різниця 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

Зміна типу

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

Різниця в списку

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

Різниця у списку 2: Зауважте, що він НЕ враховує порядок

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

Список, що містить словник:

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


2

У випадку зі списком словників , рішення про розуміння повного списку працює, поки setрішення вирішується

TypeError: unhashable type: 'dict'

Тестовий випадок

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

Простий код, який дає вам різницю в кількох елементах, якщо ви хочете цього:

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

Переглядаючи TimeComplexity In-operator, в гіршому випадку він працює з O (n). Навіть для наборів.

Отже, порівнюючи два масиви, у кращому випадку буде мати TimeComplexity O (n) та O (n ^ 2).

Альтернативне (але, на жаль, більш складне) рішення, яке працює з O (n) у кращому та гіршому випадку, це таке:

# 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

напр

>>> a=[1,2,3]
>>> b=[2,4,6]
>>> diff(a, b, cmp)
[1, 3]
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.