Wie entferne ich Elemente aus einer Liste, während ich iteriere?

934

Ich iteriere über eine Liste von Tupeln in Python und versuche, sie zu entfernen, wenn sie bestimmte Kriterien erfüllen.

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

Was soll ich anstelle von verwenden code_to_remove_tup? Ich kann nicht herausfinden, wie ich den Gegenstand auf diese Weise entfernen kann.

lfaraone
quelle
Die meisten Antworten auf dieser Seite erklären nicht wirklich, warum das Entfernen von Elementen beim Durchlaufen einer Liste zu seltsamen Ergebnissen führt, aber die akzeptierte Antwort in dieser Frage ist wahrscheinlich ein besserer Betrüger für Anfänger, die zum ersten Mal auf dieses Problem stoßen.
Ggorlen

Antworten:

827

Mit einem Listenverständnis können Sie eine neue Liste erstellen, die nur die Elemente enthält, die Sie nicht entfernen möchten:

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

Durch Zuweisen zum Slice somelist[:]können Sie die vorhandene Liste so ändern, dass sie nur die gewünschten Elemente enthält:

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

Dieser Ansatz kann nützlich sein, wenn andere Verweise auf vorhanden sind somelist , um die Änderungen widerzuspiegeln.

Anstelle eines Verständnisses könnten Sie auch verwenden itertools. In Python 2:

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

Oder in Python 3:

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

Aus Gründen der Klarheit und für diejenigen, die die Verwendung der [:]Notation als hackisch oder unscharf empfinden, ist hier eine explizitere Alternative. Theoretisch sollte es räumlich und zeitlich die gleiche Leistung erbringen wie die oben genannten Einzeiler.

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

Es funktioniert auch in anderen Sprachen, die möglicherweise nicht über die Fähigkeit zum Ersetzen von Elementen in Python-Listen verfügen , mit minimalen Änderungen. Zum Beispiel werfen nicht alle Sprachen leere Listen in eine Falsewie Python. Sie können while somelist:etwas expliziteres ersetzen while len(somelist) > 0:.

David Raznick
quelle
4
Können Sie es schneller machen, wenn Sie wissen, dass nur wenige gelöscht werden, dh nur diese löschen und die anderen an Ort und Stelle lassen, anstatt sie neu zu schreiben?
HighBandWidth
20
Was ist, wenn meine Liste riesig ist und ich es mir nicht leisten kann, eine Kopie zu erstellen?
jpcgt
15
@jpcgt Wenn Sie somelist[:] = (x for x in somelist if determine(x))dies verwenden, wird ein Generator erstellt, der möglicherweise keine unnötigen Kopien erstellt.
Rostislav Kondratenko
8
@RostislavKondratenko: list_ass_slice()Funktion, die somelist[:]=Aufrufe PySequence_Fast()intern implementiert . Diese Funktion gibt immer eine Liste zurück, dh die Lösung von @Alex Martelli, die bereits eine Liste anstelle eines Generators verwendet, ist höchstwahrscheinlich effizienter
jfs
6
Möchten Sie erklären, welche Unterschiede zwischen der Zuordnung des Listenverständnisses zur Liste und dem Listenklon bestehen? Wäre die ursprüngliche Liste somelistnicht in beiden Methoden mutiert?
Bowen Liu
589

Die Antworten, die auf ein Listenverständnis hinweisen, sind FAST korrekt - außer dass sie eine völlig neue Liste erstellen und ihr dann den gleichen Namen geben wie die alte Liste. Sie ändern NICHT die alte Liste an Ort und Stelle. Das unterscheidet sich von dem, was Sie durch selektives Entfernen tun würden, wie in @ Lennarts Vorschlag - es ist schneller, aber wenn auf Ihre Liste über mehrere Referenzen zugegriffen wird, ist die Tatsache, dass Sie nur eine der Referenzen erneut einsetzen und das Listenobjekt NICHT ändern selbst kann zu subtilen, katastrophalen Fehlern führen.

Glücklicherweise ist es extrem einfach, sowohl die Geschwindigkeit des Listenverständnisses als auch die erforderliche Semantik der direkten Änderung zu ermitteln - nur Code:

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

Man beachte den feinen Unterschied mit anderen Antworten: dies ist die Zuordnung nicht zu einem barename - es zu einer Liste Slice zuweisen, nur die gesamte Liste sein geschieht, wodurch die Liste ersetzt Inhalte innerhalb des gleichen Objekts Python - Liste , anstatt nur erneuten Einsetzen eine Referenz (vom vorherigen Listenobjekt zum neuen Listenobjekt) wie die anderen Antworten.

Alex Martelli
quelle
1
Wie mache ich die gleiche Aufgabe mit einem Diktat? In Python 2.6?
PaulMcG
11
@Paul: Da Diktate ungeordnet sind, sind Slices für Diktate bedeutungslos. Wenn Sie den Inhalt von dict adurch den Inhalt von dict ersetzen möchten b, verwenden Sie a.clear(); a.update(b).
Sven Marnach
1
Warum kann eine der Referenzen erneut ersetzt werden, indem ersetzt wird, worauf sich die Variable bezieht, um Fehler zu verursachen? Es scheint, dass dies nur in Multithread-Anwendungen ein potenzielles Problem darstellt, nicht in Single-Thread-Anwendungen.
Derek Dahmer
59
@Derek x = ['foo','bar','baz']; y = x; x = [item for item in x if determine(item)];Dies wird xdem Ergebnis des Listenverständnisses neu zugewiesen , ybezieht sich jedoch weiterhin auf die ursprüngliche Liste ['foo','bar','baz']. Wenn Sie erwartet haben xund yauf dieselbe Liste verweisen möchten, haben Sie möglicherweise Fehler eingeführt. Sie verhindern dies, indem Sie einem Teil der gesamten Liste zuweisen, wie Alex zeigt, und ich zeige hier : x = ["foo","bar","baz"]; y = x; x[:] = [item for item in x if determine(item)];. Die Liste wird an Ort und Stelle geändert. Stellen Sie sicher, dass alle Verweise auf die Liste (sowohl xals auch yhier) auf die neue Liste verweisen.
Steven T. Snyder
Wenn Sie auch die filterFunktion verwenden, wird eine neue Liste erstellt, und es werden keine Elemente an Ort und Stelle olist[:] = [i for i in olist if not dislike(i)]
geändert
303

Sie müssen eine Kopie der Liste erstellen und diese zuerst durchlaufen, da sonst die Iteration mit möglicherweise unerwarteten Ergebnissen fehlschlägt.

Zum Beispiel (hängt von der Art der Liste ab):

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

Ein Beispiel:

>>> 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
quelle
13
@Zen Weil der zweite eine Kopie der Liste durchläuft. Wenn Sie also die ursprüngliche Liste ändern, ändern Sie nicht die Kopie, über die Sie iterieren.
Lennart Regebro
3
Was ist besser, wenn man Somelist [:] macht als List (Somelist)?
Mariusz Jamro
3
list(somelist)konvertiert eine iterable in eine Liste. somelist[:]Erstellt eine Kopie eines Objekts, das das Schneiden unterstützt. Sie machen also nicht unbedingt dasselbe. In diesem Fall möchte ich eine Kopie des somelistObjekts [:]
erstellen
33
Hinweis für alle, die dies lesen, dies ist für Listen SEHR langsam. remove()muss die GANZE Liste für jede Iteration durchgehen, damit es ewig dauert.
Vitiral
7
Big O-Zeit spielt keine Rolle, wenn es um Listen mit nur einem Dutzend Elementen geht. Für zukünftige Programmierer oft klar und einfach zu verstehen, ist weitaus wertvoller als die Leistung.
Steve
127
for i in range(len(somelist) - 1, -1, -1):
    if some_condition(somelist, i):
        del somelist[i]

Du musst rückwärts gehen, sonst ist es ein bisschen so, als würdest du den Ast absägen, auf dem du sitzt :-)

Python 2-Benutzer: Ersetzen rangedurch xrange, um das Erstellen einer fest codierten Liste zu vermeiden

John Machin
quelle
13
In neueren Versionen von Python, können Sie dies noch sauber durch die Verwendung von reversed()builtin
ncoghlan
16
reverse () erstellt keine neue Liste, sondern einen umgekehrten Iterator über die angegebene Sequenz. Wie bei enumerate () müssen Sie es in list () einschließen, um tatsächlich eine Liste daraus zu erhalten. Sie können denken, sortiert () sein, das tut eine neue Liste jedes Mal schaffen (es muss, so dass es es sortieren).
Ncoghlan
1
@Mauris da enumerategibt einen Iterator zurück und reversederwartet eine Sequenz. Ich denke, Sie könnten es tun, reversed(list(enumerate(somelist)))wenn es Ihnen nichts ausmacht, eine zusätzliche Liste im Speicher zu erstellen.
Drevicko
2
Dies ist O (N * M) für Arrays. Es ist sehr langsam, wenn Sie viele Elemente aus einer großen Liste entfernen. Also nicht zu empfehlen.
Sam Watkins
2
@SamWatkins Ja, diese Antwort ist für den Fall, dass Sie einige Elemente aus einem sehr großen Array entfernen. Weniger Speicherbedarf, aber es kann mmal langsamer sein.
Navin
52

Offizielles Python 2-Tutorial 4.2. "für Aussagen"

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

Dieser Teil der Dokumentation macht deutlich, dass:

  • Sie müssen eine Kopie der iterierten Liste erstellen, um sie zu ändern
  • Eine Möglichkeit ist die Slice-Notation [:]

Wenn Sie die Sequenz ändern müssen, über die Sie innerhalb der Schleife iterieren (z. B. um ausgewählte Elemente zu duplizieren), wird empfohlen, zuerst eine Kopie zu erstellen. Durch Iterieren über eine Sequenz wird nicht implizit eine Kopie erstellt. Die Slice-Notation macht dies besonders praktisch:

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

Python 2-Dokumentation 7.3. "Die for-Anweisung"

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

In diesem Teil der Dokumentation wird erneut angegeben, dass Sie eine Kopie erstellen müssen, und es wird ein Beispiel für das tatsächliche Entfernen angegeben:

Hinweis: Es gibt eine Subtilität, wenn die Sequenz von der Schleife geändert wird (dies kann nur für veränderbare Sequenzen, dh Listen, auftreten). Ein interner Zähler wird verwendet, um zu verfolgen, welches Element als nächstes verwendet wird, und dieser wird bei jeder Iteration erhöht. Wenn dieser Zähler die Länge der Sequenz erreicht hat, endet die Schleife. Dies bedeutet, dass, wenn die Suite das aktuelle (oder ein vorheriges) Element aus der Sequenz löscht, das nächste Element übersprungen wird (da es den Index des aktuellen Elements erhält, das bereits behandelt wurde). Wenn die Suite ein Element in die Sequenz vor dem aktuellen Element einfügt, wird das aktuelle Element beim nächsten Mal durch die Schleife erneut behandelt. Dies kann zu bösen Fehlern führen, die vermieden werden können, indem eine temporäre Kopie unter Verwendung eines Teils der gesamten Sequenz erstellt wird, z.

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

Ich bin jedoch mit dieser Implementierung nicht einverstanden, da .remove()die gesamte Liste iteriert werden muss , um den Wert zu finden.

Beste Problemumgehungen

Entweder:

Im Allgemeinen möchten Sie nur schneller fahren .append() standardmäßig Option wählen, es sei denn, der Speicher ist ein großes Problem.

Könnte Python das besser machen?

Es scheint, dass diese spezielle Python-API verbessert werden könnte. Vergleichen Sie es zum Beispiel mit:

  • Java ListIterator :: entferne welche Dokumente "Dieser Aufruf kann nur einmal pro Aufruf zum nächsten oder vorherigen erfolgen"
  • C ++, std::vector::erasedas dem Element nach dem Entfernen einen gültigen Interator zurückgibt

Beides macht deutlich, dass Sie eine iterierte Liste nur mit dem Iterator selbst ändern können, und bietet Ihnen effiziente Möglichkeiten, dies zu tun, ohne die Liste zu kopieren.

Möglicherweise liegt der Grund dafür darin, dass angenommen wird, dass Python-Listen von dynamischen Arrays unterstützt werden, und daher ist jede Art der Entfernung ohnehin zeitlich ineffizient, während Java eine schönere Schnittstellenhierarchie mit beiden ArrayListund LinkedListImplementierungen von hat ListIterator.

Es scheint auch keinen expliziten verknüpften Listentyp in der Python stdlib zu geben: Python Linked List

Ciro Santilli 冠状 病毒 审查 六四 事件 法轮功
quelle
48

Ihr bester Ansatz für ein solches Beispiel wäre ein Listenverständnis

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

In Fällen, in denen Sie etwas Komplexeres tun als eine determineFunktion aufzurufen , ziehe ich es vor, eine neue Liste zu erstellen und sie einfach an sie anzuhängen. Zum Beispiel

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

Wenn Sie die Liste mit removekopieren, sieht Ihr Code möglicherweise etwas sauberer aus, wie in einer der folgenden Antworten beschrieben. Sie sollten dies definitiv nicht für extrem große Listen tun, da dies zuerst das Kopieren der gesamten Liste und das Ausführen einer O(n) removeOperation für jedes zu entfernende Element umfasst, wodurch dies zu einem O(n^2)Algorithmus wird.

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

Für diejenigen, die funktionale Programmierung mögen:

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

oder

from itertools import ifilterfalse
somelist[:] = list(ifilterfalse(determine, somelist))
Cide
quelle
1. Listenverständnis und Generatorausdrücke stammen von Haskell, einer reinen Funktionssprache. Sie sind genau so funktional wie filterund pythonischer. 2. Wenn Sie ein lambdaverwenden mapoder filterdie Liste comp oder genexpr, ist immer die bessere Option; mapund filterkann etwas schneller sein, wenn die Transformations- / Prädikatfunktion ein in C integriertes Python ist und die Iterierbarkeit nicht trivial klein ist, aber sie sind immer langsamer, wenn Sie eine benötigen lambda, die listcomp / genexpr vermeiden könnte.
ShadowRanger
13

Ich musste dies mit einer riesigen Liste tun, und das Duplizieren der Liste schien teuer zu sein, zumal in meinem Fall die Anzahl der Löschungen im Vergleich zu den verbleibenden Elementen gering war. Ich habe diesen Low-Level-Ansatz gewählt.

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

Was ich nicht weiß, ist, wie effizient ein paar Löschvorgänge im Vergleich zum Kopieren einer großen Liste sind. Bitte kommentieren Sie, wenn Sie einen Einblick haben.

Michael
quelle
In meinem Fall muss ich diese "unerwünschten" Elemente in eine andere Liste verschieben. Haben Sie einen neuen Kommentar zu dieser Lösung? Ich denke auch, dass es besser ist, einige Löschungen zu verwenden, anstatt die Liste zu duplizieren.
Gustavovelascoh
Dies ist die richtige Antwort, wenn die Leistung ein Problem darstellt (obwohl dies mit @Alexey identisch ist). Die Auswahl listals Datenstruktur sollte jedoch sorgfältig abgewogen werden, da das Entfernen aus der Mitte einer Liste in der Länge der Liste eine lineare Zeit in Anspruch nimmt. Wenn Sie keinen zufälligen Zugriff auf das k-te sequentielle Element benötigen, ziehen Sie vielleicht in Betracht OrderedDict?
Max
@GVelascoh warum nicht erstellen newlist = [], und dann newlist.append(array[i])kurz zuvor del array[i]?
Max
2
Beachten Sie, dass dies wahrscheinlich zeitlich ineffizient ist: Wenn list()es sich um eine verknüpfte Liste handelt, ist der Direktzugriff teuer. Wenn list()es sich um ein Array handelt, sind die Löschvorgänge teuer, da alle folgenden Elemente vorwärts verschoben werden müssen. Ein anständiger Iterator könnte die Implementierung einer verknüpften Liste verbessern. Dies könnte jedoch platzsparend sein.
Ciro Santilli 5 病毒 审查 六四 事件 5
10

Es kann sinnvoll sein, auch nur eine neue Liste zu erstellen, wenn das aktuelle Listenelement die gewünschten Kriterien erfüllt.

damit:

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

und um zu vermeiden, dass das gesamte Projekt mit dem neuen Listennamen neu codiert werden muss:

originalList[:] = newList

Hinweis aus der Python-Dokumentation:

copy.copy (x) Gibt eine flache Kopie von x zurück.

copy.deepcopy (x) Gibt eine tiefe Kopie von x zurück.

ntk4
quelle
3
Dies fügt keine neuen Informationen hinzu, die Jahre zuvor nicht in der akzeptierten Antwort enthalten waren.
Mark Amery
2
Es ist einfach und nur eine andere Möglichkeit, ein Problem bei MarkAmery zu betrachten. Es ist weniger komprimiert für Leute, die keine komprimierte Codierungssyntax mögen.
ntk4
9

Diese Antwort wurde ursprünglich als Antwort auf eine Frage geschrieben, die inzwischen als doppelt markiert wurde: Entfernen von Koordinaten aus der Liste in Python

Ihr Code enthält zwei Probleme:

1) Wenn Sie remove () verwenden, versuchen Sie, Ganzzahlen zu entfernen, während Sie ein Tupel entfernen müssen.

2) Die for-Schleife überspringt Elemente in Ihrer Liste.

Lassen Sie uns durchgehen, was passiert, wenn wir Ihren Code ausführen:

>>> 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)

Das erste Problem ist, dass Sie sowohl 'a' als auch 'b' an remove () übergeben, remove () jedoch nur ein einziges Argument akzeptiert. Wie können wir remove () dazu bringen, ordnungsgemäß mit Ihrer Liste zu arbeiten? Wir müssen herausfinden, was jedes Element Ihrer Liste ist. In diesem Fall ist jedes ein Tupel. Um dies zu sehen, greifen wir auf ein Element der Liste zu (die Indizierung beginnt bei 0):

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

Aha! Jedes Element von L1 ist tatsächlich ein Tupel. Das ist es, was wir übergeben müssen, um () zu entfernen. Tupel in Python sind sehr einfach. Sie werden einfach durch Einfügen von Werten in Klammern erstellt. "a, b" ist kein Tupel, aber "(a, b)" ist ein Tupel. Also ändern wir Ihren Code und führen ihn erneut aus:

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

Dieser Code läuft fehlerfrei, aber schauen wir uns die Liste an, die er ausgibt:

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

Warum ist (1, -2) noch in Ihrer Liste? Es stellt sich heraus, dass das Ändern der Liste während der Verwendung einer Schleife zum Durchlaufen eine sehr schlechte Idee ist, ohne besondere Sorgfalt. Der Grund dafür, dass (1, -2) in der Liste verbleibt, besteht darin, dass sich die Positionen der einzelnen Elemente in der Liste zwischen den Iterationen der for-Schleife geändert haben. Schauen wir uns an, was passiert, wenn wir dem obigen Code eine längere Liste hinzufügen:

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

Wie Sie aus diesem Ergebnis schließen können, überspringt die nächste Iteration der Schleife jedes Mal, wenn die bedingte Anweisung als wahr ausgewertet und ein Listenelement entfernt wird, die Auswertung des nächsten Elements in der Liste, da sich seine Werte jetzt an verschiedenen Indizes befinden.

Die intuitivste Lösung besteht darin, die Liste zu kopieren, dann die ursprüngliche Liste zu durchlaufen und nur die Kopie zu ändern. Sie können dies folgendermaßen versuchen:

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)

Die Ausgabe ist jedoch identisch mit zuvor:

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

Dies liegt daran, dass Python beim Erstellen von L2 kein neues Objekt erstellt hat. Stattdessen wurde L2 lediglich auf dasselbe Objekt wie L1 bezogen. Wir können dies mit 'is' überprüfen, das sich von lediglich "equals" (==) unterscheidet.

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

Mit copy.copy () können wir eine echte Kopie erstellen. Dann funktioniert alles wie erwartet:

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

Schließlich gibt es eine sauberere Lösung, als eine völlig neue Kopie von L1 erstellen zu müssen. Die Funktion 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)]

Leider kann ich nicht ausreichend beschreiben, wie reverse () funktioniert. Es gibt ein 'listreverseiterator'-Objekt zurück, wenn eine Liste an dieses übergeben wird. Aus praktischen Gründen können Sie sich vorstellen, dass eine umgekehrte Kopie des Arguments erstellt wird. Dies ist die Lösung, die ich empfehle.

Cinghiale
quelle
4

Wenn Sie während der Iteration etwas anderes tun möchten, ist es möglicherweise hilfreich, sowohl den Index (der garantiert, dass Sie darauf verweisen können, z. B. wenn Sie eine Liste von Diktaten haben) als auch den tatsächlichen Inhalt des Listenelements abzurufen.

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]

enumerateErmöglicht den gleichzeitigen Zugriff auf das Element und den Index. reversedist so, dass sich die Indizes, die Sie später löschen werden, nicht auf Sie ändern.

fantastisch
quelle
Warum ist der Index für den Fall, dass Sie eine Liste von Diktaten haben, relevanter als für den Fall einer anderen Art von Liste? Soweit ich das beurteilen kann, macht dies keinen Sinn.
Mark Amery
4

Vielleicht möchten Sie verwenden filter() verfügbar als integrierte Funktion verwenden.

Weitere Details finden Sie hier

Xolve
quelle
4

Die meisten Antworten hier möchten, dass Sie eine Kopie der Liste erstellen. Ich hatte einen Anwendungsfall, in dem die Liste ziemlich lang war (110.000 Elemente) und es klüger war, die Liste stattdessen weiter zu reduzieren.

Zunächst einmal müssen Sie foreach - Schleife mit while - Schleife ersetzen ,

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

Der Wert von iwird im if-Block nicht geändert, da Sie den Wert des neuen Elements aus demselben Index abrufen möchten, sobald das alte Element gelöscht wurde.

Mujeeb
quelle
3

Sie können das For-Looping in umgekehrter Reihenfolge versuchen, damit Sie für some_list Folgendes tun:

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)

Auf diese Weise wird der Index ausgerichtet und leidet nicht unter den Listenaktualisierungen (unabhängig davon, ob Sie das aktuelle Element einfügen oder nicht).

Queequeg
quelle
Das Durchlaufen reversed(list(enumerate(some_list)))wäre einfacher, als die Indizes selbst zu berechnen.
Mark Amery
@ MarkAmery glaubt nicht, dass Sie die Liste auf diese Weise ändern können.
Queequeg
3

Eine mögliche Lösung, die nützlich ist, wenn Sie nicht nur einige Dinge entfernen, sondern auch mit allen Elementen in einer einzigen Schleife etwas tun möchten:

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
quelle
Sie sollten wirklich nur Verständnis verwenden. Sie sind viel einfacher zu verstehen.
Beefster
Was ist, wenn ich badDinge entfernen , etwas damit machen und auch etwas mit goodDingen in einer Schleife machen möchte ?
Alexey
1
Eigentlich habe ich festgestellt, dass es hier etwas Kluges gibt, wenn Sie eine Kopie der Liste mit einem offenen Slice alist[:]erstellen ( ). Und da Sie vielleicht etwas Besonderes tun, hat es tatsächlich einen Anwendungsfall. Gute Überarbeitung ist gut. Nimm meine Gegenstimme.
Beefster
2

Ich musste etwas Ähnliches tun, und in meinem Fall war das Problem der Speicher - ich musste mehrere Datensatzobjekte in einer Liste zusammenführen, nachdem ich einige Dinge mit ihnen als neues Objekt erledigt hatte, und jeden Eintrag, mit dem ich zusammengeführt wurde, entfernen Vermeiden Sie es, alle zu duplizieren und Speicher zu sprengen. In meinem Fall hat es gut funktioniert, die Objekte in einem Wörterbuch anstelle einer Liste zu haben:

`` `

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
quelle
2

TLDR:

Ich habe eine Bibliothek geschrieben, mit der Sie dies tun können:

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'

Es ist am besten, wenn möglich eine andere Methode zu verwenden, bei der Sie Ihre Iterierbarkeit nicht ändern müssen, während Sie darüber iterieren. Bei einigen Algorithmen ist dies jedoch möglicherweise nicht so einfach. Wenn Sie also sicher sind, dass Sie das in der ursprünglichen Frage beschriebene Codemuster wirklich möchten, ist dies möglich.

Sollte auf allen veränderlichen Sequenzen funktionieren, nicht nur auf Listen.


Vollständige Antwort:

Bearbeiten: Das letzte Codebeispiel in dieser Antwort gibt einen Anwendungsfall dafür, warum Sie manchmal eine Liste an Ort und Stelle ändern möchten, anstatt ein Listenverständnis zu verwenden. Der erste Teil der Antworten dient als Tutorial, wie ein Array an Ort und Stelle geändert werden kann.

Daraus folgt die Lösung Antwort (für eine verwandte Frage) von senderle. Dies erklärt, wie der Array-Index aktualisiert wird, während eine geänderte Liste durchlaufen wird. Die folgende Lösung dient dazu, den Array-Index auch dann korrekt zu verfolgen, wenn die Liste geändert wird.

Download fluidIter.pyvon hier https://github.com/alanbacon/FluidIterator , es ist nur eine einzelne Datei, so dass git nicht installiert werden muss. Es gibt kein Installationsprogramm, daher müssen Sie sicherstellen, dass sich die Datei selbst im Python-Pfad befindet. Der Code wurde für Python 3 geschrieben und ist auf Python 2 nicht getestet.

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))

Dies erzeugt die folgende Ausgabe:

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]

Oben haben wir die popMethode für das Fluidlistenobjekt verwendet. Andere häufige iterable Verfahren sind auch wie implementiert del fluidL[i], .remove, .insert, .append, .extend. Die Liste kann auch mit Slices ( sortund) geändert werdenreverse Methoden sind nicht implementiert).

Die einzige Bedingung ist, dass Sie die Liste nur dann ändern müssen, wenn der Code zu irgendeinem Zeitpunkt fluidLoder wenn ler einem anderen Listenobjekt zugewiesen wurde, nicht funktioniert. Das ursprüngliche fluidLObjekt wird weiterhin von der for-Schleife verwendet, kann jedoch nicht mehr geändert werden.

dh

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

Wenn wir auf den aktuellen Indexwert der Liste zugreifen möchten, können wir keine Aufzählung verwenden, da dies nur zählt, wie oft die for-Schleife ausgeführt wurde. Stattdessen verwenden wir das Iteratorobjekt direkt.

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))

Dies gibt Folgendes aus:

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]

Die FluidIterableKlasse stellt lediglich einen Wrapper für das ursprüngliche Listenobjekt bereit. Auf das ursprüngliche Objekt kann als Eigenschaft des flüssigen Objekts wie folgt zugegriffen werden:

originalList = fluidArr.fixedIterable

Weitere Beispiele / Tests finden Sie im if __name__ is "__main__":Abschnitt unten fluidIter.py. Diese sind einen Blick wert, weil sie erklären, was in verschiedenen Situationen passiert. Beispiel: Ersetzen eines großen Teils der Liste mithilfe eines Slice. Oder Sie verwenden (und ändern) dasselbe iterable in verschachtelten for-Schleifen.

Wie ich bereits sagte: Dies ist eine komplizierte Lösung, die die Lesbarkeit Ihres Codes beeinträchtigt und das Debuggen erschwert. Daher sollten zuerst andere Lösungen wie das in David Raznicks Antwort erwähnte Listenverständnis in Betracht gezogen werden. Abgesehen davon habe ich Zeiten gefunden, in denen diese Klasse für mich nützlich und einfacher zu verwenden war, als die Indizes der Elemente zu verfolgen, die gelöscht werden müssen.


Bearbeiten: Wie in den Kommentaren erwähnt, stellt diese Antwort kein Problem dar, für das dieser Ansatz eine Lösung bietet. Ich werde versuchen, das hier anzusprechen:

Listenverständnisse bieten eine Möglichkeit, eine neue Liste zu erstellen. Bei diesen Ansätzen wird jedoch jedes Element isoliert betrachtet und nicht der aktuelle Status der Liste als Ganzes.

dh

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

Was aber, wenn das Ergebnis von den testFuncElementen abhängt, die bereits hinzugefügt wurden newList? Oder die Elemente noch inoldList könnten die noch darin enthaltenen als nächstes hinzugefügt werden? Es gibt zwar immer noch eine Möglichkeit, ein Listenverständnis zu verwenden, aber es verliert allmählich seine Eleganz, und für mich ist es einfacher, eine Liste an Ort und Stelle zu ändern.

Der folgende Code ist ein Beispiel für einen Algorithmus, der unter dem oben genannten Problem leidet. Der Algorithmus reduziert eine Liste so, dass kein Element ein Vielfaches eines anderen Elements ist.

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)

Die Ausgabe und die endgültige reduzierte Liste werden unten angezeigt

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]
Resonanz
quelle
Es ist schwer zu sagen, ob dies überentwickelt ist, da unklar ist, welches Problem es zu lösen versucht. Was erreicht das Entfernen von Elementen mit diesem Ansatz, was some_list[:] = [x for x in some_list if not some_condition(x)]nicht erreicht wird? Warum sollte jemand ohne eine Antwort darauf glauben, dass das Herunterladen und Verwenden Ihrer 600-Zeilen-Bibliothek mit Tippfehlern und auskommentiertem Code eine bessere Lösung für sein Problem darstellt als der Einzeiler? -1.
Mark Amery
@ MarkAmery. Der Hauptanwendungsfall, wenn dies der Fall ist, wenn versucht wird, zu bestimmen, ob ein Element entfernt (oder hinzugefügt oder verschoben) werden soll, basierend nicht nur auf dem Element selbst, sondern auf dem Status eines anderen Elements in der Liste oder dem Status der Liste als ganze. Zum Beispiel ist es mit Listenkomprehensionen nicht möglich , etwas wie zu schreiben , some_list[:] = [x for x in some_list if not some_condition(y)]wo yein anderes Listenelement aus x. Es wäre auch nicht möglich zu schreiben some_list[:] = [x for x in some_list if not some_condition(intermediateStateOf_some_list)].
Resonanz
2

Die effektivste Methode ist das Listenverständnis, viele Leute zeigen ihren Fall, natürlich ist es auch ein guter Weg, um iteratordurchzukommen filter.

Filtererhält eine Funktion und eine Sequenz. FilterWendet die übergebene Funktion nacheinander auf jedes Element an und entscheidet dann, ob das Element beibehalten oder verworfen werden soll, je nachdem, ob der Funktionsrückgabewert Trueoder ist False.

Es gibt ein Beispiel (erhalten Sie die Gewinnchancen im Tupel):

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

Achtung: Sie können auch nicht mit Iteratoren umgehen. Iteratoren sind manchmal besser als Sequenzen.

chseng
quelle
2

Die for-Schleife wird durch den Index iteriert.

Betrachten Sie eine Liste,

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

Sie haben die Listenvariable aufgerufen lis. und Sie verwenden das gleiche, um zu entfernen ..

Ihre Variable

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

während der 5. Iteration,

Ihre Nummer 35 war keine Primzahl, also haben Sie sie von einer Liste entfernt.

lis.remove(y)

und dann geht der nächste Wert (65) zum vorherigen Index über.

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

Der Zeiger für die 4. Iteration wurde auf den 5. verschoben.

Aus diesem Grund deckt Ihre Schleife 65 nicht ab, da sie in den vorherigen Index verschoben wurde.

Sie sollten die Liste daher nicht in eine andere Variable verweisen, die weiterhin auf das Original anstatt auf die Kopie verweist.

ite = lis #dont do it will reference instead copy

Kopieren Sie die Liste mit list[::]

jetzt wirst du es geben,

[5, 7, 13, 29]

Das Problem ist, dass Sie während der Iteration einen Wert aus einer Liste entfernt haben und Ihr Listenindex dann zusammenbricht.

Sie können stattdessen versuchen, das Verständnis zu verbessern.

welches alle iterierbaren wie, Liste, Tupel, Diktat, Zeichenfolge usw. unterstützt

Mohideen bin Mohammed
quelle
Dies half mir zu verstehen, warum mein Code fehlschlug.
Wahid Sadik
2

Wenn Sie während der Iteration Elemente aus einer Liste löschen möchten, verwenden Sie eine while-Schleife, damit Sie den aktuellen Index und den Endindex nach jedem Löschen ändern können.

Beispiel:

i = 0
length = len(list1)

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

    i += 1
Kein Name
quelle
1

Die anderen Antworten sind richtig, dass es normalerweise eine schlechte Idee ist, aus einer Liste zu löschen, die Sie iterieren. Durch die umgekehrte Iteration werden die Fallstricke vermieden, aber es ist viel schwieriger, dem Code zu folgen, der dies tut. Daher ist es normalerweise besser, ein Listenverständnis zu verwenden oder filter.

Es gibt jedoch einen Fall, in dem es sicher ist, Elemente aus einer Sequenz zu entfernen, die Sie iterieren: Wenn Sie nur ein Element entfernen, während Sie iterieren. Dies kann mit a returnoder a sichergestellt werden break. Zum Beispiel:

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

Dies ist oft einfacher zu verstehen als ein Listenverständnis, wenn Sie einige Vorgänge mit Nebenwirkungen auf das erste Element in einer Liste ausführen, die eine bestimmte Bedingung erfüllen, und dieses Element unmittelbar danach aus der Liste entfernen.

Beefster
quelle
1

Ich kann mir drei Ansätze vorstellen, um Ihr Problem zu lösen. Als Beispiel werde ich eine zufällige Liste von Tupeln erstellen somelist = [(1,2,3), (4,5,6), (3,6,6), (7,8,9), (15,0,0), (10,11,12)]. Die Bedingung, die ich wähle, ist sum of elements of a tuple = 15. In der endgültigen Liste haben wir nur die Tupel, deren Summe nicht gleich 15 ist.

Was ich ausgewählt habe, ist ein zufällig ausgewähltes Beispiel. Fühlen Sie sich frei, die Liste der Tupel und die von mir gewählte Bedingung zu ändern .

Methode 1.> Verwenden Sie das von Ihnen vorgeschlagene Framework (wobei ein Code in eine for-Schleife eingefügt wird). Ich verwende einen kleinen Code mit del, um ein Tupel zu löschen, das diese Bedingung erfüllt. Bei diesem Verfahren fehlt jedoch ein Tupel (das die genannte Bedingung erfüllt), wenn zwei nacheinander platzierte Tupel die gegebene Bedingung erfüllen.

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

Methode 2.> Erstellen Sie eine neue Liste, die Elemente (Tupel) enthält, bei denen die angegebene Bedingung nicht erfüllt ist (dies entspricht dem Entfernen von Listenelementen, bei denen die angegebene Bedingung erfüllt ist). Es folgt der Code dafür:

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

Methode 3.> Suchen Sie nach Indizes, bei denen die angegebene Bedingung erfüllt ist, und verwenden Sie dann Elemente entfernen (Tupel), die diesen Indizes entsprechen. Es folgt der Code dafür.

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

Methode 1 und Methode 2 sind schneller als Methode 3 . Methode2 und Methode3 sind effizienter als Methode1. Ich bevorzuge Methode2 . Für das vorgenannte Beispieltime(method1) : time(method2) : time(method3) = 1 : 1 : 1.7

Siddharth Satpathy
quelle
0

Für alles, was das Potenzial hat, wirklich groß zu sein, verwende ich Folgendes.

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)

Das sollte deutlich schneller sein als alles andere.

ZENTURIO
quelle
Nach meinen Messungen ist NumPy für Listen mit mehr als 20 Elementen schneller und erreicht bei großen Listen mit 1000 Elementen und mehr eine> 12-fach schnellere Filterung.
Georgy
0

In einigen Situationen, in denen Sie mehr tun, als nur eine Liste einzeln zu filtern, soll sich Ihre Iteration während der Iteration ändern.

Hier ist ein Beispiel, in dem das vorherige Kopieren der Liste falsch ist, eine umgekehrte Iteration nicht möglich ist und ein Listenverständnis ebenfalls keine Option ist.

""" 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
quelle
0

Wenn Sie die neue Liste später verwenden, können Sie das Element einfach auf Keine setzen und es dann in der späteren Schleife wie folgt beurteilen

for i in li:
    i = None

for elem in li:
    if elem is None:
        continue

Auf diese Weise müssen Sie die Liste nicht kopieren und es ist einfacher zu verstehen.

John Zhang
quelle
-1

Wenn Sie eine Liste mit Zahlen erstellen, möchten Sie alle Nein entfernen, die durch 3 teilbar sind.

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

Mit list comprehensionwird eine neue Liste erstellt und neuer Speicherplatz erstellt

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

Mit der lambda filterFunktion wird eine neue Liste erstellt und Speicherplatz belegt

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

ohne Speicherplatz für neue Liste zu verbrauchen und vorhandene Liste zu ändern

for index, value in enumerate(list_number):
    if list_number[index]%3==0:
        list_number.remove(value)
sahasrara62
quelle