在Python中从序列中删除项目的优雅方式?
当我用Python编写代码时,我经常需要根据一些标准从列表或其他序列types中删除项目。 我还没有find一个优雅高效的解决scheme,因为从当前正在迭代的列表中删除项目是不好的。 例如,你不能这样做:
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
这是不够的,相当丑陋的,可能是越野车(它如何处理多个“约翰·史密斯”的条目?)。 有没有人有一个更优雅的解决scheme,或者至less更有效的?
如何与字典一起工作?
两个简单的方法来完成过滤:
-
使用
filter
:names = filter(lambda name: name[-5:] != "Smith", names)
-
使用列表parsing:
names = [name for name in names if name[-5:] != "Smith"]
请注意,这两种情况下,谓词函数评估的值保持为True
,因此您必须将逻辑颠倒过来(即,您要说“保留没有姓Smith的人”,而不是“删除拥有最后一个名字的人名字史密斯“)。
编辑滑稽…两个人分别发布了我build议我发布我的两个答案。
您也可以在列表中向后迭代:
for name in reversed(names): if name[-5:] == 'Smith': names.remove(name)
这有一个好处,它不会创build一个新的列表(如filter
或列表理解),并使用迭代器而不是列表副本(如[:]
)。
请注意,尽pipe在向后迭代时删除元素是安全的,但插入它们有点棘手。
显而易见的答案是约翰和其他人给出的答案,即:
>>> names = [name for name in names if name[-5:] != "Smith"] # <-- slower
但是,这有一个缺点,它创build一个新的列表对象,而不是重用原始对象。 我做了一些分析和实验,我提出的最有效的方法是:
>>> names[:] = (name for name in names if name[-5:] != "Smith") # <-- faster
分配给“名称[:]”基本上是指“用下面的值replace名称列表的内容”。 它不同于分配名称,因为它不会创build一个新的列表对象。 赋值的右边是一个生成器expression式(注意使用圆括号而不是方括号)。 这将导致Python遍历列表。
一些快速分析表明,这比列表理解方法快大约30%,比过滤方法快大约40%。
警告 :虽然这个解决scheme比显而易见的解决scheme更快,但它更加模糊,并依赖于更高级的Python技术。 如果你真的使用它,我build议附上评论。 如果你真的关心这个特定操作的性能(无论如何,这个速度相当快),这可能是唯一值得使用的。 (在我使用这个的情况下,我正在进行A *波束search,并使用它来从search波束中删除search点。)
使用列表理解
list = [x for x in list if x[-5:] != "smith"]
有时,过滤(使用filter或列表理解)不起作用。 当一些其他对象持有对正在修改的列表的引用并且您需要修改该列表时,会发生这种情况。
for name in names[:]: if name[-5:] == 'Smith': names.remove(name)
与原始代码唯一的区别是在for循环中使用了names[:]
而不是names
。 通过这种方式,代码遍历列表的一个(浅)副本,并且删除按预期工作。 由于列表复制很浅,所以速度很快。
filter将是非常棒的。 简单的例子:
names = ['mike', 'dave', 'jim'] filter(lambda x: x != 'mike', names) ['dave', 'jim']
编辑: Corey的列表理解也很棒。
names = filter(lambda x: x[-5:] != "Smith", names);
这两个解决scheme, 过滤和理解需要build立一个新的名单。 我不太了解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
无论如何,对于短名单,我坚持前面提出的两个解决scheme之一。
要回答关于使用字典的问题,您应该注意Python 3.0将包含字典理解 :
>>> {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'])
如果列表应该被就地过滤,并且列表大小相当大,那么以前的答案中提到的基于list.remove()的algorithm可能是不适合的,因为它们的计算复杂度是O(n ^ 2) 。 在这种情况下,你可以使用下面的非pythonic函数:
def filter_inplace(func, original_list): """ Filters the original_list in-place. Removes elements from the original_list for which func() returns False. Algrithm's computational complexity is O(N), where N is the size of the original_list. """ # Compact the list in-place. new_list_size = 0 for item in original_list: if func(item): original_list[new_list_size] = item new_list_size += 1 # Remove trailing items from the list. tail_size = len(original_list) - new_list_size while tail_size: original_list.pop() tail_size -= 1 a = [1, 2, 3, 4, 5, 6, 7] # Remove even numbers from a in-place. filter_inplace(lambda x: x & 1, a) # Prints [1, 3, 5, 7] print a
编辑:其实,解决scheme在https://stackoverflow.com/a/4639748/274937是优于我的解决scheme。; 它更pythonic和工作更快。 所以,这是一个新的filter_inplace()实现:
def filter_inplace(func, original_list): """ Filters the original_list inplace. Removes elements from the original_list for which function returns False. Algrithm's computational complexity is O(N), where N is the size of the original_list. """ original_list[:] = [item for item in original_list if func(item)]
filter和列表parsing对你的例子是可以的,但是它们有一些问题:
- 他们复制你的列表并返回新列表,而当原始列表非常大时,这将是低效的
- 当select条件的标准(在你的情况下,如果name [-5:== =='Smith'))更复杂或者有几个条件,它们可能会非常麻烦。
即使我们可以认同它很丑,你的原始解决scheme实际上对于非常大的列表也是更有效的。 但是如果你担心可以有多个“约翰·史密斯”,可以通过删除基于位置而不是价值来修复:
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
我们不能在不考虑列表大小的情况下select一个解决scheme,但是对于大型列表,我宁愿使用2-pass解决scheme而不是filter或列表parsing
在一组的情况下。
toRemove = set([]) for item in mySet: if item is unwelcome: toRemove.add(item) mySets = mySet - toRemove
这里是我的filter_inplace
实现,可以用来过滤从列表中的项目,我find这个页面之前独立我自己。 这与PabloG发布的算法是一样的,只是更通用一些,所以你可以使用它来过滤列表,也可以根据compareFunc从列表中移除,如果reverse被设置为True
; 如果你愿意的话,可以使用一种反转filter。
def filter_inplace(conditionFunc, list, reversed=False): index = 0 while index < len(list): item = list[index] shouldRemove = not conditionFunc(item) if reversed: shouldRemove = not shouldRemove if shouldRemove: list.remove(item) else: index += 1
那么,这显然是你正在使用的数据结构的问题。 例如,使用散列表。 有些实现支持每个键的多个条目,所以可以closures最新的元素,或者删除所有的元素。
但是,这是,你要find的解决scheme是,优雅通过不同的数据结构,而不是algorithm。 也许你可以做得更好,如果它是sorting,或者什么的,但在列表上迭代是你唯一的方法。
编辑:一个人意识到他要求“效率”…所有这些build议的方法只是迭代列表,这是他所build议的。