【问题标题】:Compute list difference计算列表差异
【发布时间】:2011-09-23 02:35:03
【问题描述】:

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

例子

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

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

【问题讨论】:

    标签: python list


    【解决方案1】:

    有 3 个选项可以做到这一点,其中两个可以接受,一个不应该这样做。

    三个选项,概括地说是:

    1. 减去两组(有时最好)
    2. 检查每个列表项是否存在于集合中(最好的大多数时间)
    3. 检查每个列表项是否存在于列表中(不要这样做)

    选项 3) 永远不应选择选项 2)。根据您的应用程序的需要,您可能更喜欢选项 1) 或 2),而 2) 可能是大多数用例中的首选方法。 2) 与 1) 的性能非常相似,因为两者都具有 O(m + n) 时间复杂度。相比之下,2) 在空间复杂度方面比 1) 具有边际优势,并且保持原始列表的顺序和原始列表中的任何重复。

    如果您想删除重复而不关心顺序,那么 1) 可能最适合您。

    import time
    
    def fun1(l1, l2):
        # Order and duplications in l1 are both lost, O(m) + O(n)
        return set(l1) - set(l2)
    
    def fun2(l1, l2):
        # Order and duplications in l1 are both preserved, O(m) + O(n)
        l2_set = set(l2)
        return [item for item in l1 if item not in l2_set]
    
    def fun3(l1, l2):
        # Order and duplications in l1 are both preserved, O(m * n)
        # Don't do
        return [item for item in l1 if item not in l2]
    
    A = list(range(7500))
    B = list(range(5000, 10000))
    
    loops = 100
    
    start = time.time()
    for _ in range(loops):
        fun1(A, B)
    print(f"fun1 time: {time.time() - start}")
    
    start = time.time()
    for _ in range(loops):
        fun2(A, B)
    print(f"fun2 time: {time.time() - start}")
    
    start = time.time()
    for _ in range(loops):
        fun3(A, B)
    print(f"fun3 time: {time.time() - start}")
    
    fun1 time: 0.03749704360961914
    fun2 time: 0.04109621047973633
    fun3 time: 32.55076885223389
    

    【讨论】:

      【解决方案2】:

      我在这个线程中没有看到在 A 中保留重复的解决方案。当 A 的元素与 B 的元素匹配时,必须在 B 中删除该元素,以便当相同的元素在 A 中再次出现时,如果此元素在 B 中仅出现一次,则它必须出现在差异中。

      def diff(first, second):
         l2 = list(second)
         l3 = []
         for el in first:
            if el in l2:
               l2.remove(el)
            else:
               l3 += [el]
         return l3
      
      l1 = [1, 2, 1, 3, 4]
      l2 = [1, 2, 3, 3]
      diff(l1, l2)
      >>> [1, 4]
      

      【讨论】:

        【解决方案3】:

        如果顺序无所谓,可以简单的计算集差:

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

        【讨论】:

        • 这是迄今为止最好的解决方案。每个包含约 6000 个字符串的列表的测试用例表明,这种方法比列表推导式快 100 倍。
        • 取决于应用程序:如果顺序或重复保存很重要,Roman Bodnarchuk 可能有更好的方法。对于速度和纯粹的类似集合的行为,这个似乎更好。
        • 如果列表中有多个相同的元素,此解决方案将不起作用。
        • 比列表理解要好得多。
        • 这个解决方案看起来很明显,但它是不正确的。对不起。当然,我们的意思是一个列表可以有重复的相等元素。否则我们会询问集合之间的差异,而不是列表差异。
        【解决方案4】:

        上面的例子简化了计算差异的问题。假设排序或重复数据删除肯定更容易计算差异,但如果您的比较无法承受这些假设,那么您将需要一个不平凡的 diff 算法实现。请参阅 python 标准库中的 difflib。

        #! /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() ) ) ) )
        

        或者 Python3...

        #! /usr/bin/python3
        from difflib import SequenceMatcher
        from functools import reduce
        
        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]]
        

        【讨论】:

        • 你会为 difflib 获得 +1,这是我以前从未见过的。尽管如此,我不同意上述答案使问题变得微不足道如上所述
        • 感谢您使用 difflib - 我正在寻找使用标准库的解决方案。但是,这在 Python 3 中不起作用,因为 print 已从命令更改为函数,并且 reducefiltermap 已被声明为 unpythonic。 (我认为Guido may be right - 我也不明白reduce 做了什么。)
        • 让它适用于 py3 并不是一个很大的转变。我已经阅读了关于过滤器、映射、减少的辩论,并同意将减少和过滤器的替代 impl 推入 functools 的选择。 Python 的混合功能、OO 和程序性质一直是 IMO 的优势之一。
        【解决方案5】:

        添加一个答案来处理我们希望与重复有严格区别的情况,即我们希望在结果中保留的第一个列表中有重复。例如得到,

        [1, 1, 1, 2] - [1, 1] --> [1, 2]
        

        我们可以使用一个额外的计数器来拥有一个优雅的差异函数。

        from collections import Counter
        
        def diff(first, second):
            secondCntr = Counter(second)
            second = set(second)
            res = []
            for i in first:
                if i not in second:
                    res.append(i)
                elif i in secondCntr:
                    if secondCntr[i] > 0:
                        secondCntr[i] -= 1
                    else:
                        res.append(i)        
            return res
        

        【讨论】:

          【解决方案6】:

          简单的代码,如果你想要的话,可以为你提供多个项目的区别:

          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)
          

          【讨论】:

            【解决方案7】:

            最简单的方法,

            使用 set().difference(set())

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

            答案是set([1])

            【讨论】:

              【解决方案8】:

              如果您不关心项目顺序或重复,请使用set。如果你这样做,请使用list comprehensions

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

              【讨论】:

              • 考虑使用set(b) 以确保算法是 O(nlogn) 而不是 Theta(n^2)
              • @Pencilcheck - 如果您关心 A 中的排序或重复,则不需要。将 set 应用于 B 是无害的,但将其应用于 A 并使用结果而不是原始 A 是不是。
              • @NeilG 您是否考虑过构建集合所花费的时间?在我的情况下(两个列表都有大约 10M 个字符串)构建两个集合并减去它们的时间比构建一个集合并遍历列表要大得多。
              • @dimril 如果这就是你想要做的,也许你应该实现一些更复杂的东西。例如,您可以对两个列表 O(n log n + m log m) 进行排序,然后遍历第二个列表,但使用二进制搜索来查找第一个列表中的项目。它会出现 O(n log n + m log m + m log n) 操作(而不是 O(n*m) 操作),这看起来还不错。只需确保检查邻居以消除二进制搜索实现中的重复项。甚至可能有一个包已经实现了这个,但我没有检查。
              • 抱歉,这个解决方案不会保留 A 中的重复,因为 second 永远不会改变。当 A 的元素与 B 的元素匹配时,必须在 B 中删除该元素,以便当相同的元素在 A 中再次出现时,如果该元素仅在 B 中出现一次,则必须保留它。我放了一个版本线程中的 diff 函数,考虑到 A 中的重复。
              【解决方案9】:

              字典列表的情况下,完整列表理解解决方案有效,而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}]
              

              【讨论】:

                【解决方案10】:

                查看 In-operator 的 TimeComplexity 时,在最坏的情况下,它适用于 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]
                

                【讨论】:

                • 除非您的项目的散列函数被破坏,否则您在实践中永远不会遇到 O(n) 最坏情况的行为。 (在这种情况下,这就是你应该解决的问题。)
                【解决方案11】:
                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 
                

                【讨论】:

                  【解决方案12】:

                  如果您希望将差异递归深入到列表中的项目中,我已经为 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"]}
                  

                  【讨论】:

                    【解决方案13】:

                    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) 似乎更快。

                    【讨论】:

                      【解决方案14】:

                      一个班轮:

                      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)
                      

                      【讨论】:

                        【解决方案15】:

                        你可以做一个

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

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

                        【讨论】:

                        • 但是如果 A = [1,1,1] 和 B = [0] 那么返回 [1]
                        • @Mark Bell:那是因为集合是一个不同的列表。 (删除重复项)
                        • @cloudy 那么这并不能回答问题。
                        • @samm82 if A=[1,1,1] than set(A) is [1] 因为 set 是一个不同的列表并删除重复项。这就是为什么,如果 A = [1,1,1] 和 B = [0] 它返回 [1]。
                        【解决方案16】:

                        您可能希望使用set 而不是list

                        【讨论】:

                          猜你喜欢
                          • 1970-01-01
                          • 2018-08-30
                          • 2016-06-14
                          • 2017-07-07
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 2022-01-17
                          相关资源
                          最近更新 更多