假设我有两个表,l1和l2。我想执行l1 - l2,返回l1中不在l2中的所有元素。
我可以想出一个简单的循环方法来做这个,但那真的很低效。python式的高效方法是什么?
举个例子,如果l1 = [1,2,6,8], l2 = [2,3,5,8], l1 - l2应该返回[1,6]
假设我有两个表,l1和l2。我想执行l1 - l2,返回l1中不在l2中的所有元素。
我可以想出一个简单的循环方法来做这个,但那真的很低效。python式的高效方法是什么?
举个例子,如果l1 = [1,2,6,8], l2 = [2,3,5,8], l1 - l2应该返回[1,6]
当前回答
使用filterfalse而不使用lambda-expression
When using functions like filter or filterfalse and similar from itertools you can usually save performance by avoiding lambda-expressions and using already existing functions. Instances of list and set defines a __contains__-method to use for containment checks. The in-operator calls this method under the hood, so using x in l2 can be replaced by l2.__contains__(x). Usually this replacement is not really prettier but in this specific case it allows us to gain better performance than using a lambda-expression, when used in combination with filterfalse:
>>> from itertools import filterfalse
>>> l1 = [1, 2, 6, 8]
>>> l2 = [2, 3, 5, 8]
>>> list(filterfalse(l2.__contains__, l1))
[1, 6]
Filterfalse创建一个迭代器,该迭代器生成的所有元素在用作12.2 .__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
其他回答
Python有一个称为列表推导式的语言特性,它非常适合使这类事情变得极其简单。下面的语句完全是你想要的,并将结果存储在l3中:
l3 = [x for x in l1 if x not in l2]
L3将包含[1,6]。
一种方法是使用集合:
>>> set([1,2,6,8]) - set([2,3,5,8])
set([1, 6])
但是请注意,集合不会保留元素的顺序,并且会删除任何重复的元素。元素也需要是可哈希的。如果这些限制是可以容忍的,那么这通常是最简单和性能最高的选项。
使用Python set类型。这是最Pythonic的。:)
此外,由于它是原生的,它也应该是最优化的方法。
See:
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
使用set.difference ():
你可以使用set.difference()来获取新的set,其中包含集合中不存在于其他集合中的元素。即set(A).difference(B)将返回包含A中存在而B中不存在的项的set。例如:
>>> set([1,2,6,8]).difference([2,3,5,8])
{1, 6}
它是Arkku的答案中提到的一种函数方法(它使用算术减法-运算符来获得集差)。
因为集合是无序的,你将失去初始列表中元素的顺序。(如果你想保持元素的顺序,请继续阅读下一节)
使用列表理解和基于集的查找
如果你想保持从初始列表的顺序,那么甜甜圈的基于列表理解的答案将做的技巧。但是,通过在内部使用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的成员资格检查更快,请阅读这篇文章:是什么使得set比list更快?
使用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]
如果你想要那种行为,集合方法是最好的。如果您不想删除列表l1中仅在l2中存在过一次的元素的所有实例,那么这些set操作将导致错误的结果。假设你在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]
注意,这将明显比设置操作慢,只在用例需要时使用它 如果你不想修改原来的列表-先创建一个列表的副本