【问题标题】:Remove all the elements that occur in one list from another从另一个列表中删除出现在一个列表中的所有元素
【发布时间】:2011-05-11 19:19:34
【问题描述】:

假设我有两个列表,l1l2。我想执行l1 - l2,它返回l1的所有元素而不是l2

我可以想到一种简单的循环方法来执行此操作,但这将是非常低效的。什么是pythonic和有效的方法?

例如,如果我有l1 = [1,2,6,8] and l2 = [2,3,5,8]l1 - l2 应该返回[1,6]

【问题讨论】:

  • 提示:PEP8 指出不应使用小写“L”,因为它看起来太像 1。
  • 我同意。我读了整个问题和答案,想知道为什么人们一直使用 11 和 12。只有当我读到@spelchekr 的评论时,它才有意义。
  • @JimG。数据框和列表不是一回事。
  • 这个问题没有很好的定义。列表允许重复项。 [1, 1, 2, 3] - [1, 2] 应该返回 [1, 3] 还是只返回 [3]?下面的解决方案似乎都假设它应该返回 [3],即所有与第二个列表的元素匹配的成员都应该从第一个列表中删除。您有时需要列表操作来遵守代数性质。如果 A - B = C,则 C + B = A。这里不是这种情况,因为重复的值会丢失。

标签: python list


【解决方案1】:

Python 有一个名为List Comprehensions 的语言特性,它非常适合让这类事情变得非常简单。以下语句完全符合您的要求并将结果存储在l3

l3 = [x for x in l1 if x not in l2]

l3 将包含[1, 6]

【讨论】:

  • 非常pythonic;我喜欢!效率如何?
  • 我相信它的效率很高,而且它的好处是可读性极强,并且对于您要完成的工作一目了然。我遇到了一篇关于效率的博文,您可能会觉得有趣:blog.cdleary.com/2010/04/efficiency-of-list-comprehensions
  • @fandom:列表推导本身非常有效(尽管生成器推导可能通过不在内存中复制元素而更有效),但in 运算符在列表上效率不高。列表上的in 是 O(n),而集合上的 in 是 O(1)。但是,在您获得数千个或更多元素之前,您不太可能注意到其中的差异。
  • l3 = [x for x in l1 if x not in set(l2)] ?我确定set(l2) 是否会被多次调用。
  • 你也可以设置l2s = set(l2),然后说l3 = [x for x in l1 if x not in l2s]。稍微容易一些。
【解决方案2】:

一种方法是使用集合:

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

但是请注意,集合不会保留元素的顺序,并会导致删除任何重复的元素。元素也需要是可散列的。如果这些限制是可以容忍的,这通常可能是最简单且性能最高的选项。

【讨论】:

  • 这也会从l1 中删除重复项,这可能是一个不受欢迎的副作用。
  • ..并丢失元素顺序(如果顺序很重要)。
  • 我只想补充一点,我对这个和接受的答案进行了计时,它的性能提高了大约 3 倍:timeit.timeit('a = [1,2,3,4]; b = [1,3]; c = [i for i in a if a not in b]', number=100000) -> 0.12061533199999985timeit.timeit('a = {1,2,3,4}; b = {1,3}; c = a - b', number=100000) -> 0.04106225999998969。所以如果性能是一个重要因素,这个答案可能更合适(如果你不关心重复或顺序)
  • 更快,但不是现有顺序
【解决方案3】:

性能比较

比较此处提到的所有答案在 Python 3.9.1Python 2.7.16 上的性能。

Python 3.9.1

答案按表现顺序列出:

  1. Arkku's set 差值使用减法“-”运算 - (每个循环 91.3 纳秒)

    mquadri$ python3 -m timeit -s "l1 = set([1,2,6,8]); l2 = set([2,3,5,8]);" "l1 - l2"
    5000000 loops, best of 5: 91.3 nsec per loop
    
  2. Moinuddin Quadri's 使用set().difference()-(每个循环 133 纳秒)

    mquadri$ python3 -m timeit -s "l1 = set([1,2,6,8]); l2 = set([2,3,5,8]);" "l1.difference(l2)"
    2000000 loops, best of 5: 133 nsec per loop
    
  3. Moinuddin Quadri's 使用基于 set 的查找的列表理解-(每个循环 366 纳秒)

     mquadri$ python3 -m timeit -s "l1 = [1,2,6,8]; l2 = set([2,3,5,8]);" "[x for x in l1 if x not in l2]"
     1000000 loops, best of 5: 366 nsec per loop
    
  4. Donut's 普通列表上的列表理解 - (每个循环 489 纳秒)

     mquadri$ python3 -m timeit -s "l1 = [1,2,6,8]; l2 = [2,3,5,8];" "[x for x in l1 if x not in l2]"
     500000 loops, best of 5: 489 nsec per loop
    
  5. Daniel Pryden's 带有基于 set 的查找的生成器表达式和类型转换为 list - (每个循环 583 纳秒) :根据 OP 的要求,显式类型转换到列表以获取最终对象为list。如果 generator expression 被替换为 list comprehension,它将与 Moinuddin Quadri's list comprehension 与基于 set 的查找相同。 p>

     mquadri$ mquadri$ python3 -m timeit -s "l1 = [1,2,6,8]; l2 = set([2,3,5,8]);" "list(x for x in l1 if x not in l2)"
     500000 loops, best of 5: 583 nsec per loop
    
  6. Moinuddin Quadri's 使用 filter() 并显式类型转换为 list(需要像 Python 3.x 中一样显式类型转换,它返回迭代器) - (每个循环 681 纳秒)

     mquadri$ python3 -m timeit -s "l1 = [1,2,6,8]; l2 = set([2,3,5,8]);" "list(filter(lambda x: x not in l2, l1))"
     500000 loops, best of 5: 681 nsec per loop
    
  7. Akshay Hazari's 使用 functools.reduce + filter -(每个循环 3.36 微秒)的组合:从 Python 3 显式类型转换为 list .x 它开始返回返回迭代器。我们还需要导入 functools 以在 Python 3.x 中使用 reduce

     mquadri$ python3 -m timeit "from functools import reduce; l1 = [1,2,6,8]; l2 = [2,3,5,8];" "list(reduce(lambda x,y : filter(lambda z: z!=y,x) ,l1,l2))"
     100000 loops, best of 5: 3.36 usec per loop
    

Python 2.7.16

答案按表现顺序列出:

  1. Arkku's set 使用减法的差异“-”操作 -(每个循环 0.0783 微秒)

    mquadri$ python -m timeit -s "l1 = set([1,2,6,8]); l2 = set([2,3,5,8]);" "l1 - l2"
    10000000 loops, best of 3: 0.0783 usec per loop
    
  2. Moinuddin Quadri's 使用set().difference()-(每个循环 0.117 微秒)

    mquadri$ mquadri$ python -m timeit -s "l1 = set([1,2,6,8]); l2 = set([2,3,5,8]);" "l1.difference(l2)"
    10000000 loops, best of 3: 0.117 usec per loop
    
  3. Moinuddin Quadri's 基于set 的查找的列表理解-(每个循环 0.246 微秒)

     mquadri$ python -m timeit -s "l1 = [1,2,6,8]; l2 = set([2,3,5,8]);" "[x for x in l1 if x not in l2]"
     1000000 loops, best of 3: 0.246 usec per loop
    
  4. Donut's 普通列表上的列表理解 - (每个循环 0.372 微秒)

     mquadri$ python -m timeit -s "l1 = [1,2,6,8]; l2 = [2,3,5,8];" "[x for x in l1 if x not in l2]"
     1000000 loops, best of 3: 0.372 usec per loop
    
  5. Moinuddin Quadri's 使用filter() - (每个循环 0.593 微秒)

     mquadri$ python -m timeit -s "l1 = [1,2,6,8]; l2 = set([2,3,5,8]);" "filter(lambda x: x not in l2, l1)"
     1000000 loops, best of 3: 0.593 usec per loop
    
  6. Daniel Pryden's 带有基于 set 的查找的生成器表达式和类型转换为 list - (每个循环 0.964):根据 OP 的要求,显式类型转换到列表以获取最终对象为list。如果 generator expression 被替换为 list comprehension,它将与 Moinuddin Quadri's list comprehension 与基于 set 的查找相同。 p>

     mquadri$ python -m timeit -s "l1 = [1,2,6,8]; l2 = set([2,3,5,8]);" "list(x for x in l1 if x not in l2)"
     1000000 loops, best of 3: 0.964 usec per loop
    
  7. Akshay Hazari's 使用 functools.reduce + filter -(每个循环 2.78 微秒)

     mquadri$ python -m timeit "l1 = [1,2,6,8]; l2 = [2,3,5,8];" "reduce(lambda x,y : filter(lambda z: z!=y,x) ,l1,l2)"
     100000 loops, best of 3: 2.78 usec per loop
    

【讨论】:

  • 这个答案是对人类的伟大服务。我正在使用列表理解,但我的操作未能在 25 分钟内完成;然后我切换到设置减法,它在 24 秒内完成。奇迹般的进步,远远超出你的时间。
  • 哇哇哇哇!惊人的努力。
  • 为什么 Python 3.9 中不同方法的循环数不同?
【解决方案4】:

扩展 Donut 的答案和此处的其他答案,您可以通过使用生成器推导而不是列表推导以及使用 set 数据结构(因为 in 运算符为 O(n ) 在列表上,但在集合上为 O(1)。

所以这里有一个适合你的函数:

def filter_list(full_list, excludes):
    s = set(excludes)
    return (x for x in full_list if x not in s)

结果将是一个迭代器,它会懒惰地获取过滤后的列表。如果您需要一个真正的列表对象(例如,如果您需要对结果执行len()),那么您可以轻松地构建一个列表,如下所示:

filtered_list = list(filter_list(full_list, excludes))

【讨论】:

    【解决方案5】:

    使用 Python 集合类型。那将是最 Pythonic 的。 :)

    另外,既然是原生的,应该也是最优化的方法。

    见:

    http://docs.python.org/library/stdtypes.html#set

    http://docs.python.org/library/sets.htm(适用于旧版 python)

    # Using Python 2.7 set literal format.
    # Otherwise, use: l1 = set([1,2,6,8])
    #
    l1 = {1,2,6,8}
    l2 = {2,3,5,8}
    l3 = l1 - l2
    

    【讨论】:

    • 使用集合时要注意输出是有序的,即{1,3,2}变成{1,2,3}和{"A","C","B "} 变成 {"A","B","C"} 而你可能不想这样。
    • 如果列表l1包含重复元素,此方法将不起作用。
    【解决方案6】:

    使用Set Comprehensions {x for x in l2} 或 set(l2) 获取设置,然后使用List Comprehensions 获取列表

    l2set = set(l2)
    l3 = [x for x in l1 if x not in l2set]
    

    基准测试代码:

    import time
    
    l1 = list(range(1000*10 * 3))
    l2 = list(range(1000*10 * 2))
    
    l2set = {x for x in l2}
    
    tic = time.time()
    l3 = [x for x in l1 if x not in l2set]
    toc = time.time()
    diffset = toc-tic
    print(diffset)
    
    tic = time.time()
    l3 = [x for x in l1 if x not in l2]
    toc = time.time()
    difflist = toc-tic
    print(difflist)
    
    print("speedup %fx"%(difflist/diffset))
    

    基准测试结果:

    0.0015058517456054688
    3.968189239501953
    speedup 2635.179227x    
    

    【讨论】:

    • l2set = set( l2 ) 而不是 l2set = { x for x in l2 }
    • 不错的灵魂!但必须记住,它只适用于可散列的对象。
    【解决方案7】:

    替代解决方案:

    reduce(lambda x,y : filter(lambda z: z!=y,x) ,[2,3,5,8],[1,2,6,8])
    

    【讨论】:

    • 使用这种方法有什么好处吗?看起来它更复杂,更难阅读,没有太多好处。
    • 这可能看起来很复杂。 Reduce 非常灵活,可以用于很多目的。它被称为折叠。 reduce 实际上是 foldl 。假设您想在其中添加更复杂的东西,那么在这个函数中是可能的,但是作为选择的最佳答案的列表理解只会为您提供相同类型的输出,即列表并且可能具有相同的长度,而折叠则可以也改变输出类型。 en.wikipedia.org/wiki/Fold_%28higher-order_function%29 。该解决方案的复杂度为 n*m 或更小。不过,其他人可能会更好,也可能不会。
    • reduce(函数、列表、初始累加器(可以是任何类型))
    【解决方案8】:

    使用filterfalse 没有 lambda 表达式

    当使用filterfilterfalse 等函数以及itertools 中的类似函数时,您通常可以通过避免使用lambda 表达式并使用现有函数来节省性能。 listset 的实例定义了一个 __contains__ 方法用于遏制检查。 in-operator 在后台调用此方法,因此使用 x in l2 可以替换为 l2.__contains__(x)。通常这种替换并不是真的更漂亮,但在这种特定情况下,当与filterfalse 结合使用时,它可以让我们获得比使用lambda-表达式更好的性能:

    >>> from itertools import filterfalse
    >>> l1 = [1, 2, 6, 8]
    >>> l2 = [2, 3, 5, 8]
    >>> list(filterfalse(l2.__contains__, l1))
    [1, 6]
    

    filterfalse 创建一个迭代器,当用作l2.__contains__ 的参数时,该迭代器产生返回false 的所有元素。

    Sets 更快地实现了__contains__,所以更好的是:

    >>> from itertools import filterfalse
    >>> l1 = [1, 2, 6, 8]
    >>> l2 = set([2, 3, 5, 8])
    >>> list(filterfalse(l2.__contains__, l1))
    [1, 6]
    

    性能

    使用列表:

    $  python3 -m timeit -s "from itertools import filterfalse; l1 = [1,2,6,8]; l2 = set([2,3,5,8]);" "list(filterfalse(l2.__contains__, l1))"
    500000 loops, best of 5: 522 nsec per loop
    

    使用集合:

    $ python3 -m timeit -s "from itertools import filterfalse; l1 = [1,2,6,8]; l2 = set([2,3,5,8]);" "list(filterfalse(l2.__contains__, l1))"
    1000000 loops, best of 5: 359 nsec per loop
    

    【讨论】:

      【解决方案9】:

      Python 3.8 上的集合与列表理解基准测试

      (与 Moinuddin Quadri 的基准相加)

      tldr:使用 Arkku 的 set 解决方案,比承诺的还要快!

      对照列表检查现有文件

      在我的示例中,我发现使用 Arkku 的集合解决方案pythonic 列表理解40 倍 (!)根据列表检查现有文件名的实际应用。

      列表理解:

      %%time
      import glob
      existing = [int(os.path.basename(x).split(".")[0]) for x in glob.glob("*.txt")]
      wanted = list(range(1, 100000))
      [i for i in wanted if i not in existing]
      

      持续时间:28.2 秒

      套装

      %%time
      import glob
      existing = [int(os.path.basename(x).split(".")[0]) for x in glob.glob("*.txt")]
      wanted = list(range(1, 100000))
      set(wanted) - set(existing)
      

      持续时间:689 毫秒

      【讨论】:

        【解决方案10】:

        试试这个:

        l1=[1,2,6,8]
        l2=[2,3,5,8]
        r=[]
        for x in l1:
            if x in l2:
                continue
            r=r+[x]
        print(r)
        

        【讨论】:

          【解决方案11】:

          使用set.difference():

          您可以使用set.difference() 来获取新集合,其中包含集合中不包含在其他元素中的元素。即set(A).difference(B) 将返回包含A 中存在的项目的集合,但不会在B 中。例如:

          >>> set([1,2,6,8]).difference([2,3,5,8])
          {1, 6}
          

          这是Arkku's answer 中提到的函数式方法来获取set 的差异(它使用算术减法- 运算符来设置差异)

          由于sets 是无序的,您将失去初始列表中元素的顺序。 (如果您想保持元素的顺序,请继续阅读下一节)

          使用 List Comprehension 和基于 set 的查找

          如果您希望保持初始列表中的顺序,那么基于Donut's list comprehension 的答案就可以解决问题。但是,您可以通过在内部使用set 来检查元素是否存在于其他列表中,从而获得更好的性能。例如:

          l1, l2 = [1,2,6,8], [2,3,5,8]
          s2 = set(l2)  # Type-cast `l2` to `set`
          
          l3 = [x for x in l1 if x not in s2]
                                       #   ^ Doing membership checking on `set` s2
          

          如果您想知道为什么会员检查速度更快,setlist 相比,请阅读:What makes sets faster than lists?


          使用 filter()lambda 表达式

          这是另一个使用filter()lambda 表达式替代方案。在这里添加仅供参考,但性能效率不高:

          >>> l1 = [1,2,6,8]
          >>> l2 = set([2,3,5,8])
          
          #     v  `filter` returns the a iterator object. Here I'm type-casting 
          #     v  it to `list` in order to display the resultant value
          >>> list(filter(lambda x: x not in l2, l1))
          [1, 6]
          

          【讨论】:

            猜你喜欢
            • 2022-11-13
            • 2022-01-24
            • 2015-04-24
            • 2015-09-20
            • 2017-09-21
            • 1970-01-01
            • 2011-01-31
            • 2019-10-06
            相关资源
            最近更新 更多