用Python從序列中刪除項的優雅方式?(復制)

[英]Elegant way to remove items from sequence in Python? [duplicate]


This question already has an answer here:

這個問題已經有了答案:

When I am writing code in Python, I often need to remove items from a list or other sequence type based on some criteria. I haven't found a solution that is elegant and efficient, as removing items from a list you are currently iterating through is bad. For example, you can't do this:

在使用Python編寫代碼時,我經常需要根據某些條件從列表或其他序列類型中刪除項目。我還沒有找到一種優雅、高效的解決方案,因為從當前迭代的列表中刪除條目是不好的。例如,你不能這樣做:

for name in names:
    if name[-5:] == 'Smith':
        names.remove(name)

I usually end up doing something like this:

我通常會這樣做:

toremove = []
for name in names:
    if name[-5:] == 'Smith':
        toremove.append(name)
for name in toremove:
    names.remove(name)
del toremove

This is innefficient, fairly ugly and possibly buggy (how does it handle multiple 'John Smith' entries?). Does anyone have a more elegant solution, or at least a more efficient one?

這是一種非常復雜的、相當丑陋的、可能有bug的(它如何處理多個“John Smith”條目?)有人有更優雅的解決方案嗎,或者至少有更高效的方案嗎?

How about one that works with dictionaries?

那使用字典的呢?

14 个解决方案

#1


53  

Two easy ways to accomplish just the filtering are:

實現過濾的兩種簡單方法是:

  1. Using filter:

    使用過濾器:

    names = filter(lambda name: name[-5:] != "Smith", names)

    name = filter(lambda name: name[-5:] != "Smith", names)

  2. Using list comprehensions:

    使用列表理解:

    names = [name for name in names if name[-5:] != "Smith"]

    名稱=[名稱中名稱的名稱,如果名稱為[-5:]!= "史密斯"]

Note that both cases keep the values for which the predicate function evaluates to True, so you have to reverse the logic (i.e. you say "keep the people who do not have the last name Smith" instead of "remove the people who have the last name Smith").

注意,這兩種情況都將謂詞函數計算的值保持為True,因此您必須反轉邏輯(例如,您說“保留沒有姓Smith的人”,而不是“刪除姓Smith的人”)。

Edit Funny... two people individually posted both of the answers I suggested as I was posting mine.

編輯有趣…兩個人分別把我建議的兩個答案貼在網上。

#2


37  

You can also iterate backwards over the list:

你也可以對列表進行反向迭代:

for name in reversed(names):
    if name[-5:] == 'Smith':
        names.remove(name)

This has the advantage that it does not create a new list (like filter or a list comprehension) and uses an iterator instead of a list copy (like [:]).

這樣做的好處是,它不創建新的列表(比如過濾器或列表理解),而是使用迭代器而不是列表拷貝(比如[:])。

Note that although removing elements while iterating backwards is safe, inserting them is somewhat trickier.

請注意,雖然向后迭代時刪除元素是安全的,但是插入元素有點麻煩。

#3


28  

The obvious answer is the one that John and a couple other people gave, namely:

最明顯的答案是約翰和其他一些人給出的答案,即:

>>> names = [name for name in names if name[-5:] != "Smith"]       # <-- slower

But that has the disadvantage that it creates a new list object, rather than reusing the original object. I did some profiling and experimentation, and the most efficient method I came up with is:

但這樣做的缺點是,它創建了一個新的list對象,而不是重用原始對象。我做了一些分析和實驗,我想到的最有效的方法是:

>>> names[:] = (name for name in names if name[-5:] != "Smith")    # <-- faster

Assigning to "names[:]" basically means "replace the contents of the names list with the following value". It's different from just assigning to names, in that it doesn't create a new list object. The right hand side of the assignment is a generator expression (note the use of parentheses rather than square brackets). This will cause Python to iterate across the list.

賦給“names[:]”的基本意思是“用以下值替換名稱列表的內容”。它不同於僅僅為名稱賦值,因為它不創建一個新的列表對象。賦值的右邊是一個生成器表達式(注意使用圓括號而不是方括號)。這將導致Python遍歷列表。

Some quick profiling suggests that this is about 30% faster than the list comprehension approach, and about 40% faster than the filter approach.

一些快速分析表明,這比列表理解方法快30%,比過濾器方法快40%。

Caveat: while this solution is faster than the obvious solution, it is more obscure, and relies on more advanced Python techniques. If you do use it, I recommend accompanying it with a comment. It's probably only worth using in cases where you really care about the performance of this particular operation (which is pretty fast no matter what). (In the case where I used this, I was doing A* beam search, and used this to remove search points from the search beam.)

注意:雖然這個解決方案比顯而易見的解決方案要快,但是它更加模糊,並且依賴於更高級的Python技術。如果您確實使用了它,我建議您在它后面加上一條注釋。它可能只在您真正關心這個特定操作的性能的情況下才值得使用(無論如何它都非常快)。(在我使用這個的情況下,我做了一個* beam搜索,並用它從搜索光束中移除搜索點。)

#4


10  

Using a list comprehension

使用列表理解

list = [x for x in list if x[-5:] != "smith"]

#5


4  

There are times when filtering (either using filter or a list comprehension) doesn't work. This happens when some other object is holding a reference to the list you're modifying and you need to modify the list in place.

有時過濾(使用過濾器或列表理解)不起作用。當其他對象持有要修改的列表的引用並需要適當地修改列表時,就會發生這種情況。

for name in names[:]:
    if name[-5:] == 'Smith':
        names.remove(name)

The only difference from the original code is the use of names[:] instead of names in the for loop. That way the code iterates over a (shallow) copy of the list and the removals work as expected. Since the list copying is shallow, it's fairly quick.

與原始代碼的惟一區別是在for循環中使用名稱[:]而不是名稱。這樣,代碼在列表的(淺層)副本上迭代,刪除操作按預期進行。由於列表拷貝很淺,所以速度很快。

#6


3  

filter would be awesome for this. Simple example:

濾鏡會非常棒。簡單的例子:

names = ['mike', 'dave', 'jim']
filter(lambda x: x != 'mike', names)
['dave', 'jim']

Edit: Corey's list comprehension is awesome too.

編輯:科里的列表理解能力也很棒。

#7


2  

names = filter(lambda x: x[-5:] != "Smith", names);

#8


2  

Both solutions, filter and comprehension requires building a new list. I don't know enough of the Python internals to be sure, but I think that a more traditional (but less elegant) approach could be more efficient:

解決方案、篩選和理解都需要構建一個新的列表。我不太了解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

Anyway, for short lists, I stick with either of the two solutions proposed earlier.

總之,對於簡短的列表,我堅持前面提出的兩個解決方案中的任何一個。

#9


2  

To answer your question about working with dictionaries, you should note that Python 3.0 will include dict comprehensions:

要回答關於使用字典的問題,您應該注意Python 3.0將包括字典的理解:

>>> {i : chr(65+i) for i in range(4)}

In the mean time, you can do a quasi-dict comprehension this way:

同時,你也可以用這種方式進行准詞典理解:

>>> dict([(i, chr(65+i)) for i in range(4)])

Or as a more direct answer:

或者更直接的回答:

dict([(key, name) for key, name in some_dictionary.iteritems if name[-5:] != 'Smith'])

#10


2  

If the list should be filtered in-place and the list size is quite big, then algorithms mentioned in the previous answers, which are based on list.remove(), may be unsuitable, because their computational complexity is O(n^2). In this case you can use the following no-so pythonic function:

如果列表應該過濾就地和列表大小相當大,那么算法在前面提到的答案,這是基於list.remove(),可能是不適合的,因為他們的計算復雜度是O(n ^ 2)。在這種情況下,您可以使用下面的no-so python函數:

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

Edit: Actually, the solution at https://stackoverflow.com/a/4639748/274937 is superior to mine solution. It is more pythonic and works faster. So, here is a new filter_inplace() implementation:

編輯:實際上,https://stackoverflow.com/a/4639748/274937的解決方案優於我的解決方案。它更符合python語言,運行速度更快。因此,這里有一個新的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)]

#11


1  

The filter and list comprehensions are ok for your example, but they have a couple of problems:

過濾器和列表理解對於您的示例來說是可以的,但是它們有幾個問題:

  • They make a copy of your list and return the new one, and that will be inefficient when the original list is really big
  • 他們復制你的列表並返回新的列表,當原始列表真的很大時,這將是低效的
  • They can be really cumbersome when the criteria to pick items (in your case, if name[-5:] == 'Smith') is more complicated, or has several conditions.
  • 當選擇項(在您的例子中,如果名稱[-5:]= 'Smith')的條件比較復雜或有幾個條件時,它們可能會非常麻煩。

Your original solution is actually more efficient for very big lists, even if we can agree it's uglier. But if you worry that you can have multiple 'John Smith', it can be fixed by deleting based on position and not on value:

你原來的解決方案實際上更有效率,即使我們同意它更丑。但如果你擔心你可能有多個“約翰·史密斯”,可以根據位置而不是價值進行刪除:

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

We can't pick a solution without considering the size of the list, but for big lists I would prefer your 2-pass solution instead of the filter or lists comprehensions

我們不能不考慮列表的大小就選擇一個解決方案,但是對於大列表,我更喜歡你的2-pass解決方案,而不是過濾器或列表理解

#12


1  

In the case of a set.

在集合的情況下。

toRemove = set([])  
for item in mySet:  
    if item is unwelcome:  
        toRemove.add(item)  
mySets = mySet - toRemove 

#13


1  

Here is my filter_inplace implementation that can be used to filter items from a list in-place, I came up with this on my own independently before finding this page. It is the same algorithm as what PabloG posted, just made more generic so you can use it to filter lists in place, it is also able to remove from the list based on the comparisonFunc if reversed is set True; a sort-of of reversed filter if you will.

這是我的filter_inplace實現,它可以用於從一個現成的列表中過濾項,在找到這個頁面之前,我自己獨立地提出了這個實現。它和PabloG發布的算法是一樣的,只是變得更一般了所以你可以用它來過濾適當的列表,它也可以從列表中刪除如果反向被設置為True;如果你願意,一種反向過濾器。

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

#14


-2  

Well, this is clearly an issue with the data structure you are using. Use a hashtable for example. Some implementations support multiple entries per key, so one can either pop the newest element off, or remove all of them.

顯然,這是您正在使用的數據結構的問題。例如使用hashtable。有些實現支持每個鍵有多個條目,因此可以取出最新的元素,或者刪除所有的元素。

But this is, and what you're going to find the solution is, elegance through a different data structure, not algorithm. Maybe you can do better if it's sorted, or something, but iteration on a list is your only method here.

但這是,你要找到的解決方案是,通過不同的數據結構優雅,而不是算法。也許你可以做得更好,如果它是排序的,或者別的什么,但是在列表上迭代是你唯一的方法。

edit: one does realize he asked for 'efficiency'... all these suggested methods just iterate over the list, which is the same as what he suggested.

編輯:有人意識到他要求的是“效率”……所有這些建議的方法都只是遍歷列表,這與他的建議相同。


注意!

本站翻译的文章,版权归属于本站,未经许可禁止转摘,转摘请注明本文地址:https://www.itdaan.com/blog/2008/08/20/729bf302e182b5aed3a066147813d032.html



 
粤ICP备14056181号  © 2014-2020 ITdaan.com