your programing

Python의 시퀀스에서 항목을 제거하는 우아한 방법?

lovepro 2021. 1. 5. 19:47
반응형

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'항목을 어떻게 처리합니까?). 누구든지 더 우아한 솔루션이 있습니까, 아니면 적어도 더 효율적인 솔루션이 있습니까?

사전과 함께 작동하는 것은 어떻습니까?


필터링 만 수행하는 두 가지 쉬운 방법은 다음과 같습니다.

  1. 사용 filter:

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

  2. 목록 이해력 사용 :

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

두 경우 모두 조건 자 함수가로 평가되는 값을 유지 True하므로 논리를 반대로해야합니다 (예 : "성이있는 사람 제거"대신 "Smith 성이없는 사람 유지"라고 말함). 스미스").

Edit Funny ... 두 사람이 제가 제 글을 올릴 때 제가 제안한 두 답변을 개별적으로 올렸습니다.


목록을 거꾸로 반복 할 수도 있습니다.

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

이것은 새로운 목록 (like filter또는 list comprehension)을 생성하지 않고 목록 사본 (like [:]) 대신 반복자를 사용 한다는 장점이 있습니다 .

역방향으로 반복하는 동안 요소를 제거하는 것이 안전하지만 삽입하는 것이 다소 까다 롭습니다.


분명한 대답은 John과 다른 두 사람이 준 것입니다.

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

그러나 그것은 원래 객체를 재사용하는 대신 새로운 목록 객체를 생성한다는 단점이 있습니다. 몇 가지 프로파일 링과 실험을 수행했으며 가장 효율적인 방법은 다음과 같습니다.

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

"names [:]"에 할당하는 것은 기본적으로 "이름 목록의 내용을 다음 값으로 대체"를 의미합니다. 새 목록 개체를 만들지 않는다는 점에서 이름에 할당하는 것과 다릅니다. 할당의 오른쪽은 생성기 표현식입니다 (대괄호 대신 괄호 사용에 유의하십시오). 이렇게하면 Python이 목록 전체를 반복합니다.

일부 빠른 프로파일 링에 따르면 이것이 목록 이해 방식보다 약 30 % 빠르며 필터 방식보다 약 40 % 빠릅니다.

주의 사항 :이 솔루션은 명백한 솔루션보다 빠르지 만 더 모호하며 고급 Python 기술에 의존합니다. 당신이 그것을 사용한다면, 나는 그것을 코멘트와 함께 동반하는 것이 좋습니다. 이 특정 작업의 성능에 대해 정말로 관심이있는 경우에만 사용할 가치가있을 것입니다 (무엇이든 매우 빠름). (이것을 사용한 경우에는 A * 빔 검색을했는데 검색 빔에서 검색 포인트를 제거하는 데 사용했습니다.)


사용 목록의 이해를

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

필터링 (필터 또는 목록 이해 사용)이 작동하지 않는 경우가 있습니다. 이것은 다른 개체가 수정중인 목록에 대한 참조를 보유하고 있고 목록을 제자리에서 수정해야 할 때 발생합니다.

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

원래 코드와의 유일한 차이점 은 for 루프에서 names[:]대신를 사용 한다는 것입니다 names. 이렇게하면 코드가 목록의 (얕은) 사본을 반복하고 제거가 예상대로 작동합니다. 목록 복사가 얕기 때문에 상당히 빠릅니다.


필터는 이것에 대단 할 것입니다. 간단한 예 :

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

편집 : Corey의 목록 이해도 굉장합니다.


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

솔루션, 필터이해 모두 새로운 목록을 작성해야합니다. 확실하게 파이썬 내부에 ​​대해 충분히 알지 못합니다 만 , 좀 더 전통적인 (그러나 덜 우아한) 접근 방식이 더 효율적일 수 있다고 생각 합니다.

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

어쨌든 짧은 목록의 경우 앞서 제안한 두 가지 솔루션 중 하나를 고수합니다.


사전 작업에 대한 질문에 답하려면 Python 3.0에 dict comprehensions 가 포함된다는 점에 유의해야합니다 .

>>> {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 ()를 기반으로하는 이전 답변에서 언급 한 알고리즘은 계산 복잡성이 O (n ^ 2)이기 때문에 적합하지 않을 수 있습니다. . 이 경우 다음과 같은 비단뱀 함수를 사용할 수 있습니다.

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

편집 : 실제로 https://stackoverflow.com/a/4639748/274937 의 솔루션 이 광산 솔루션보다 우수합니다. 더 비단뱀적이고 빠르게 작동합니다. 그래서 다음은 새로운 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)]

필터 및 목록 이해는 귀하의 예에 적합하지만 몇 가지 문제가 있습니다.

  • 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.

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


In the case of a set.

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

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.

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

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.

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.

ReferenceURL : https://stackoverflow.com/questions/18418/elegant-way-to-remove-items-from-sequence-in-python

반응형