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

时间:2010-11-18 02:45:50

标签: python list

假设我有两个列表l1l2。我想执行l1 - l2,它会返回l1l2以外的所有元素。

我可以想到一个简单的循环方法来做到这一点,但这将是非常低效的。什么是pythonic和有效的方法呢?

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

11 个答案:

答案 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 表达式

当使用 filterfilterfalse 等函数以及 itertools 中的类似函数时,您通常可以通过避免使用 lambda 表达式并使用现有函数来节省性能。 listset 的实例定义了用于包含检查的 __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)

Python 3.8 上的集合与列表理解基准

(加上 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

如果您想知道为什么成员检查速度比 setlist,请阅读: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)