在Python中从序列中删除项目的优雅方法?
当我用Python编写代码时,经常需要根据某些条件从列表或者其他序列类型中删除项目。我还没有找到一种既优雅又高效的解决方案,因为从当前正在迭代的列表中删除项目是很糟糕的。例如,我们不能执行以下操作:
for name in names: if name[-5:] == 'Smith': names.remove(name)
我通常最终会做这样的事情:
toremove = [] for name in names: if name[-5:] == 'Smith': toremove.append(name) for name in toremove: names.remove(name) del toremove
这是效率低下,相当丑陋甚至可能是错误的(它如何处理多个" John Smith"条目?)。有没有人有一个更优雅的解决方案,或者至少是一个更高效的解决方案?
与字典配合使用的那一个怎么样?
解决方案
回答
好吧,这显然是我们正在使用的数据结构的问题。例如,使用哈希表。某些实现每个键支持多个条目,因此可以将最新元素弹出,或者将其全部删除。
但这就是我们要找到的解决方案,它是通过不同的数据结构(而不是算法)带来的优雅。如果对它进行排序或者其他操作,也许可以做得更好,但是列表中的迭代是这里唯一的方法。
编辑:确实意识到他要求"效率" ...所有这些建议的方法都只是遍历列表,这与他的建议相同。
回答
names = filter(lambda x: x[-5:] != "Smith", names);
回答
过滤器对此将是很棒的。简单的例子:
names = ['mike', 'dave', 'jim'] filter(lambda x: x != 'mike', names) ['dave', 'jim']
编辑:Corey的列表理解也是很棒的。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
回答
使用列表理解
list = [x for x in list if x[-5:] != "smith"]
回答
仅完成过滤的两种简单方法是:
- 使用
filter
:names = filter(lambda name:name [-5:]!=" Smith",名称)
- 使用列表推导:`names = [如果name [-5:]则为name中的name,如果= [Smith"]]
请注意,这两种情况都将谓词函数求值的值保留为True,因此我们必须颠倒逻辑(即我们说"保留姓氏为史密斯的人"而不是"删除姓氏为史密斯的人"姓史密斯")。
编辑好笑...两个人分别张贴了我在发布我的同时提出的两个答案。
回答
过滤和理解这两个解决方案都需要建立一个新列表。我不太了解Python的内部知识,但我认为采用更传统(但不太优雅)的方法可能会更有效:
names = ['Jones', 'Vai', 'Smith', 'Perez'] item = 0 while item <> len(names): name = names [item] if name=='Smith': names.remove(name) else: item += 1 print names
无论如何,对于简短列表,我坚持使用先前提出的两种解决方案中的任何一种。
回答
对于示例,可以使用filter和list理解,但是有两个问题:
- 他们会复制列表,然后返回新列表,当原始列表很大时,效率会很低
- 他们可真是繁琐时的标准挑选的物品(你的情况,如果name [-5:] =='史密斯)是比较复杂的,或者有几个条件。
原始解决方案实际上对于大型列表更有效,即使我们可以接受它比较丑陋。但是,如果我们担心可以有多个"约翰·史密斯",可以通过根据排名而不是根据价值进行删除来解决:
names = ['Jones', 'Vai', 'Smith', 'Perez', 'Smith'] toremove = [] for pos, name in enumerate(names): if name[-5:] == 'Smith': toremove.append(pos) for pos in sorted(toremove, reverse=True): del(names[pos]) print names
我们不能在不考虑列表大小的情况下选择解决方案,但是对于大型列表,我希望我们使用两遍解决方案,而不是使用过滤器或者列表理解功能
回答
有时过滤(使用过滤器或者列表推导)不起作用。当其他某个对象持有对我们正在修改的列表的引用,而我们需要在适当的位置修改列表时,就会发生这种情况。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。.########## $!#$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
for name in names[:]: if name[-5:] == 'Smith': names.remove(name)
与原始代码的唯一区别是在for循环中使用了"名称[:]"而不是"名称"。这样,代码将在列表的(浅)副本上进行迭代,删除操作将按预期进行。由于列表复制较浅,因此相当快。
回答
要回答有关使用字典的问题,我们应该注意Python 3.0将包括dict理解:
>>> {i : chr(65+i) for i in range(4)}
同时,我们可以通过以下方式进行准字典理解:
>>> dict([(i, chr(65+i)) for i in range(4)])
或者作为更直接的答案:
dict([(key, name) for key, name in some_dictionary.iteritems if name[-5:] != 'Smith'])
回答
我们还可以向后遍历列表:
for name in reversed(names): if name[-5:] == 'Smith': names.remove(name)
这样做的好处是它不会创建新列表(例如filter
或者列表理解),而是使用迭代器代替列表副本(例如:)。
请注意,尽管在向后迭代的同时删除元素是安全的,但插入元素有些麻烦。
回答
在一套的情况下。
toRemove = set([]) for item in mySet: if item is unwelcome: toRemove.add(item) mySets = mySet - toRemove