[python] 반복하는 동안 목록에서 항목을 제거하는 방법은 무엇입니까?

파이썬에서 튜플 목록을 반복하고 특정 기준을 충족하면 제거하려고합니다.

for tup in somelist:
    if determine(tup):
         code_to_remove_tup

대신에 무엇을 사용해야 code_to_remove_tup합니까? 이 방식으로 항목을 제거하는 방법을 알 수 없습니다.



답변

목록 이해를 사용하여 제거하지 않으려는 요소 만 포함하는 새 목록을 만들 수 있습니다.

somelist = [x for x in somelist if not determine(x)]

또는 slice에 할당 somelist[:]하여 기존 항목을 변경하여 원하는 항목 만 포함 할 수 있습니다.

somelist[:] = [x for x in somelist if not determine(x)]

이 방법은 somelist변경 사항을 반영해야하는 다른 참조가있는 경우 유용 할 수 있습니다 .

이해하는 대신을 사용할 수도 있습니다 itertools. 파이썬 2에서 :

from itertools import ifilterfalse
somelist[:] = ifilterfalse(determine, somelist)

또는 파이썬 3에서 :

from itertools import filterfalse
somelist[:] = filterfalse(determine, somelist)

명확성을 기하기 위해 그리고 [:]hackish 또는 fuzzy 표기법을 사용하는 사람들을 위해 보다 명확한 대안이 있습니다. 이론적으로는 위의 한 줄짜리 라이너와 공간 및 시간과 동일한 성능을 발휘해야합니다.

temp = []
while somelist:
    x = somelist.pop()
    if not determine(x):
        temp.append(x)
while temp:
    somelist.append(templist.pop())

또한 최소한의 수정으로 Python 목록의 항목 바꾸기 기능 이 없을 수있는 다른 언어에서도 작동합니다 . 예를 들어, 모든 언어 False가 파이썬처럼 빈 목록을 캐스트 하지는 않습니다. while somelist:보다 명시적인 것을 대신 할 수 있습니다 while len(somelist) > 0:.


답변

목록 이해를 제안하는 답변은 완전히 정확합니다. 단지 완전히 새로운 목록을 작성한 다음 이전 목록과 동일한 이름을 지정하면 이전 목록을 수정하지 않습니다. @Lennart의 제안 에서와 같이 선택적 제거로 수행하는 것과는 다릅니다. 더 빠르지 만 여러 참조를 통해 목록에 액세스하면 참조 중 하나만 다시하고 목록 객체를 변경하지 않는다는 사실 그 자체는 미묘하고 비참한 버그로 이어질 수 있습니다.

다행스럽게도 목록 이해 속도와 내부 변경에 필요한 의미를 모두 쉽게 얻을 수 있습니다.

somelist[:] = [tup for tup in somelist if determine(tup)]

다른 답변과의 미묘한 차이점에 유의하십시오.이 이름은 베어 이름에 할당되지 않습니다. 목록 전체에 발생하는 목록 조각에 할당되므로 하나의 참조를 다시 가져 오는 대신 동일한 Python 목록 객체 내에서 목록 내용 을 대체합니다. 다른 답변과 같이 (이전 목록 객체에서 새 목록 객체로).


답변

목록의 사본을 가져 와서 먼저 반복해야합니다. 그렇지 않으면 예상치 못한 결과로 인해 반복이 실패합니다.

예를 들어 (목록 유형에 따라 다름) :

for tup in somelist[:]:
    etc....

예를 들면 :

>>> somelist = range(10)
>>> for x in somelist:
...     somelist.remove(x)
>>> somelist
[1, 3, 5, 7, 9]

>>> somelist = range(10)
>>> for x in somelist[:]:
...     somelist.remove(x)
>>> somelist
[]


답변

for i in range(len(somelist) - 1, -1, -1):
    if some_condition(somelist, i):
        del somelist[i]

그렇지 않으면 거꾸로 가야합니다. 그렇지 않으면 앉아있는 나뭇 가지를 톱질하는 것과 같습니다.

파이썬 2 사용자 : 대체 range에 의해 xrange피하기 위해 하드 코딩 된 목록을 작성


답변

공식 파이썬 2 튜토리얼 4.2. “명세서”

https://docs.python.org/2/tutorial/controlflow.html#for-statements

문서 의이 부분은 다음을 분명히합니다.

  • 반복 목록을 복사하여 수정해야합니다.
  • 한 가지 방법은 슬라이스 표기법을 사용하는 것입니다. [:]

루프 내에서 반복하는 시퀀스를 수정해야하는 경우 (예 : 선택한 항목 복제) 먼저 복사하는 것이 좋습니다. 시퀀스를 반복해도 암시 적으로 복사되지 않습니다. 슬라이스 표기법은이를 특히 편리하게 만듭니다.

>>> words = ['cat', 'window', 'defenestrate']
>>> for w in words[:]:  # Loop over a slice copy of the entire list.
...     if len(w) > 6:
...         words.insert(0, w)
...
>>> words
['defenestrate', 'cat', 'window', 'defenestrate']

파이썬 2 문서 7.3. “for 문”

https://docs.python.org/2/reference/compound_stmts.html#for

문서 의이 부분은 다시 한 번 복사해야한다고 말하고 실제 제거 예를 제공합니다.

참고 : 루프가 시퀀스를 수정하는 경우 미묘한 부분이 있습니다 (이는 변경 가능한 시퀀스 (예 : 목록)에 대해서만 발생할 수 있음). 내부 카운터는 다음에 어떤 항목이 사용되는지 추적하는 데 사용되며 각 반복마다 증가합니다. 이 카운터가 시퀀스 길이에 도달하면 루프가 종료됩니다. 즉, 스위트가 시퀀스에서 현재 (또는 이전) 항목을 삭제하면 다음 항목은 건너 뜁니다 (이미 처리 된 현재 항목의 색인을 가져 오기 때문에). 마찬가지로 제품군이 현재 항목 앞에 순서대로 항목을 삽입하면 다음에 루프를 통해 현재 항목이 다시 처리됩니다. 이로 인해 전체 시퀀스 조각을 사용하여 임시 복사본을 만들어 피할 수있는 불쾌한 버그가 발생할 수 있습니다.

for x in a[:]:
    if x < 0: a.remove(x)

그러나 값을 찾기 위해 전체 목록.remove()반복해야하기 때문에이 구현에 동의하지 않습니다 .

최상의 해결 방법

어느 한 쪽:

  • https://stackoverflow.com/a/1207460/895245.append() : 새 배열을 처음부터 새로 시작한 후 다시 시작

    이 시간은 효율적이지만 반복하는 동안 어레이의 사본을 유지하기 때문에 공간이 덜 효율적입니다.

  • del인덱스와 함께 사용 : https : //.com/a/1207485/895245

    이것은 배열 사본을 분배하므로 공간 효율적이지만 CPython 목록 이 동적 배열로 구현 되므로 시간이 덜 효율적 입니다.

    즉, 항목을 제거하려면 모든 다음 항목을 하나씩 뒤로 이동해야합니다 (O (N)).

일반적으로 .append()메모리가 큰 문제가 아닌 한 기본적으로 더 빠른 옵션을 원합니다 .

파이썬이 더 잘 할 수 있습니까?

이 특정 Python API가 개선 될 수있는 것 같습니다. 예를 들어 다음과 비교하십시오.

두 가지 모두 반복자 자체를 제외하고 반복되는 목록을 수정할 수 없다는 것을 분명히하고 목록을 복사하지 않고 효율적으로 수행 할 수있는 방법을 제공합니다.

아마도 기본 이론적 근거는 파이썬 목록이 동적 배열을 지원한다고 가정하기 때문에 어쨌든 모든 유형의 제거는 시간이 비효율적이지만 Java는의 구현 ArrayListLinkedList구현이 모두 더 좋은 인터페이스 계층 구조를 갖습니다 ListIterator.

파이썬 stdlib에는 명시 적으로 연결된 목록 유형이없는 것 같습니다 : Python Linked List


답변

이러한 예에 대한 가장 좋은 방법은 목록 이해입니다.

somelist = [tup for tup in somelist if determine(tup)]

determine함수를 호출하는 것보다 복잡한 작업을 수행하는 경우 새 목록을 작성하고 단순히 추가 할 때 추가하는 것이 좋습니다. 예를 들어

newlist = []
for tup in somelist:
    # lots of code here, possibly setting things up for calling determine
    if determine(tup):
        newlist.append(tup)
somelist = newlist

를 사용하여 목록을 복사하면 remove아래 답변 중 하나에 설명 된대로 코드가 좀 더 깔끔해 보일 수 있습니다. 목록 전체를 먼저 복사하고 O(n) remove제거 할 각 요소에 대해 작업을 수행 하여 O(n^2)알고리즘으로 만들기 때문에 매우 큰 목록에 대해서는이 작업을 수행하지 않아야 합니다.

for tup in somelist[:]:
    # lots of code here, possibly setting things up for calling determine
    if determine(tup):
        newlist.append(tup)


답변

기능적 프로그래밍을 좋아하는 사람들을 위해 :

somelist[:] = filter(lambda tup: not determine(tup), somelist)

또는

from itertools import ifilterfalse
somelist[:] = list(ifilterfalse(determine, somelist))