假设我有两个列表l1
和l2
。我想执行l1 - l2
,它会返回l1
中l2
以外的所有元素。
我可以想到一个简单的循环方法来做到这一点,但这将是非常低效的。什么是pythonic和有效的方法呢?
例如,如果我有l1 = [1,2,6,8] and l2 = [2,3,5,8]
,则l1 - l2
应该返回[1,6]
答案 0 :(得分:356)
Python有一个名为List Comprehensions的语言功能,非常适合使这类事情变得非常简单。以下语句完全符合您的要求,并将结果存储在l3
:
l3 = [x for x in l1 if x not in l2]
l3
将包含[1, 6]
。
希望这有帮助!
答案 1 :(得分:108)
一种方法是使用集合:
>>> set([1,2,6,8]) - set([2,3,5,8])
set([1, 6])
答案 2 :(得分:31)
扩展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))
答案 3 :(得分:28)
使用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
答案 4 :(得分:25)
作为替代方案,您也可以使用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]
效果比较
我在这里比较这里提到的所有答案的表现。正如预期的那样,基于Arkku's set
的操作最快。
Arkku's Set Difference - 首先(每个循环0.124次使用)
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.124 usec per loop
Daniel Pryden's List Comprehension with set
lookup - 第二个(每个循环0.302次使用)
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.302 usec per loop
Donut's List Comprehension on plain list - 第三个(每个循环0.552次使用)
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.552 usec per loop
Moinuddin Quadri's using filter
- 第四个(每个循环0.972次使用)
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.972 usec per loop
Akshay Hazari's using combination of reduce
+ filter
- 第五个(每个循环3.97次使用)
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: 3.97 usec per loop
PS: set
不维护订单并从列表中删除重复的元素。因此,如果您需要其中任何一个,请不要使用设置差异。
答案 5 :(得分:7)
替代解决方案:
reduce(lambda x,y : filter(lambda z: z!=y,x) ,[2,3,5,8],[1,2,6,8])
答案 6 :(得分:2)
使用Set Comprehensions {x for l2中的x}或set(l2)进行设置,然后使用List Comprehensions获取列表
if ( $str =~ /xtop\.xnext/g ) {
$str =~ s/\G[^.]*\K\./-/g;
}
基准测试代码:
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))
答案 7 :(得分:1)
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
答案 8 :(得分:0)
(加上 Moinuddin Quadri 的基准)
tldr:使用Arkku's set solution,相比之下,它甚至比承诺的还要快!
在我的示例中,我发现使用 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 毫秒
答案 9 :(得分:0)
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
运算符进行集合差)中提到的获得-
差异的函数方法 em>.
由于 sets 是无序的,您将失去初始列表中元素的顺序。 (如果你想保持元素的顺序,请继续阅读下一节)
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
如果您想知道为什么成员检查速度比 set
快 list
,请阅读: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]
答案 10 :(得分:0)
试试这个:
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)