从另一个列表中删除一个列表中出现的所有元素
- 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
应该返回[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.1和Python 2.7.16上的性能。
Python 3.9.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
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
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
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
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
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
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
答案按表现顺序列出:
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
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
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
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
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
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
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]
请注意,这将比设置操作慢得多,仅当您的用例需要它时才使用它
如果您不想修改原始列表 - 请先创建列表的副本
解决方案 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())
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 项目管理必备:盘点2024年13款好用的项目管理软件
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)