Jak usunąć elementy z listy podczas iteracji?

934

Ieruję listę krotek w Pythonie i próbuję je usunąć, jeśli spełniają określone kryteria.

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

Czego powinienem użyć zamiast code_to_remove_tup? Nie mogę wymyślić, jak usunąć przedmiot w ten sposób.

lfaraone
źródło
Większość odpowiedzi na tej stronie tak naprawdę nie wyjaśnia, dlaczego usuwanie elementów podczas iteracji po liście daje dziwne wyniki, ale odpowiedź zaakceptowana w tym pytaniu ma i prawdopodobnie jest lepszym duplikatem dla początkujących, którzy napotykają ten problem po raz pierwszy.
ggorlen,

Odpowiedzi:

827

Możesz użyć zrozumienia listy, aby utworzyć nową listę zawierającą tylko elementy, których nie chcesz usuwać:

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

Lub przypisując do plasterka somelist[:], możesz zmutować istniejącą listę, aby zawierała tylko te elementy, które chcesz:

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

Takie podejście może być przydatne, jeśli istnieją inne odniesienia do somelist tej potrzeby, aby odzwierciedlić zmiany.

Zamiast zrozumienia możesz także użyć itertools. W Python 2:

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

Lub w Python 3:

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

Dla jasności i dla tych, którzy używają [:]notacji hackish lub fuzzy, oto bardziej wyraźna alternatywa. Teoretycznie powinien on działać tak samo w odniesieniu do przestrzeni i czasu, jak powyższe linijki.

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

Działa również w innych językach, które mogą nie mieć możliwości zamiany elementów list Pythona, z minimalnymi modyfikacjami. Na przykład, nie wszystkie języki przesyłają puste listy do a, Falsetak jak Python. Możesz zastąpić while somelist:coś bardziej wyraźnego, na przykład while len(somelist) > 0:.

David Raznick
źródło
4
Czy możesz to przyspieszyć, jeśli wiesz, że tylko kilka zostanie usuniętych, tzn. Usuń tylko te i pozostaw inne w miejscu, zamiast je ponownie pisać?
highBandWidth
20
Co jeśli moja lista jest ogromna i nie stać mnie na zrobienie kopii?
jpcgt
15
@jpcgt Powinieneś użyć somelist[:] = (x for x in somelist if determine(x))tego, aby utworzyć generator, który może nie tworzyć niepotrzebnych kopii.
Rostislav Kondratenko
8
@RostislavKondratenko: list_ass_slice()funkcja, która implementuje somelist[:]=wywołania PySequence_Fast()wewnętrznie. Ta funkcja zawsze zwraca listę, tzn. Rozwiązanie @Alex Martelli, które już korzysta z listy zamiast z generatora, jest najprawdopodobniej bardziej wydajne
jfs
6
Czy chciałbyś wyjaśnić, jakie są różnice między przypisaniem listy do listy a jej klonowaniem? Czy oryginalna lista somelistnie zostałaby zmutowana w obu metodach?
Bowen Liu,
589

Odpowiedzi sugerujące zrozumienie listy są PRAWIE NIEPRAWIDŁOWE - poza tym, że budują zupełnie nową listę, a następnie nadają jej tę samą nazwę, co stara lista, ponieważ NIE modyfikują starej listy w miejscu. Różni się to od tego, co robisz przez selektywne usuwanie, jak sugeruje @ Lennart - jest szybsze, ale jeśli twoja lista jest dostępna za pomocą wielu referencji, fakt, że po prostu resetujesz jedno z referencji i NIE zmieniasz obiektu listy samo w sobie może prowadzić do subtelnych, katastrofalnych błędów.

Na szczęście niezwykle łatwo jest uzyskać zarówno szybkość analiz listowych, jak i wymaganą semantykę modyfikacji w miejscu - wystarczy kod:

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

Zwróć uwagę na subtelną różnicę w stosunku do innych odpowiedzi: ta NIE przypisuje się do nagiej nazwy - przypisuje ją do wycinka listy, który akurat jest całą listą, zastępując w ten sposób zawartość listy w tym samym obiekcie listy Python , zamiast tylko ponownego umieszczania jednego odwołania (od poprzedniego obiektu listy do nowego obiektu listy), podobnie jak inne odpowiedzi.

Alex Martelli
źródło
1
Jak wykonać to samo zlecenie na plasterki za pomocą dykta? W Pythonie 2.6?
PaulMcG,
11
@Paul: Ponieważ dyktanda są nieuporządkowane, plasterki nie mają znaczenia dla dykt. Jeśli chcesz zastąpić zawartość dict atreścią dict b, użyj a.clear(); a.update(b).
Sven Marnach
1
Dlaczego „ponowne osadzenie” jednego z odniesień przez zastąpienie tego, co zmienna odnosi się do błędu, powoduje błędy? Wydaje się, że byłby to potencjalny problem tylko w aplikacjach wielowątkowych, a nie jednowątkowych.
Derek Dahmer,
59
@Derek Ponownie x = ['foo','bar','baz']; y = x; x = [item for item in x if determine(item)];przypisuje xsię to wynikowi zrozumienia listy, ale ynadal odnosi się do oryginalnej listy ['foo','bar','baz']. Jeśli się spodziewałeś xi yodwołujesz się do tej samej listy, być może wprowadziłeś błędy. Temu zapobiec poprzez przypisanie do plasterka całej listy, jak pokazuje Alex i pokażę tutaj: x = ["foo","bar","baz"]; y = x; x[:] = [item for item in x if determine(item)];. Lista jest modyfikowana na miejscu. upewniając się, że wszystkie odniesienia do listy (zarówno tutaj, jak xi ytutaj) odnoszą się do nowej listy.
Steven T. Snyder
w rzeczywistości, użycie filterfunkcji również tworzy nową listę, nie modyfikuje elementów w miejscu ... tylkoolist[:] = [i for i in olist if not dislike(i)]
John Strood
302

Musisz najpierw wykonać kopię listy i powtórzyć ją, w przeciwnym razie iteracja zakończy się niepowodzeniem, co może być nieoczekiwanym rezultatem.

Na przykład (zależy od typu listy):

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

Przykład:

>>> 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
[]
Lennart Regebro
źródło
13
@Zen Ponieważ drugi z nich iteruje kopię listy. Więc kiedy modyfikujesz oryginalną listę, nie modyfikujesz kopiowanej iteracji.
Lennart Regebro
3
Co jest lepsze w robieniu somelisty [:] w porównaniu do listy (somelisty)?
Mariusz Jamro
3
list(somelist)przekształci iterowalny w listę. somelist[:]tworzy kopię obiektu obsługującego krojenie. Więc niekoniecznie robią to samo. W tym przypadku chcę zrobić kopię somelistobiektu, więc używam[:]
Lennart Regebro
33
Uwaga dla każdego, kto to czyta, jest to BARDZO wolne dla list. remove()musi przejrzeć CAŁĄ listę dla każdej iteracji, więc zajmie to wieczność.
vitiral
7
Duży czas O nie ma znaczenia, gdy mamy do czynienia z listami kilkunastu przedmiotów. Często zrozumiały i łatwy do zrozumienia dla przyszłych programistów jest o wiele cenniejszy niż wydajność.
Steve,
127
for i in range(len(somelist) - 1, -1, -1):
    if some_condition(somelist, i):
        del somelist[i]

Musisz cofnąć się w przeciwnym razie przypomina to ścinanie gałęzi, na której siedzisz :-)

Użytkownicy Python 2: zamień rangena, xrangeaby uniknąć tworzenia listy zakodowanej na stałe

John Machin
źródło
13
W najnowszych wersjach Pythona możesz to zrobić jeszcze bardziej czysto, używając reversed()wbudowanego
ncoghlan
16
Reverse () nie tworzy nowej listy, tworzy iterator do tyłu na podstawie podanej sekwencji. Podobnie jak enumerate (), musisz owinąć go w list (), aby faktycznie wyciągnąć z niego listę. Możesz być myślenie sortowane (), która ma utworzyć nową listę za każdym razem (to musi, więc może go rozwiązać).
ncoghlan
1
@ Mauris, ponieważ enumeratezwraca iterator i reversedoczekuje sekwencji. Myślę, że możesz to zrobić, reversed(list(enumerate(somelist)))jeśli nie masz nic przeciwko tworzeniu dodatkowej listy w pamięci.
drevicko,
2
To jest O (N * M) dla tablic, jest bardzo wolne, jeśli usuniesz wiele pozycji z dużej listy. Więc nie polecam.
Sam Watkins,
2
@SamWatkins Tak, ta odpowiedź dotyczy usuwania kilku elementów z bardzo dużej tablicy. Mniejsze zużycie pamięci, ale może być mczasem wolniejsze.
Navin
52

Oficjalny samouczek języka Python 2 4.2. „dla wyciągów”

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

Ta część dokumentacji wyjaśnia, że:

  • musisz wykonać kopię listy iterowanej, aby ją zmodyfikować
  • jednym ze sposobów jest zapis notacji plastra [:]

Jeśli musisz zmodyfikować sekwencję, w której iterujesz podczas pracy w pętli (na przykład w celu zduplikowania wybranych elementów), zaleca się najpierw wykonanie kopii. Iteracja po sekwencji nie tworzy domyślnie kopii. Notacja plastra sprawia, że ​​jest to szczególnie wygodne:

>>> 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']

Dokumentacja Python 2 7.3. „Oświadczenie za”

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

Ta część dokumentów mówi jeszcze raz, że musisz zrobić kopię, i podaje rzeczywisty przykład usunięcia:

Uwaga: Subtelność jest subtelna, gdy sekwencja jest modyfikowana przez pętlę (może się to zdarzyć tylko w przypadku sekwencji zmiennych, tj. List). Wewnętrzny licznik służy do śledzenia, który element jest używany następnie, i jest on zwiększany przy każdej iteracji. Gdy licznik osiągnie długość sekwencji, pętla kończy się. Oznacza to, że jeśli pakiet usunie bieżący (lub poprzedni) element z sekwencji, następny element zostanie pominięty (ponieważ otrzymuje indeks bieżącego elementu, który został już przetworzony). Podobnie, jeśli pakiet wstawi element w sekwencji przed bieżącym elementem, bieżący element zostanie ponownie przetworzony następnym razem przez pętlę. Może to prowadzić do nieprzyjemnych błędów, których można uniknąć, wykonując tymczasową kopię przy użyciu wycinka całej sekwencji, np.

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

Jednak nie zgadzam się z tą implementacją, ponieważ .remove()musi iterować całą listę aby znaleźć wartość.

Najlepsze obejścia

Zarówno:

Ogólnie rzecz biorąc, po prostu chcesz iść szybciej .append() domyślnie wybrać opcję, chyba że problemem jest pamięć.

Czy Python mógłby to zrobić lepiej?

Wygląda na to, że ten konkretny interfejs API języka Python można ulepszyć. Porównaj to na przykład z:

  • Java ListIterator :: usuń dokumenty „To wywołanie można wykonać tylko raz dla każdego połączenia do następnego lub poprzedniego”
  • C ++, std::vector::erasektóry zwraca poprawny interator do elementu po usuniętym

oba z nich wyraźnie pokazują, że nie można modyfikować iterowanej listy, z wyjątkiem samego iteratora, i zapewniają efektywne sposoby wykonania tego bez kopiowania listy.

Być może podstawowym uzasadnieniem jest to, że zakłada się, że listy Pythona są dynamicznie wspierane przez tablicę, a zatem wszelkie typy usuwania będą i tak nieefektywne czasowo, podczas gdy Java ma lepszą hierarchię interfejsu zarówno z implementacjami, jak ArrayListiLinkedListListIterator .

Wydaje się, że w stdlib Pythona nie ma jawnie połączonej listy typów: Python Linked List

Ciro Santilli
źródło
48

Najlepszym podejściem do takiego przykładu byłoby zrozumienie listy

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

W przypadkach, w których robisz coś bardziej złożonego niż wywoływanie determinefunkcji, wolę zbudować nową listę i po prostu dołączyć do niej podczas pracy. Na przykład

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

Skopiowanie listy za pomocą removemoże sprawić, że Twój kod będzie wyglądał na bardziej przejrzysty, jak opisano w jednej z odpowiedzi poniżej. Zdecydowanie nie powinieneś tego robić w przypadku bardzo dużych list, ponieważ wymaga to najpierw skopiowania całej listy, a także wykonania O(n) removeoperacji dla każdego usuwanego elementu, co czyni z niego O(n^2)algorytm.

for tup in somelist[:]:
    # lots of code here, possibly setting things up for calling determine
    if determine(tup):
        newlist.append(tup)
Eli Courtwright
źródło
37

Dla tych, którzy lubią programowanie funkcjonalne:

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

lub

from itertools import ifilterfalse
somelist[:] = list(ifilterfalse(determine, somelist))
Cide
źródło
1. Zrozumienie listy i wyrażenia generatora są zapożyczone z Haskell, czysto funkcjonalnego języka; są dokładnie tak funkcjonalne jak filteri bardziej Pythonic. 2. Jeśli potrzebujesz lambdaużyć maplub filter, lista komp lub genexpr jest zawsze lepszą opcją; mapi filtermoże być nieco szybszy, gdy funkcja transformacji / predykatu jest wbudowanym Pythonem zaimplementowanym w C, a iterowalność nie jest trywialnie mała, ale zawsze są one wolniejsze, gdy potrzebujesz listy lambda, której listcomp / genexpr mógłby uniknąć.
ShadowRanger
13

Musiałem to zrobić z ogromną listą, a powielenie listy wydawało się drogie, zwłaszcza że w moim przypadku liczba usunięć byłaby niewielka w porównaniu z pozostałymi elementami. Przyjąłem to podejście na niskim poziomie.

array = [lots of stuff]
arraySize = len(array)
i = 0
while i < arraySize:
    if someTest(array[i]):
        del array[i]
        arraySize -= 1
    else:
        i += 1

To, czego nie wiem, to skuteczność kilku operacji usuwania w porównaniu z kopiowaniem dużej listy. Proszę o komentarz, jeśli masz jakiś wgląd.

Michał
źródło
W moim przypadku muszę przenieść te „niechciane” elementy na inną listę. Czy masz jakiś nowy komentarz na temat tego rozwiązania? Myślę też, że lepiej jest użyć niektórych skasowań zamiast powielić listę.
gustavovelascoh
To właściwa odpowiedź, jeśli wydajność stanowi problem (chociaż taki sam jak @Alexey). To powiedziawszy, listnależy przede wszystkim starannie rozważyć wybór jako struktury danych, ponieważ usunięcie ze środka listy zajmuje liniowy czas na długości listy. Jeśli tak naprawdę nie potrzebujesz losowego dostępu do k-tego sekwencyjnego elementu, może zastanów się OrderedDict?
maksymalnie
@GVelascoh, dlaczego nie stworzyć newlist = [], a potem newlist.append(array[i])tuż przed del array[i]?
maks
2
Zauważ, że jest to prawdopodobnie nieefektywne czasowo: jeśli list()jest to lista połączona, dostęp losowy jest drogi, jeśli list()jest tablicą, usuwanie jest kosztowne, ponieważ wymaga przesunięcia wszystkich następujących elementów do przodu. Przyzwoity iterator może pomóc w implementacji listy połączonej. Może to jednak być efektywne pod względem przestrzeni.
Ciro Santilli 冠状 病毒 审查 六四 事件 法轮功
10

Rozsądne może być również utworzenie nowej listy, jeśli bieżący element listy spełnia wymagane kryteria.

więc:

for item in originalList:
   if (item != badValue):
        newList.append(item)

i aby uniknąć konieczności ponownego kodowania całego projektu przy użyciu nazwy nowych list:

originalList[:] = newList

Uwaga, z dokumentacji Python:

copy.copy (x) Zwraca płytką kopię x.

copy.deepcopy (x) Zwraca głęboką kopię x.

ntk4
źródło
3
Nie dodaje to żadnych nowych informacji, których nie było w przyjętej odpowiedzi wiele lat wcześniej.
Mark Amery
2
To prosty i kolejny sposób spojrzenia na problem @ MarkAmery. Jest mniej skondensowany dla osób, które nie lubią skompresowanej składni kodowania.
ntk4
9

Ta odpowiedź została pierwotnie napisana w odpowiedzi na pytanie, które zostało oznaczone jako duplikat: Usuwanie współrzędnych z listy w pythonie

W twoim kodzie są dwa problemy:

1) Korzystając z funkcji remove (), próbujesz usunąć liczby całkowite, ale musisz usunąć krotkę.

2) Pętla for spowoduje pominięcie pozycji na liście.

Zobaczmy, co się stanie, gdy wykonamy Twój kod:

>>> L1 = [(1,2), (5,6), (-1,-2), (1,-2)]
>>> for (a,b) in L1:
...   if a < 0 or b < 0:
...     L1.remove(a,b)
... 
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
TypeError: remove() takes exactly one argument (2 given)

Pierwszy problem polega na tym, że przekazujesz zarówno „a”, jak i „b” do remove (), ale remove () akceptuje tylko jeden argument. Jak więc możemy usunąć remove (), aby działał poprawnie z twoją listą? Musimy dowiedzieć się, jaki jest każdy element twojej listy. W tym przypadku każdy jest krotką. Aby to zobaczyć, uzyskajmy dostęp do jednego elementu listy (indeksowanie zaczyna się od 0):

>>> L1[1]
(5, 6)
>>> type(L1[1])
<type 'tuple'>

Aha! Każdy element L1 jest w rzeczywistości krotką. Właśnie to musimy przekazać, aby usunąć (). Krotki w pythonie są bardzo proste, są po prostu tworzone przez umieszczanie wartości w nawiasach. „a, b” nie jest krotką, ale „(a, b)” jest krotką. Zmodyfikujemy więc Twój kod i uruchomimy go ponownie:

# The remove line now includes an extra "()" to make a tuple out of "a,b"
L1.remove((a,b))

Ten kod działa bezbłędnie, ale spójrzmy na listę, którą wypisuje:

L1 is now: [(1, 2), (5, 6), (1, -2)]

Dlaczego (1, -2) jest nadal na twojej liście? Okazuje się, że modyfikowanie listy podczas iteracji w pętli jest bardzo złym pomysłem bez specjalnej uwagi. Powodem, dla którego (1, -2) pozostaje na liście, jest to, że lokalizacje każdego elementu na liście zmieniały się między iteracjami pętli for. Zobaczmy, co się stanie, jeśli podamy powyższy kod dłuższą listę:

L1 = [(1,2),(5,6),(-1,-2),(1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
### Outputs:
L1 is now: [(1, 2), (5, 6), (1, -2), (3, 4), (5, 7), (2, 1), (5, -1), (0, 6)]

Jak można wywnioskować z tego wyniku, za każdym razem, gdy instrukcja warunkowa ma wartość true, a element listy jest usuwany, następna iteracja pętli pomija ocenę następnego elementu na liście, ponieważ jego wartości są teraz zlokalizowane w różnych indeksach.

Najbardziej intuicyjnym rozwiązaniem jest skopiowanie listy, a następnie iterowanie oryginalnej listy i modyfikowanie tylko kopii. Możesz spróbować zrobić to w ten sposób:

L2 = L1
for (a,b) in L1:
    if a < 0 or b < 0 :
        L2.remove((a,b))
# Now, remove the original copy of L1 and replace with L2
print L2 is L1
del L1
L1 = L2; del L2
print ("L1 is now: ", L1)

Jednak dane wyjściowe będą identyczne jak wcześniej:

'L1 is now: ', [(1, 2), (5, 6), (1, -2), (3, 4), (5, 7), (2, 1), (5, -1), (0, 6)]

Wynika to z faktu, że kiedy stworzyliśmy L2, Python tak naprawdę nie utworzył nowego obiektu. Zamiast tego odwoływał się jedynie do L2 do tego samego obiektu co L1. Możemy to zweryfikować za pomocą „is”, który różni się od zwykłego „equals” (==).

>>> L2=L1
>>> L1 is L2
True

Możemy wykonać prawdziwą kopię za pomocą copy.copy (). Wtedy wszystko działa zgodnie z oczekiwaniami:

import copy
L1 = [(1,2), (5,6),(-1,-2), (1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
L2 = copy.copy(L1)
for (a,b) in L1:
    if a < 0 or b < 0 :
        L2.remove((a,b))
# Now, remove the original copy of L1 and replace with L2
del L1
L1 = L2; del L2
>>> L1 is now: [(1, 2), (5, 6), (3, 4), (5, 7), (2, 1), (0, 6)]

Wreszcie istnieje jedno czystsze rozwiązanie niż konieczność zrobienia całkowicie nowej kopii L1. Funkcja reverse ():

L1 = [(1,2), (5,6),(-1,-2), (1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
for (a,b) in reversed(L1):
    if a < 0 or b < 0 :
        L1.remove((a,b))
print ("L1 is now: ", L1)
>>> L1 is now: [(1, 2), (5, 6), (3, 4), (5, 7), (2, 1), (0, 6)]

Niestety nie potrafię odpowiednio opisać, jak działa reverse (). Zwraca obiekt „listreverseiterator”, gdy lista jest do niego przekazywana. Ze względów praktycznych można go traktować jako tworzenie odwróconej kopii jego argumentu. To rozwiązanie polecam.

Cinghiale
źródło
4

Jeśli chcesz zrobić cokolwiek innego podczas iteracji, może być miło uzyskać zarówno indeks (co gwarantuje, że możesz się do niego odwoływać, na przykład, jeśli masz listę nagrań), jak i faktyczną zawartość elementu listy.

inlist = [{'field1':10, 'field2':20}, {'field1':30, 'field2':15}]    
for idx, i in enumerate(inlist):
    do some stuff with i['field1']
    if somecondition:
        xlist.append(idx)
for i in reversed(xlist): del inlist[i]

enumeratedaje dostęp do elementu i indeksu jednocześnie. reversedpolega na tym, że wskaźniki, które zamierzasz później usunąć, nie zmieniają się dla Ciebie.

fantastyczny
źródło
Dlaczego uzyskanie indeksu jest bardziej odpowiednie w przypadku, gdy masz listę nagrań, niż w przypadku jakiejkolwiek innej listy? O ile wiem, nie ma to sensu.
Mark Amery
4

Możesz użyć filter() dostępny jako wbudowany.

Po więcej szczegółów sprawdź tutaj

Xolve
źródło
4

Większość odpowiedzi tutaj chce, abyś utworzył kopię listy. Miałem przypadek użycia, w którym lista była dość długa (110 000 pozycji) i rozsądniej było zamiast tego zmniejszać listę.

Przede wszystkim musisz zamienić pętlę foreach na pętlę while ,

i = 0
while i < len(somelist):
    if determine(somelist[i]):
         del somelist[i]
    else:
        i += 1

Wartość inie zmienia się w bloku if, ponieważ po usunięciu starego elementu będziesz chciał uzyskać wartość nowego elementu Z TEGO SAMEGO INDEKSU.

Mujeeb
źródło
3

Możesz spróbować for-looping w odwrotnej kolejności, więc dla some_list możesz zrobić coś takiego:

list_len = len(some_list)
for i in range(list_len):
    reverse_i = list_len - 1 - i
    cur = some_list[reverse_i]

    # some logic with cur element

    if some_condition:
        some_list.pop(reverse_i)

W ten sposób indeks jest wyrównany i nie cierpi z powodu aktualizacji listy (niezależnie od tego, czy pop element jest pobierany, czy nie).

Queequeg
źródło
Pętla reversed(list(enumerate(some_list)))byłaby prostsza niż samodzielne obliczanie indeksów.
Mark Amery
@ MarkAmery nie sądzę, że możesz zmienić listę w ten sposób.
Queequeg
3

Jedno możliwe rozwiązanie, przydatne, jeśli chcesz nie tylko usunąć niektóre rzeczy, ale także zrobić coś ze wszystkimi elementami w jednej pętli:

alist = ['good', 'bad', 'good', 'bad', 'good']
i = 0
for x in alist[:]:
    if x == 'bad':
        alist.pop(i)
        i -= 1
    # do something cool with x or just print x
    print(x)
    i += 1
Alexey
źródło
Naprawdę powinieneś używać pojęć. Są znacznie łatwiejsze do zrozumienia.
Beefster,
Co jeśli chcę badcoś usunąć , zrobić coś z tym, a także zrobić coś z goodrzeczami w jednej pętli?
Alexey,
1
W rzeczywistości zdałem sobie sprawę, że jest tutaj spryt, że tworzysz kopię listy za pomocą otwartego wycinka ( alist[:]) A ponieważ możesz robić coś fantazyjnego, w rzeczywistości ma ona zastosowanie. Dobra wersja jest dobra. Weź moją opinię.
Beefster
2

Musiałem zrobić coś podobnego, aw moim przypadku problemem była pamięć - musiałem scalić wiele obiektów zestawu danych na liście, po zrobieniu z nimi pewnych rzeczy jako nowego obiektu i musiałem pozbyć się każdego wpisu, z którym się łączyłem unikaj powielania ich wszystkich i niszczenia pamięci. W moim przypadku dobrze działały obiekty w słowniku zamiast listy:

``

k = range(5)
v = ['a','b','c','d','e']
d = {key:val for key,val in zip(k, v)}

print d
for i in range(5):
    print d[i]
    d.pop(i)
print d

``

rafa
źródło
2

TLDR:

Napisałem bibliotekę, która pozwala ci to zrobić:

from fluidIter import FluidIterable
fSomeList = FluidIterable(someList)  
for tup in fSomeList:
    if determine(tup):
        # remove 'tup' without "breaking" the iteration
        fSomeList.remove(tup)
        # tup has also been removed from 'someList'
        # as well as 'fSomeList'

Najlepiej jest użyć innej metody, jeśli to możliwe, która nie wymaga modyfikowania iteracji podczas iteracji, ale w przypadku niektórych algorytmów może nie być to takie proste. Jeśli więc masz pewność, że naprawdę chcesz wzorca kodu opisanego w pierwotnym pytaniu, jest to możliwe.

Powinien działać na wszystkich zmiennych sekwencjach, a nie tylko na listach.


Pełna odpowiedź:

Edycja: Ostatni przykładowy kod w tej odpowiedzi podaje przypadek użycia, dla którego czasami warto zmodyfikować listę zamiast używać jej interpretacji. Pierwsza część odpowiedzi służy jako samouczek na temat sposobu modyfikowania tablicy w miejscu.

Rozwiązanie wynika z tego odpowiedzi (na powiązane pytanie) od nadawcy. To wyjaśnia, w jaki sposób indeks tablicy jest aktualizowany podczas iteracji po zmodyfikowanej liście. Poniższe rozwiązanie ma na celu prawidłowe śledzenie indeksu tablicy, nawet jeśli lista zostanie zmodyfikowana.

Pobrać fluidIter.pyz tutaj https://github.com/alanbacon/FluidIterator , to tylko jeden plik, więc nie ma potrzeby instalowania git. Nie ma instalatora, więc musisz sam upewnić się, że plik znajduje się w ścieżce Pythona. Kod został napisany dla Pythona 3 i nie jest testowany w Pythonie 2.

from fluidIter import FluidIterable
l = [0,1,2,3,4,5,6,7,8]  
fluidL = FluidIterable(l)                       
for i in fluidL:
    print('initial state of list on this iteration: ' + str(fluidL)) 
    print('current iteration value: ' + str(i))
    print('popped value: ' + str(fluidL.pop(2)))
    print(' ')

print('Final List Value: ' + str(l))

Spowoduje to wygenerowanie następującego wyniku:

initial state of list on this iteration: [0, 1, 2, 3, 4, 5, 6, 7, 8]
current iteration value: 0
popped value: 2

initial state of list on this iteration: [0, 1, 3, 4, 5, 6, 7, 8]
current iteration value: 1
popped value: 3

initial state of list on this iteration: [0, 1, 4, 5, 6, 7, 8]
current iteration value: 4
popped value: 4

initial state of list on this iteration: [0, 1, 5, 6, 7, 8]
current iteration value: 5
popped value: 5

initial state of list on this iteration: [0, 1, 6, 7, 8]
current iteration value: 6
popped value: 6

initial state of list on this iteration: [0, 1, 7, 8]
current iteration value: 7
popped value: 7

initial state of list on this iteration: [0, 1, 8]
current iteration value: 8
popped value: 8

Final List Value: [0, 1]

Powyżej zastosowaliśmy popmetodę na obiekcie listy płynów. Inne typowe metody iterowalny realizowane są także takie jak del fluidL[i], .remove, .insert, .append, .extend. Lista może być również modyfikowana za pomocą plasterków ( sortireverse metody nie są zaimplementowane).

Jedynym warunkiem jest to, że musisz modyfikować listę tylko w miejscu, jeśli w dowolnym momencie fluidLlub zostałeś lponownie przypisany do innego obiektu listy, kod nie działałby. Oryginalny fluidLobiekt nadal byłby używany przez pętlę for, ale stałby się poza zasięgiem, abyśmy mogli go modyfikować.

to znaczy

fluidL[2] = 'a'   # is OK
fluidL = [0, 1, 'a', 3, 4, 5, 6, 7, 8]  # is not OK

Jeśli chcemy uzyskać dostęp do bieżącej wartości indeksu listy, nie możemy użyć wyliczenia, ponieważ liczy się tylko ile razy uruchomiona została pętla for. Zamiast tego użyjemy bezpośrednio obiektu iteratora.

fluidArr = FluidIterable([0,1,2,3])
# get iterator first so can query the current index
fluidArrIter = fluidArr.__iter__()
for i, v in enumerate(fluidArrIter):
    print('enum: ', i)
    print('current val: ', v)
    print('current ind: ', fluidArrIter.currentIndex)
    print(fluidArr)
    fluidArr.insert(0,'a')
    print(' ')

print('Final List Value: ' + str(fluidArr))

Spowoduje to wyświetlenie następujących danych:

enum:  0
current val:  0
current ind:  0
[0, 1, 2, 3]

enum:  1
current val:  1
current ind:  2
['a', 0, 1, 2, 3]

enum:  2
current val:  2
current ind:  4
['a', 'a', 0, 1, 2, 3]

enum:  3
current val:  3
current ind:  6
['a', 'a', 'a', 0, 1, 2, 3]

Final List Value: ['a', 'a', 'a', 'a', 0, 1, 2, 3]

FluidIterableKlasa tylko zapewnia otoki na pierwotnej liście obiektu. Dostęp do oryginalnego obiektu można uzyskać jako właściwość obiektu płynnego w następujący sposób:

originalList = fluidArr.fixedIterable

Więcej przykładów / testów można znaleźć w if __name__ is "__main__":sekcji na dole fluidIter.py. Warto na nie spojrzeć, ponieważ wyjaśniają, co dzieje się w różnych sytuacjach. Na przykład: Zastępowanie dużych sekcji listy za pomocą wycinka. Lub używając (i modyfikując) tę samą iterowalną w zagnieżdżonej pętli.

Tak jak powiedziałem na początek: jest to skomplikowane rozwiązanie, które pogorszy czytelność twojego kodu i utrudni debugowanie. Dlatego też inne rozwiązania, takie jak listowe lista wymienionych w Davida Raznick za odpowiedzi należy rozważyć w pierwszej kolejności. Biorąc to pod uwagę, znalazłem czasy, w których ta klasa była dla mnie przydatna i łatwiejsza w użyciu niż śledzenie wskaźników elementów, które należy usunąć.


Edycja: Jak wspomniano w komentarzach, ta odpowiedź tak naprawdę nie stanowi problemu, dla którego to podejście zapewnia rozwiązanie. Spróbuję rozwiązać ten problem tutaj:

Wyjaśnienia list umożliwiają generowanie nowej listy, ale te podejścia mają tendencję do patrzenia na każdy element osobno, a nie na bieżący stan listy jako całości.

to znaczy

newList = [i for i in oldList if testFunc(i)]

Ale co jeśli wynik testFunczależy od elementów, które zostały newListjuż dodane ? Lub elementy nadal woldList tym, mogą zostać dodane później? Nadal może istnieć sposób korzystania ze zrozumienia listy, ale zacznie tracić swoją elegancję i dla mnie łatwiej jest zmodyfikować listę na miejscu.

Poniższy kod jest przykładem algorytmu, który cierpi z powodu powyższego problemu. Algorytm zmniejszy listę, aby żaden element nie był wielokrotnością żadnego innego elementu.

randInts = [70, 20, 61, 80, 54, 18, 7, 18, 55, 9]
fRandInts = FluidIterable(randInts)
fRandIntsIter = fRandInts.__iter__()
# for each value in the list (outer loop)
# test against every other value in the list (inner loop)
for i in fRandIntsIter:
    print(' ')
    print('outer val: ', i)
    innerIntsIter = fRandInts.__iter__()
    for j in innerIntsIter:
        innerIndex = innerIntsIter.currentIndex
        # skip the element that the outloop is currently on
        # because we don't want to test a value against itself
        if not innerIndex == fRandIntsIter.currentIndex:
            # if the test element, j, is a multiple 
            # of the reference element, i, then remove 'j'
            if j%i == 0:
                print('remove val: ', j)
                # remove element in place, without breaking the
                # iteration of either loop
                del fRandInts[innerIndex]
            # end if multiple, then remove
        # end if not the same value as outer loop
    # end inner loop
# end outerloop

print('')
print('final list: ', randInts)

Dane wyjściowe i końcowa lista zredukowana są pokazane poniżej

outer val:  70

outer val:  20
remove val:  80

outer val:  61

outer val:  54

outer val:  18
remove val:  54
remove val:  18

outer val:  7
remove val:  70

outer val:  55

outer val:  9
remove val:  18

final list:  [20, 61, 7, 55, 9]
Rezonans
źródło
Trudno powiedzieć, czy jest to zbyt skomplikowane, ponieważ nie jest jasne, jaki problem próbuje rozwiązać; co osiąga usunięcie elementów przy użyciu tego podejścia, some_list[:] = [x for x in some_list if not some_condition(x)]czego nie osiąga? Bez odpowiedzi na to pytanie, dlaczego ktokolwiek miałby wierzyć, że pobieranie i korzystanie z 600-liniowej biblioteki wraz z literówkami i komentarzem jest lepszym rozwiązaniem ich problemu niż jednoliniowa? -1.
Mark Amery
@MarkAmery. Główny przypadek użycia w przypadku, gdy ma to miejsce przy próbie ustalenia, czy element powinien zostać usunięty (dodany lub przeniesiony) na podstawie nie tylko samego elementu, ale stanu innego elementu na liście lub stanu listy jako elementu cały. Na przykład, ze zrozumieniem listy nie jest możliwe napisanie czegoś takiego, some_list[:] = [x for x in some_list if not some_condition(y)]gdzie yjest inny element listy x. Nie byłoby też możliwe pisanie some_list[:] = [x for x in some_list if not some_condition(intermediateStateOf_some_list)].
Rezonans
2

Najskuteczniejszą metodą jest lista zrozumienie, wiele osób pokazać swoją sprawę, oczywiście, jest to również dobry sposób, aby uzyskać iteratorthrough filter.

Filterodbiera funkcję i sekwencję. Filterstosuje kolejno przekazaną funkcję do każdego elementu, a następnie decyduje, czy zachować lub odrzucić element w zależności od tego, czy wartością zwracaną funkcji jest, Trueczy False.

Jest przykład (zdobądź szanse na krotkę):

list(filter(lambda x:x%2==1, (1, 2, 4, 5, 6, 9, 10, 15)))  
# result: [1, 5, 9, 15]

Uwaga: Nie można również obsługiwać iteratorów. Iteratory są czasem lepsze niż sekwencje.

chseng
źródło
2

pętla będzie iterować przez indeks ..

uważają, że masz listę,

[5, 7, 13, 29, 65, 91]

używasz zmiennej listy o nazwie lis. a ty używasz tego samego do usunięcia ...

twoja zmienna

lis = [5, 7, 13, 29, 35, 65, 91]
       0  1   2   3   4   5   6

podczas piątej iteracji

twój numer 35 nie był liczbą pierwszą, więc usunąłeś go z listy.

lis.remove(y)

a następnie następna wartość (65) przechodzi do poprzedniego indeksu.

lis = [5, 7, 13, 29, 65, 91]
       0  1   2   3   4   5

więc wskaźnik wykonanej 4. iteracji przesunięty na 5.

dlatego twoja pętla nie obejmuje 65, ponieważ została przeniesiona do poprzedniego indeksu.

więc nie powinieneś odwoływać się do listy do innej zmiennej, która wciąż odwołuje się do oryginału zamiast do kopiowania.

ite = lis #dont do it will reference instead copy

więc skopiuj listę używając list[::]

teraz da ci

[5, 7, 13, 29]

Problem polega na tym, że podczas iteracji usunięto wartość z listy, a następnie indeks listy się zwinął.

więc zamiast tego możesz spróbować zrozumieć.

który obsługuje wszystkie iterowalne opcje, takie jak: lista, krotka, dict, ciąg itp

Mohideen bin Mohammed
źródło
Pomogło mi to zrozumieć, dlaczego mój kod zawodzi.
Wahid Sadik
2

Jeśli chcesz usunąć elementy z listy podczas iteracji, użyj pętli while, aby móc zmieniać bieżący indeks i indeks końcowy po każdym usunięciu.

Przykład:

i = 0
length = len(list1)

while i < length:
    if condition:
        list1.remove(list1[i])
        i -= 1
        length -= 1

    i += 1
Bez nazwy
źródło
1

Pozostałe odpowiedzi są poprawne, ponieważ zazwyczaj jest to zły pomysł, aby usunąć z listy, którą iterujesz. Odwrotne iterowanie pozwala uniknąć pułapek, ale znacznie trudniej jest podążać za kodem, który to robi, więc zwykle lepiej jest używać rozumienia listy lub filter.

Jest jednak jeden przypadek, w którym można bezpiecznie usunąć elementy z sekwencji, którą iterujesz: jeśli usuwasz tylko jeden element podczas iteracji. Można to zapewnić za pomocą a returnlub a break. Na przykład:

for i, item in enumerate(lst):
    if item % 4 == 0:
        foo(item)
        del lst[i]
        break

Jest to często łatwiejsze do zrozumienia niż zrozumienie listy, gdy wykonujesz pewne operacje z efektami ubocznymi na pierwszym elemencie na liście, który spełnia pewien warunek, a następnie usuwasz go z listy natychmiast po.

Wołowina
źródło
1

Mogę wymyślić trzy podejścia do rozwiązania twojego problemu. Jako przykład utworzę losową listę krotek somelist = [(1,2,3), (4,5,6), (3,6,6), (7,8,9), (15,0,0), (10,11,12)]. Warunek, który wybrałem, jest sum of elements of a tuple = 15. Na końcowej liście będziemy mieć tylko te krotki, których suma nie jest równa 15.

To, co wybrałem, to losowo wybrany przykład. Krępuj się zmienić na listę krotek i kondycję , które wybrałem.

Metoda 1.> Użyj zaproponowanego frameworka (gdzie wypełnia się kod wewnątrz pętli for). Używam małego kodu, delaby usunąć krotkę spełniającą ten warunek. Jednak ta metoda pominie krotkę (która spełnia wspomniany warunek), jeśli dwa kolejno umieszczone krotki spełniają dany warunek.

for tup in somelist:
    if ( sum(tup)==15 ): 
        del somelist[somelist.index(tup)]

print somelist
>>> [(1, 2, 3), (3, 6, 6), (7, 8, 9), (10, 11, 12)]

Metoda 2.> Skonstruuj nową listę zawierającą elementy (krotki), w których dany warunek nie jest spełniony (jest to to samo, co usunięcie elementów listy, w których dany warunek jest spełniony). Oto kod do tego:

newlist1 = [somelist[tup] for tup in range(len(somelist)) if(sum(somelist[tup])!=15)]

print newlist1
>>>[(1, 2, 3), (7, 8, 9), (10, 11, 12)]

Metoda 3.> Znajdź indeksy, w których spełniony jest dany warunek, a następnie użyj elementów usuwających (krotek) odpowiadających tym indeksom. Poniżej znajduje się kod do tego.

indices = [i for i in range(len(somelist)) if(sum(somelist[i])==15)]
newlist2 = [tup for j, tup in enumerate(somelist) if j not in indices]

print newlist2
>>>[(1, 2, 3), (7, 8, 9), (10, 11, 12)]

Metoda 1 i metoda 2 są szybsze niż metoda 3 . Metoda 2 i metoda 3 są bardziej wydajne niż metoda 1. I wolą Method2 . W powyższym przykładzietime(method1) : time(method2) : time(method3) = 1 : 1 : 1.7

Siddharth Satpathy
źródło
0

Do wszystkiego, co może być naprawdę duże, używam następujących.

import numpy as np

orig_list = np.array([1, 2, 3, 4, 5, 100, 8, 13])

remove_me = [100, 1]

cleaned = np.delete(orig_list, remove_me)
print(cleaned)

To powinno być znacznie szybsze niż cokolwiek innego.

CENTURION
źródło
Z tego, co zmierzyłem, NumPy zaczyna być szybszy dla list składających się z ponad 20 elementów i osiąga> 12 razy szybsze filtrowanie dla dużych list zawierających 1000 elementów i więcej.
Georgy
0

W niektórych sytuacjach, gdy robisz coś więcej niż tylko filtrowanie listy pojedynczo, chcesz, aby iteracja zmieniła się podczas iteracji.

Oto przykład, w którym wcześniejsze skopiowanie listy jest nieprawidłowe, odwrotna iteracja jest niemożliwa, a zrozumienie listy również nie jest możliwe.

""" Sieve of Eratosthenes """

def generate_primes(n):
    """ Generates all primes less than n. """
    primes = list(range(2,n))
    idx = 0
    while idx < len(primes):
        p = primes[idx]
        for multiple in range(p+p, n, p):
            try:
                primes.remove(multiple)
            except ValueError:
                pass #EAFP
        idx += 1
        yield p
CodeKid
źródło
0

Jeśli później użyjesz nowej listy, możesz po prostu ustawić element na Brak, a następnie ocenić go w późniejszej pętli, tak jak to

for i in li:
    i = None

for elem in li:
    if elem is None:
        continue

W ten sposób nie musisz kopiować listy i łatwiej to zrozumieć.

John Zhang
źródło
-1

wymyśl listę liczb, a chcesz usunąć wszystkie nie, które są podzielne przez 3,

list_number =[i for i in range(100)]

przy użyciu list comprehensionspowoduje to zmianę nowej listy i utworzenie nowej przestrzeni pamięci

new_list =[i for i in list_number if i%3!=0]

użycie lambda filterfunkcji spowoduje utworzenie nowej listy wynikowej i zajmie miejsce w pamięci

new_list = list(filter(lambda x:x%3!=0, list_number))

bez zajmowania miejsca w pamięci dla nowej listy i modyfikowania istniejącej listy

for index, value in enumerate(list_number):
    if list_number[index]%3==0:
        list_number.remove(value)
sahasrara62
źródło