从另一个列表中删除一个列表中出现的所有元素

2024-11-29 08:42:00
admin
原创
128
摘要:问题描述:假设我有两个列表,l1和l2。我想要执行l1 - l2,返回l1不在中的所有元素l2。我可以想到一种简单的循环方法来做到这一点,但这样做效率真的很低。 有什么 Python 式且有效的方法可以做到这一点?例如,如果我有l1 = [1,2,6,8] and l2 = [2,3,5,8],l1 - l2...

问题描述:

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

我可以想到一种简单的循环方法来做到这一点,但这样做效率真的很低。 有什么 Python 式且有效的方法可以做到这一点?

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


解决方案 1:

Python 有一个称为列表推导的语言特性,非常适合使这类事情变得非常容易。以下语句完全按照您的要求执行,并将结果存储在l3

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

l3将包含[1, 6]

解决方案 2:

一种方法是使用集合:

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

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

解决方案 3:

性能比较

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

Python 3.9.1

答案按表现顺序列出:

  1. Arkku 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
  1. Moinuddin Quadri 的 使用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
  1. Moinuddin Quadri 的 基于查找的列表理解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
  1. Donut 对普通列表的列表理解- (每个循环 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
  1. Daniel Pryden 的 生成器表达式set基于查找并类型转换为list- (每个循环 583 纳秒)list :根据 OP 的要求,明确类型转换为列表以获取最终对象。如果将生成器表达式替换为列表推导,它将变得与Moinuddin Quadri 的基于查找的列表推导相同set

 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
  1. Moinuddin Quadri 的 使用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
  1. Akshay Hazari 使用functools.reduce+filter - 的组合(每循环 3.36 微秒):明确类型转换为list从 Python 3.x 开始返回迭代器。此外,我们需要导入在 Python 3.x 中functools使用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 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
  1. Moinuddin Quadri 的 使用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
  1. Moinuddin Quadri 的 基于查找的列表理解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
  1. Donut 对普通列表的列表理解- (每个循环 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
  1. Moinuddin Quadri 的 使用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
  1. Daniel Pryden 的 生成器表达式基于set查找并类型转换为list- (每个循环 0.964)list :根据 OP 的要求,明确类型转换为列表以获取最终对象。如果将生成器表达式替换为列表推导,它将变得与Moinuddin Quadri 的基于查找的列表推导相同set

 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
  1. Akshay Hazari 使用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

解决方案 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

解决方案 6:

使用 集合推导式{x for x in l2} 或 set(l2) 来获取集合,然后使用列表推导式来获取列表

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}
l2set = set(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    

解决方案 7:

替代解决方案:

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

解决方案 8:

使用set.difference()

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

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

这是Arkku 的答案中提到的获取set差异的一种功能方法(使用算术减法运算符来计算集合差异)-

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

使用基于查找的列表理解set

如果您想保持初始列表的顺序,那么Donut 的基于列表理解的答案就可以了。但是,您可以通过内部使用来检查元素是否存在于其他列表中,从而从接受的答案中获得更好的性能。例如: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

如果您有兴趣了解为什么set与相比,成员资格检查更快list,请阅读:是什么让集合比列表更快?


使用filter()lambda表达式

这是使用lambda 表达式的另一种替代方法filter()。在这里添加它只是为了参考,但它的性能效率不高:

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

解决方案 9:

filterfalse 使用lambda 表达式

filter当使用类似或filterfalse和 的函数时itertools,通常可以通过避免使用lambda-表达式并使用已经存在的函数来节省性能。list和 的实例set定义了一个__contains__用于包含检查的 -方法。in-操作符在后台调用此方法,因此x in l2可以使用 替换。 通常这种替换并不是更漂亮,但在这种特定情况下,当与 结合使用时,l2.__contains__(x)它允许我们获得比使用 -表达式更好的性能:lambda`filterfalse`

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

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

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

解决方案 10:

如果您想要这种行为,则集合方法是最好的。如果您不想删除列表 l1 中在 l2 中仅出现一次的元素的所有实例,则这些集合操作将导致不正确的结果。假设您在 l1 中有重复元素,甚至在 l2 中也可能有重复元素,并且想要两个列表 l1 - l2 的实际差异,同时保持剩余元素的顺序:

l1 = [1, 2, 3, 4, 5, 5, 6, 5, 5, 2]
l2 = [1, 2, 2, 5]
_ = [l1.remove(item) for item in l2 if item in l1] # discard return value
print(l1) # [3, 4, 5, 6, 5, 5]
  1. 请注意,这将比设置操作慢得多,仅当您的用例需要它时才使用它

  2. 如果您不想修改原始列表 - 请先创建列表的副本

解决方案 11:

Python 3.8 上的集合与列表推导式基准测试

(与 Moinuddin Quadri 的基准相加)

tldr:使用Arkku 的设定解决方案,相比之下它甚至比承诺的还要快!

根据列表检查现有文件

在我的示例中,我发现在实际应用中根据列表检查现有文件名时,使用Arkku 的集合解决方案比使用Python 列表理解要快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 毫秒

解决方案 12:

尝试一下:

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)

解决方案 13:

利用字典的有序属性来维持顺序(Python 3.7+)

注意:Python 3.6 中的参考实现dicts按插入顺序维护键,但规范不保证这一点。对于 3.7 及更高版本,添加了此保证。

函数的键dict是一种set;重复项会被隐式过滤掉,并且由于哈希处理,查找效率会更高。因此,我们可以通过使用l1作为键构建字典,然后删除 中出现的任何键来实现“集合差异” l2。这可以保持顺序并使用快速算法,但会产生相当多的常数因子开销。

d = dict.fromkeys(l1)
for i in l2:
    try:
        del d[i]
    except KeyError:
        pass
l3 = list(d.keys())
相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1068  
  IPD(Integrated Product Development,集成产品开发)流程是一种广泛应用于高科技和制造业的产品开发方法论。它通过跨职能团队的紧密协作,将产品开发周期缩短,同时提高产品质量和市场成功率。在IPD流程中,CDCP(Concept Decision Checkpoint,概念决策检查点)是一个关...
IPD培训课程   6  
  研发IPD(集成产品开发)流程作为一种系统化的产品开发方法,已经在许多行业中得到广泛应用。它不仅能够提升产品开发的效率和质量,还能够通过优化流程和资源分配,显著提高客户满意度。客户满意度是企业长期成功的关键因素之一,而IPD流程通过其独特的结构和机制,能够确保产品从概念到市场交付的每个环节都围绕客户需求展开。本文将深入...
IPD流程   6  
  IPD(Integrated Product Development,集成产品开发)流程是一种以跨职能团队协作为核心的产品开发方法,旨在通过优化资源分配、提高沟通效率以及减少返工,从而缩短项目周期并提升产品质量。随着企业对产品上市速度的要求越来越高,IPD流程的应用价值愈发凸显。通过整合产品开发过程中的各个环节,IPD...
IPD项目管理咨询   6  
  跨部门沟通是企业运营中不可或缺的一环,尤其在复杂的产品开发过程中,不同部门之间的协作效率直接影响项目的成败。集成产品开发(IPD)作为一种系统化的项目管理方法,旨在通过优化流程和增强团队协作来提升产品开发的效率和质量。然而,跨部门沟通的复杂性往往成为IPD实施中的一大挑战。部门之间的目标差异、信息不对称以及沟通渠道不畅...
IPD是什么意思   6  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用