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)
Нейл Дж

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) операций), что выглядит не так уж плохо. Просто убедитесь, что соседи также устраняют дубликаты в ваших реализациях двоичного поиска. Может даже быть пакет, который уже реализует это, но я не проверял.
Яак

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
Если у вас есть несколько одинаковых элементов в списке, это решение не будет работать.
Карантан

Гораздо лучше, чем понимание списка.
Давэй

4
Это решение кажется таким очевидным, но оно неверно. Мне жаль. Конечно, мы имеем в виду, что список может иметь повторяющиеся равные элементы. В противном случае мы спрашиваем о разнице между наборами, а не о разнице в списке.
sergzach

67

Вы можете сделать

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

и

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

7
Но если A = [1,1,1] и B = [0], то это возвращает [1]
Марк Белл

1
@Mark Bell: Это потому, что набор представляет собой отдельный список. (удаляет дубликаты)
облачно

1
@cloudy Тогда это не отвечает на вопрос.
samm82

@ samm82, если A = [1,1,1], чем set (A) равно [1], потому что set является отдельным списком и удаляет дубликаты. Поэтому, если 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'

@ roman-bodnarchuk Функция понимания списка def diff (a, b) работает быстрее.


14

Приведенные выше примеры упрощают задачу вычисления различий. Предполагая, что сортировка или дедупликация определенно облегчит вычисление разницы, но если ваше сравнение не может позволить себе эти предположения, вам понадобится нетривиальная реализация алгоритма diff. Смотрите difflib в стандартной библиотеке python.

#! /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, которого я раньше не видел. тем не менее, я не согласен с тем, что приведенные выше ответы упрощают проблему, как указано .
ОПБ

Спасибо за использование difflib - я искал решение с использованием стандартной библиотеки. Тем не менее, это не работает в Python 3, поскольку printизменилось с команды на функцию reduce, filterи mapбыло объявлено непифоническим. (И я думаю, что Гвидо может быть прав - я тоже не понимаю, что reduceделает.)
Post169

Не большой сдвиг, чтобы заставить его работать на py3. Я читал дискуссию о фильтре, отображении, уменьшении и соглашаюсь с выбором толкать уменьшение и и альтернативное значение фильтра в functools. ИМО, смешанная функциональность, ОО и процедурная природа Python всегда были одной из его сильных сторон.
Кевин



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

Если взглянуть на временную сложность In-оператора, в худшем случае он работает с O (n). Даже для наборов.

Таким образом, при сравнении двух массивов мы будем иметь временную сложность 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.