从一个列表移除所有在另一个列表中出现的元素
假设我有两个列表,l1
和 l2
。我想要做的是 l1 - l2
,也就是找出所有在 l1
中但不在 l2
中的元素。
我可以想到用一个简单的循环来实现这个功能,但这样做效率会很低。有没有更好、更高效的方法来做到这一点呢?
举个例子,如果我有 l1 = [1,2,6,8] 和 l2 = [2,3,5,8]
,那么 l1 - l2
应该返回 [1,6]
。
13 个回答
性能比较
这里比较了所有答案在 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 开始它返回迭代器。还需要导入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
以下是按性能排序的答案:
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
一种方法是使用集合:
>>> set([1,2,6,8]) - set([2,3,5,8])
set([1, 6])
不过要注意,集合不保留元素的顺序,而且会自动去掉重复的元素。此外,集合里的元素必须是可以被哈希的。如果这些限制可以接受,那么使用集合通常是最简单且性能最高的选择。
Python 有一个叫做 列表推导式 的功能,非常适合用来轻松处理这种情况。下面这行代码正好能实现你想要的效果,并把结果存储在 l3
里:
l3 = [x for x in l1 if x not in l2]
l3
的内容将会是 [1, 6]
。