Python - 从列表中删除项目

Python - 从列表中删除项目

问题描述:

# I have 3 lists: 
L1 = [1, 2, 3, 4, 5, 6, 7, 8, 9] 
L2 = [4, 7, 8] 
L3 = [5, 2, 9] 
# I want to create another that is L1 minus L2's memebers and L3's memebers, so: 
L4 = (L1 - L2) - L3 # Of course this isn't going to work 

我想知道,什么是“正确”的方式来做到这一点。我可以通过很多不同的方式做到这一点,但是Python的风格指南认为,应该只有一种正确的方式来完成每件事情。我从来不知道这是什么。Python - 从列表中删除项目

+3

没有一个正确的方法来做这件事,直到你决定你是否照顾或不关心重复和订购。可能是某种列表理解或根据你所关心的设定工作。 – istruble 2010-10-16 05:40:20

+1

另外,可以假设列表中的所有项目都会一直可用?如果不是,或者有时不会,那将非常重要。 – martineau 2010-10-16 12:03:01

+1

你为什么不用套头?那么你的“算术”就可以工作。 – poke 2010-10-16 15:41:14

这里有一些尝试:

L4 = [ n for n in L1 if (n not in L2) and (n not in L3) ] # parens for clarity 

tmpset = set(L2 + L3) 
L4 = [ n for n in L1 if n not in tmpset ] 

现在我有时间去思考,我意识到L2 + L3事情创建了一个临时列表,立即被扔掉。因此,一个更好的方法是:

tmpset = set(L2) 
tmpset.update(L3) 
L4 = [ n for n in L1 if n not in tmpset ] 

更新:我看到一些过分的要求被抛向四周约的表现,我想断言,我的解决方案已经尽可能地快。创建中间结果,无论它们是中间列表还是中间迭代器,都必须重复调用,总是比单独给出L2L3以使该集合直接迭代,就像我在这里完成的那样。

$ python -m timeit \ 
    -s 'L1=range(300);L2=range(30,70,2);L3=range(120,220,2)' \ 
    'ts = set(L2); ts.update(L3); L4 = [ n for n in L1 if n not in ts ]' 
10000 loops, best of 3: 39.7 usec per loop 

所有其他替代品(我能想到的)必然会比这慢。这样的循环自己,例如,而不是让set()构造做他们,增加了费用:

$ python -m timeit \ 
    -s 'L1=range(300);L2=range(30,70,2);L3=range(120,220,2)' \ 
    'unwanted = frozenset(item for lst in (L2, L3) for item in lst); L4 = [ n for n in L1 if n not in unwanted ]' 
10000 loops, best of 3: 46.4 usec per loop 

使用迭代器,都将它们涉及与状态保存回调,显然会更贵:

$ python -m timeit \ 
    -s 'L1=range(300);L2=range(30,70,2);L3=range(120,220,2);from itertools import ifilterfalse, chain' \ 
    'L4 = list(ifilterfalse(frozenset(chain(L2, L3)).__contains__, L1))' 
10000 loops, best of 3: 47.1 usec per loop 

所以我相信答案我给昨晚仍远和(为“远和”大于周围5μsec,明明值)是最好的,除非提问会有重复的L1和希望每次重复出现在其中一个其他列表中时,每次都会删除一次。

+0

通过从两个列表迭代器的链构建一个冻结集可能可以实现更多的性能。 – intuited 2010-10-16 04:44:29

+0

不,冻结集的速度与正常速度的速度完全相同,但通常需要更多的开销,因为您必须自己创建中间结果或循环,如果在这里您正在从几个输入迭代中构建它们。 – 2010-10-16 12:48:38

假设你的个人名单将不包含重复....使用SetDifference

L1 = [1, 2, 3, 4, 5, 6, 7, 8, 9] 
L2 = [4, 7, 8] 
L3 = [5, 2, 9] 
print(list(set(L1) - set(L2) - set(L3))) 
+2

这会失去命令。 – 2010-10-16 04:23:08

+1

是的,一个列表和一个集合的主要区别... – mepcotterell 2010-10-16 04:24:18

+1

如果订单/重复不是问题,这是最干净的选项,IMO – 2010-10-16 04:32:00

在列表中执行此类操作可能会很快妨碍您的程序性能。每次删除都会发生什么,列表操作会执行一个新的malloc &移动元素。如果您有非常大的列表或其他地方,这可能会很昂贵。所以我会建议这 -

我假设你的列表有独特的元素。否则,你需要在你的字典中保留一个具有重复值的列表。反正你提供的数据,在这里它是 -

方法1

d = dict() 
for x in L1: d[x] = True 

# Check if L2 data is in 'd' 
for x in L2: 
    if x in d: 
     d[x] = False 

for x in L3: 
    if x in d: 
     d[x] = False 

# Finally retrieve all keys with value as True. 
final_list = [x for x in d if d[x]] 

方法2 如果一切看起来像太多的代码。然后你可以尝试使用set。但是这样你的列表将会丢失所有重复的元素。

final_set = set.difference(set(L1),set(L2),set(L3)) 
final_list = list(final_set) 
+0

列表理解不会删除昂贵的操作。 – aaronasterling 2010-10-16 04:38:29

+0

#aaron是的我知道。我指的是圣地亚哥公布的解决方案。 – 2010-10-16 04:43:51

+1

嘿,你基本上使用字典作为一个集合。他们有一个完整的其他数据类型:http://docs.python.org/library/stdtypes.html#types-set – intuited 2010-10-16 04:48:07

这可能是比列表理解答案pythonesque少,但有一个更简洁的外观给它:

l1 = [ ... ] 
l2 = [ ... ] 

diff = list(l1) # this copies the list 
for element in l2: 
    diff.remove(element) 

这里的好处是,我们保护列表的顺序,如果有重复元素,我们在每次出现在l2时只删除一个元素。

+1

的讨论这是非常昂贵,相反,更多看起来比简单的理解复杂。 – aaronasterling 2010-10-16 04:37:57

+0

看起来有味道问题。我非常喜欢列表理解,我实际上倾向于过度使用它们,但我不认为“如果n不在......中,n在n中”对眼睛来说很好。无论如何,我承认,计算成本很高。 – slezica 2010-10-16 04:44:19

更新:::帖子中包含了与frozensets相比劣势集的错误指控。我坚持认为在这个实例中使用一个冷凝集合仍然是明智的,即使不需要散列集合本身,仅仅因为它在语义上更正确。虽然在实践中,我可能不会打扰多余的6个字符。我没有动力去浏览和编辑这篇文章,所以只是建议“指控”链接到一些不正确运行的测试。评论中散布了血淋淋的细节。 :::更新

由布兰登·克雷格罗德的代码posted第二块是相当不错,但他并没有对我的建议作出回应有关使用frozenset(当然,不是当我开始写这个,反正) ,我会继续并自己发布。

手头承诺的全部基础是检查一系列值(L1)中的每一个值是否都在另一组值中;该组值是L2L3的内容。在这个句子中使用“set”这个词是说:即使L2L3list s,我们并不关心他们的列表类属性,比如它们的值的顺序或者它们的值有多少包含。我们只关心集合(这里又是)它们共同包含的值。

如果该组值被存储为列表,则必须逐个检查列表元素,检查每个列表元素。这是相对耗时的,而且是不好的语义:再次,它是一组“值”,而不是一个列表。所以Python有这些整齐的集合类型,它们拥有一堆独特的值,并且可以快速告诉你是否有某个值。这与python的dict类型在查找关键字时的工作方式基本相同。

frozensets是集是可变的,这意味着它们可以在创建之后可以修改之间的差异。这两种类型的文档是here

由于我们需要创建的集合,存储在L2L3中的值的联合一旦创建就不会被修改,它在语义上适合使用不可变数据类型。这也有一些性能优势。那么,这是有道理的,它会有一些优势;否则,为什么Python将frozenset作为内建函数?

更新 ...

布兰登已经回答了这个问题:冰冻套真正的优势在于他们的不变性,使他们有可能是hashable,使他们能够字典键或其他组成员。

我跑比较用于创建和查找在相对大的(3000元素)冷冻并可变设定速度一些非正式定时测试;没有太大的区别。这与上面的链接冲突,但支持布兰登说他们是相同的,但在可变性方面。

... 更新

现在,因为frozensets是不可改变的,他们没有更新方法。布兰登使用set.update方法来避免创建并丢弃临时列表以创建集合;我将采取不同的方法。

items = (item for lst in (L2, L3) for item in lst) 

generator expression使得items一个迭代结束,连续的L2L3内容。不仅如此,它还可以在不创建完整列表的情况下完成 - 完整的中间对象。在生成器中使用嵌套for表达式有点令人困惑,但我设法通过记住它们的嵌套顺序与它们在编写实际for循环时的顺序相同,例如

def get_items(lists): 
    for lst in lists: 
     for item in lst: 
      yield item 

generator function等同于我们分配给items发电机表达。那么,除了它是一个参数化的函数定义,而不是直接赋值给一个变量。

无论如何,够离题了。与发电机有关的大事是他们实际上没有做任何事情。那么,至少不是马上:他们只是将工作设置在稍后完成,当时生成器表达式为迭代为。这正式被称为懒惰。我们将通过将items传递给frozenset函数来做到这一点(无论如何,我是这样做的),该函数遍历它并返回一个冷冻冷冻集。

unwanted = frozenset(items) 

其实你可以结合起来,最后两行,通过把发电机表达权的通话里面frozenset

unwanted = frozenset(item for lst in (L2, L3) for item in lst) 

只要这个整齐的语法技巧的工作由生成器表达式创建的iterator是您要调用的函数的唯一参数。否则,你必须把它写在它通常单独的一组括号中,就像你将一个元组作为参数传递给函数一样。现在

我们可以建立以同样的方式,布兰登做了一个新的列表,用list comprehension。这些使用相同的语法生成表达式,基本上做同样的事情,但他们都渴望,而不是(再次,这些都是实际的技术术语),所以他们马上在项目工作迭代和创建他们的名单。

L4 = [item for item in L1 if item not in unwanted] 

这等效于通过一个发电机表达式list,例如

L4 = list(item for item in L1 if item not in unwanted) 

但更习惯。

因此,这将创建列表L4,含有没有在任何L2L3L1的元素,保持他们在最初的顺序和他们的,有数量。


如果你只是想知道值在L1但不是在L2L3,它更容易:你刚才创建集:

L1_unique_values = set(L1) - unwanted 

你可以列个清单出来它,as does st0le,但这可能不是你想要的。如果你真的想要的设定值那些只在L1发现,你可能有一个很好的理由保持这种设置set,或者确实是一个frozenset

L1_unique_values = frozenset(L1) - unwanted 

... Annnnd现在完全不同的东西:

from itertools import ifilterfalse, chain 
L4 = list(ifilterfalse(frozenset(chain(L2, L3)).__contains__, L1)) 
+0

+1非常丰富。最近的增加(与itertools)是非常好的。我会说你已经在过滤列表中获得了博士学位,这是基于包含在一组列表中的。 – aaronasterling 2010-10-16 08:02:34

+0

@aaron:这是需要多年的学习,但这是值得的。 – intuited 2010-10-16 08:06:02

+0

我错过了什么,或者是你的生成器表达只是'itertools.chain'?如果是的话,就使用它(你可以保留生成器和生成器表达式的解释,但人们需要了解它们)。 – delnan 2010-10-16 12:21:09

我认为对于这样一个简单的问题,intuited的答案太长了,Python已经有了一个内置函数来将两个列表作为一个生成器链接起来。

的过程如下:

  1. 使用itertools.chain到链L2和L3,而无需创建一个占用内存的副本
  2. 创建从一组(在这种情况下,frozenset这样做,因为我们不在创建之后不会改变它)
  3. 使用列表理解过滤出L1中以及L2或L3中的元素。由于set/frozenset查找(x in someset)是O(1),这将非常快。

而现在的代码:

L1 = [1, 2, 3, 4, 5, 6, 7, 8, 9] 
L2 = [4, 7, 8] 
L3 = [5, 2, 9] 

from itertools import chain 
tmp = frozenset(chain(L2, L3)) 
L4 = [x for x in L1 if x not in tmp] # [1, 3, 6] 

这应该是最快,最简单和最占用内存的解决方案之一。

+0

这不是最快的;检查我的帖子中的测试。在集合和已经可迭代的列表之间放置迭代器会降低速度。 – 2010-10-16 19:37:31

+0

@Brandon Craig Rhodes:好吧,让我们说“最快的解决方案之一”。感谢您发布您的基准测试结果。 – AndiDog 2010-10-16 20:32:44

+0

的确 - 您的解决方案无疑是最快速的,当然也是这个问题值得关注的O(* n * log * m *)解决方案之一。我只是想确保程序员认识到迭代器不是精灵尘埃,它比在容器本身上循环更快;迭代器返回的每个项目都需要重新激活它的范围,并重新开始其代码,所以它们的好处不是免费的。 – 2010-10-16 21:34:02