Python,计算列表差异


195

在Python中,计算两个列表之间差异的最佳方法是什么?

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

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

Answers:


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

31
考虑使用set(b)来确保算法是O(nlogn)而不是Theta(n ^ 2)
Neil G

8
@Pencilcheck-如果您不关心A中的顺序或重复,则不适用。应用于setB是无害的,但将其应用于A并使用结果而不是原始结果A则不是。
马克·里德

1
@NeilG您是否考虑花费时间来构建该集合?在我的情况下(两个列表都有大约1000万个字符串),建立两个集合并减去它们的时间比建立一个集合并遍历列表的时间要大得多。
dimril '18

@dimril如果您要这样做,也许您应该实现一些更复杂的方法。例如,您可以对两个列表O(n log n + m log m)进行排序,然后遍历第二个列表,但是使用二进制搜索在第一个列表中查找项目。它会出现O(n log n + m log m + m log n)个操作(而不是O(n * m)个操作),这似乎还不错。只要确保检查邻居以消除二进制搜索实现中的重复项即可。甚至可能已经有一个程序包实现了此功能,但我没有检查。
jaaq

364

如果顺序无关紧要,则可以简单地计算出设定差:

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

15
取决于应用程序:如果顺序或重复保存很重要,则罗马Bodnarchuk可能有更好的方法。对于速度和纯粹的类似集合的行为,这似乎更好。
布赖恩·P

7
如果列表中有多个相等的元素,则此解决方案将不起作用。
karantan's

比列表理解要好得多。
大维

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

上面的例子简化了计算差异的问题。假设排序或重复数据删除无疑会更轻松地计算差异,但是如果您的比较无法提供这些假设,那么您将需要一个非平凡的diff算法实现。请参阅python标准库中的difflib。

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
您获得difflib的+1,这是我以前从未见过的。不过,我不同意上述答案淡化问题的规定
rbp

感谢您使用difflib-我正在寻找使用标准库的解决方案。然而,这不是在Python 3个工作,作为print已经从一个命令改为功能,并且reducefiltermap已被宣布unpythonic。(而且我认为Guido可能是正确的 -我也不明白是什么reduce。)
Post169

使它适用于py3并没有太大的转变。我已经阅读了关于过滤器,映射,约简的辩论,并同意将过滤器的约简和替代隐式推入功能工具的选择。IMO一直是python的功能,OO和程序的混合特性,这是其优势之一。
凯文

14

Python 2.7.3(默认,2014年2月27日,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)似乎更快。


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

如果您希望差异递归地深入到列表中的项目,我为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-operator的TimeComplexity时,在最坏的情况下它与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]
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.