Come rimuovere elementi da un elenco durante l'iterazione?


934

Sto ripetendo un elenco di tuple in Python e sto tentando di rimuoverle se soddisfano determinati criteri.

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

Cosa dovrei usare al posto di code_to_remove_tup? Non riesco a capire come rimuovere l'oggetto in questo modo.


La maggior parte delle risposte in questa pagina non spiega davvero perché la rimozione di elementi durante l'iterazione su un elenco produca risultati strani, ma la risposta accettata in questa domanda lo fa , ed è probabilmente una soluzione migliore per i principianti che incontrano questo problema per la prima volta.
Ggorlen,

Risposte:


827

È possibile utilizzare una comprensione dell'elenco per creare un nuovo elenco contenente solo gli elementi che non si desidera rimuovere:

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

Oppure, assegnando alla sezione somelist[:], è possibile mutare l'elenco esistente per contenere solo gli elementi desiderati:

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

Questo approccio potrebbe essere utile se ci sono altri riferimenti a somelist che devono riflettere i cambiamenti.

Invece di una comprensione, potresti anche usare itertools. In Python 2:

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

O in Python 3:

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

Per motivi di chiarezza e per coloro che trovano l'uso della [:]notazione hacking o fuzzy, ecco un'alternativa più esplicita. Teoricamente, dovrebbe funzionare allo stesso modo per quanto riguarda lo spazio e il tempo rispetto alle linee di cui sopra.

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

Funziona anche in altre lingue che potrebbero non avere la possibilità di sostituire gli elementi degli elenchi Python, con modifiche minime. Ad esempio, non tutte le lingue trasmettono elenchi vuoti a Falsecome fa Python. Puoi sostituire while somelist:qualcosa di più esplicito come while len(somelist) > 0:.


4
Puoi renderlo più veloce se sai che solo alcuni verranno eliminati, ovvero eliminali solo e lascia gli altri sul posto anziché riscriverli?
highBandWidth,

20
E se la mia lista fosse enorme e non potessi permettermi di fare una copia?
jpcgt,

15
@jpcgt Dovresti usare somelist[:] = (x for x in somelist if determine(x))questo creerà un generatore che potrebbe non creare copie non necessarie.
Rostislav Kondratenko,

8
@RostislavKondratenko: list_ass_slice()funzione che implementa le somelist[:]=chiamate PySequence_Fast()internamente. Questa funzione restituisce sempre un elenco, ovvero la soluzione di @Alex Martelli che utilizza già un elenco anziché un generatore è molto probabilmente più efficiente
jfs

6
Ti andrebbe di spiegare quali sono le differenze tra l'assegnazione della comprensione dell'elenco all'elenco e il clone dell'elenco, per favore? L'elenco originale non somelistsarebbe mutato in entrambi i metodi?
Bowen Liu,

589

Le risposte che suggeriscono la comprensione dell'elenco sono QUASI corrette - tranne per il fatto che costruiscono un elenco completamente nuovo e quindi gli danno lo stesso nome del vecchio elenco, NON modificano il vecchio elenco in atto. È diverso da quello che faresti con la rimozione selettiva, come nel suggerimento di @ Lennart : è più veloce, ma se si accede alla tua lista tramite più riferimenti il ​​fatto che stai semplicemente riposizionando uno dei riferimenti e NON alterando l'oggetto elenco stesso può portare a bug sottili e disastrosi.

Fortunatamente, è estremamente facile ottenere sia la velocità di comprensione dell'elenco sia la semantica richiesta dell'alterazione sul posto - basta codificare:

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

Nota la sottile differenza con le altre risposte: questa NON si sta assegnando a un nome a barre - sta assegnando a una sezione di elenco che sembra essere l'intero elenco, sostituendo così il contenuto dell'elenco all'interno dello stesso oggetto elenco Python , piuttosto che semplicemente riposizionare un riferimento (dall'oggetto elenco precedente al nuovo oggetto elenco) come le altre risposte.


1
Come posso fare lo stesso compito suddiviso con un dict? In Python 2.6?
PaulMcG

11
@Paul: poiché i dadi non sono ordinati, le sezioni non hanno significato per i dadi. Se si desidera sostituire il contenuto di dict acon il contenuto di dict b, utilizzare a.clear(); a.update(b).
Sven Marnach,

1
Perché 'riposizionare' uno dei riferimenti sostituendo ciò a cui la variabile fa riferimento per causare bug? Sembra che sarebbe solo un potenziale problema nelle applicazioni multi-thread, non a thread singolo.
Derek Dahmer,

59
@Derek x = ['foo','bar','baz']; y = x; x = [item for item in x if determine(item)];Questo riassegna xal risultato della comprensione della lista, ma yfa ancora riferimento alla lista originale['foo','bar','baz'] . Se ti aspettavi xe fai yriferimento allo stesso elenco, potresti aver introdotto dei bug. È impedire questo assegnando ad una fetta di tutta la lista, come dimostra Alex, e mi mostra qui: x = ["foo","bar","baz"]; y = x; x[:] = [item for item in x if determine(item)];. L'elenco è stato modificato al suo posto. assicurando che tutti i riferimenti all'elenco (entrambi xe yqui) facciano riferimento al nuovo elenco.
Steven T. Snyder,

infatti, anche l'uso della filterfunzione crea un nuovo elenco, non modifica gli elementi sul posto ... soloolist[:] = [i for i in olist if not dislike(i)]
John Strood

303

Devi prendere una copia dell'elenco e iterare prima su di esso, altrimenti l'iterazione fallirà con risultati che potrebbero essere imprevisti.

Ad esempio (dipende dal tipo di elenco):

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

Un esempio:

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

13
@Zen Perché il secondo scorre su una copia dell'elenco. Pertanto, quando si modifica l'elenco originale, non si modifica la copia su cui si esegue l'iterazione.
Lennart Regebro,

3
Cosa c'è di meglio nel fare una lista [:] rispetto all'elenco (una lista)?
Mariusz Jamro,

3
list(somelist)convertirà un iterabile in un elenco. somelist[:]crea una copia di un oggetto che supporta il slicing. Quindi non necessariamente fanno la stessa cosa. In questo caso voglio fare una copia somelistdell'oggetto, quindi uso[:]
Lennart Regebro,

33
Nota a chiunque legga questo, questo è MOLTO lento per gli elenchi. remove()deve andare su TUTTA la lista per ogni iterazione, quindi ci vorrà per sempre.
vitiral,

7
Il Big O time non ha importanza quando si tratta di elenchi di solo una dozzina di articoli. Spesso chiaro e semplice da capire per i futuri programmatori è molto più prezioso delle prestazioni.
Steve,

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

Devi andare indietro, altrimenti è un po 'come segare il ramo su cui sei seduto :-)

Utenti Python 2: sostituisci rangecon xrangeper evitare di creare un elenco hardcoded


13
Nelle recenti versioni di Python, puoi farlo in modo ancora più pulito usando il reversed()comando incorporato
ncoghlan del

16
reversed () non crea un nuovo elenco, crea un iteratore inverso sulla sequenza fornita. Come enumerate (), devi racchiuderlo in list () per estrarne effettivamente un elenco. Si può pensare di ordinato (), che fa creare un nuovo elenco ogni volta (che deve, in modo che possa risolvere la cosa).
ncoghlan,

1
@Mauris perché enumeraterestituisce un iteratore e si reversedaspetta una sequenza. Immagino che potresti farlo reversed(list(enumerate(somelist)))se non ti dispiace creare un elenco aggiuntivo in memoria.
drevicko,

2
Questo è O (N * M) per gli array, è molto lento se si rimuovono molti elementi da un elenco di grandi dimensioni. Quindi non raccomandato.
Sam Watkins,

2
@SamWatkins Sì, questa risposta è per quando rimuovi un paio di elementi da un array molto grande. Meno utilizzo della memoria, ma può essere mtempi più lenti.
Navin,

52

Tutorial ufficiale su Python 2 4.2. "per dichiarazioni"

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

Questa parte della documentazione chiarisce che:

  • è necessario creare una copia dell'elenco ripetuto per modificarlo
  • un modo per farlo è con la notazione slice [:]

Se è necessario modificare la sequenza su cui si sta eseguendo l'iterazione all'interno del ciclo (ad esempio per duplicare gli elementi selezionati), si consiglia di effettuare prima una copia. L'iterazione su una sequenza non crea implicitamente una copia. La notazione delle sezioni rende questo particolarmente conveniente:

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

Documentazione di Python 2 7.3. "La dichiarazione for"

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

Questa parte della documentazione afferma ancora una volta che è necessario effettuare una copia e fornisce un esempio di rimozione effettiva:

Nota: c'è una sottigliezza quando la sequenza viene modificata dal loop (ciò può avvenire solo per sequenze mutabili, ovvero elenchi). Un contatore interno viene utilizzato per tenere traccia di quale elemento viene utilizzato successivamente, e questo viene incrementato ad ogni iterazione. Quando questo contatore ha raggiunto la lunghezza della sequenza, il loop termina. Ciò significa che se la suite elimina l'elemento corrente (o precedente) dalla sequenza, l'elemento successivo verrà ignorato (poiché ottiene l'indice dell'elemento corrente che è già stato trattato). Allo stesso modo, se la suite inserisce un elemento nella sequenza prima dell'elemento corrente, l'elemento corrente verrà nuovamente trattato la volta successiva attraverso il ciclo. Questo può portare a fastidiosi bug che possono essere evitati facendo una copia temporanea usando una porzione dell'intera sequenza, ad es.

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

Tuttavia, non sono d'accordo con questa implementazione, dal momento che .remove()deve iterare l' intero elenco per trovare il valore.

Migliori soluzioni alternative

O:

In genere vuoi solo andare per il più veloce .append() opzione per impostazione predefinita a meno che la memoria non sia un grosso problema.

Python potrebbe farlo meglio?

Sembra che questa particolare API Python possa essere migliorata. Confrontalo, ad esempio, con:

  • Java ListIterator :: rimuovere quali documenti "Questa chiamata può essere effettuata una sola volta per chiamata al successivo o precedente"
  • C ++ std::vector::eraseche restituisce un interatore valido per l'elemento dopo quello rimosso

entrambi chiariscono chiaramente che non è possibile modificare un elenco in fase di iterazione se non con lo stesso iteratore, e offre modi efficaci per farlo senza copiare l'elenco.

Forse la logica di fondo è che si presume che gli elenchi di Python siano supportati da array dinamici, e quindi qualsiasi tipo di rimozione sarà comunque inefficiente in termini di tempo, mentre Java ha una gerarchia di interfaccia migliore con entrambe ArrayListe LinkedListimplementazioni di ListIterator.

Nemmeno sembra esserci un tipo di elenco di collegamenti espliciti nello stdlib di Python : Elenco di collegamenti Python


48

Il tuo miglior approccio per un tale esempio sarebbe la comprensione di un elenco

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

Nei casi in cui stai facendo qualcosa di più complesso rispetto alla chiamata di una determinefunzione, preferisco costruire un nuovo elenco e semplicemente aggiungerlo mentre procedo. Per esempio

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

Copiare l'elenco usando removepotrebbe rendere il tuo codice un po 'più pulito, come descritto in una delle risposte di seguito. Non dovresti assolutamente farlo per elenchi estremamente grandi, poiché ciò comporta prima di tutto la copia dell'intero elenco e anche l'esecuzione di unO(n) remove un'operazione per ogni elemento rimosso, trasformandolo in un O(n^2)algoritmo.

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

37

Per coloro che amano la programmazione funzionale:

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

o

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

1. La comprensione dell'elenco e le espressioni del generatore sono prese in prestito da Haskell, un linguaggio funzionale puro; sono esattamente funzionali come filter, e più Pythonic. 2. Se hai bisogno lambdadi usare mapo filter, l'elenco comp o genexpr è sempre l'opzione migliore; mape filterpuò essere leggermente più veloce quando la funzione di trasformazione / predicato è un built-in Python implementato in C e l'iterabile non è banalmente piccolo, ma sono sempre più lenti quando hai bisogno di un lambdache il listcomp / genexpr potrebbe evitare.
ShadowRanger il

13

Avevo bisogno di farlo con un elenco enorme e duplicare l'elenco sembrava costoso, soprattutto perché nel mio caso il numero di eliminazioni sarebbe stato ridotto rispetto agli elementi rimasti. Ho adottato questo approccio di basso livello.

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

Quello che non so è quanto siano efficienti un paio di eliminazioni rispetto alla copia di un grande elenco. Per favore commenta se hai qualche idea.


Nel mio caso, devo spostare quegli elementi "indesiderati" in un altro elenco. Hai qualche nuovo commento su questa soluzione? Penso anche che sia meglio usare alcune eliminazioni invece di duplicare l'elenco.
gustavovelascoh,

Questa è la risposta giusta se le prestazioni sono un problema (sebbene uguale a @Alexey). Detto questo, la scelta di listcome struttura di dati in primo luogo dovrebbe essere attentamente considerata poiché la rimozione dal centro di un elenco richiede un tempo lineare nella lunghezza dell'elenco. Se non hai davvero bisogno di un accesso casuale all'elemento sequenziale k-esimo, potresti considerare OrderedDict?
max

@GVelascoh perché non creare newlist = [], e poi newlist.append(array[i])poco prima del array[i]?
max

2
Si noti che questo è probabilmente tempo inefficiente: se list()è un elenco collegato, l'accesso casuale è costoso, se list()è un array, le eliminazioni sono costose perché richiedono di spostare in avanti tutti gli elementi seguenti. Un iteratore decente potrebbe rendere le cose buone per l'implementazione dell'elenco collegato. Questo potrebbe tuttavia essere efficiente in termini di spazio.
Ciro Santilli 5 冠状 病 六四 事件 法轮功

10

Potrebbe anche essere intelligente creare anche un nuovo elenco se l'elemento dell'elenco corrente soddisfa i criteri desiderati.

così:

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

e per evitare di dover ricodificare l'intero progetto con il nome della nuova lista:

originalList[:] = newList

nota, dalla documentazione di Python:

copy.copy (x) Restituisce una copia superficiale di x.

copy.deepcopy (x) Restituisce una copia profonda di x.


3
Ciò non aggiunge nuove informazioni che non erano presenti nella risposta accettata anni prima.
Mark Amery,

2
È semplice e solo un altro modo di vedere un problema @MarkAmery. È meno condensato per quelle persone a cui non piace la sintassi della codifica compressa.
ntk4,

9

Questa risposta è stata originariamente scritta in risposta a una domanda che è stata contrassegnata come duplicata: Rimozione delle coordinate dall'elenco su Python

Esistono due problemi nel codice:

1) Quando si utilizza remove (), si tenta di rimuovere numeri interi mentre è necessario rimuovere una tupla.

2) Il ciclo for salterà gli elementi nell'elenco.

Esaminiamo cosa succede quando eseguiamo il tuo codice:

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

Il primo problema è che stai passando 'a' e 'b' per rimuovere (), ma remove () accetta solo un singolo argomento. Quindi, come possiamo ottenere remove () per funzionare correttamente con il tuo elenco? Dobbiamo capire qual è ogni elemento del tuo elenco. In questo caso, ognuno è una tupla. Per vedere questo, accediamo a un elemento dell'elenco (l'indicizzazione inizia da 0):

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

Aha! Ogni elemento di L1 è in realtà una tupla. Quindi è quello che dobbiamo passare per rimuovere (). Le tuple in pitone sono molto semplici, sono semplicemente create racchiudendo i valori tra parentesi. "a, b" non è una tupla, ma "(a, b)" è una tupla. Quindi modifichiamo il codice ed eseguiamo di nuovo:

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

Questo codice viene eseguito senza errori, ma diamo un'occhiata all'elenco che genera:

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

Perché (1, -2) è ancora nella tua lista? Si scopre che modificare l'elenco mentre si utilizza un ciclo per scorrere su di esso è una pessima idea senza cure particolari. Il motivo per cui (1, -2) rimane nell'elenco è che le posizioni di ciascun elemento all'interno dell'elenco sono cambiate tra le iterazioni del ciclo for. Diamo un'occhiata a cosa succede se alimentiamo il codice sopra un elenco più lungo:

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

Come puoi dedurre da quel risultato, ogni volta che l'istruzione condizionale viene valutata vera e viene rimosso un elemento dell'elenco, la successiva iterazione del ciclo salterà la valutazione dell'elemento successivo nell'elenco perché i suoi valori si trovano ora in indici diversi.

La soluzione più intuitiva è copiare l'elenco, quindi scorrere l'elenco originale e modificare solo la copia. Puoi provare a farlo in questo modo:

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)

Tuttavia, l'output sarà identico a prima:

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

Questo perché quando abbiamo creato L2, Python non ha effettivamente creato un nuovo oggetto. Invece, faceva semplicemente riferimento a L2 allo stesso oggetto di L1. Possiamo verificarlo con 'is' che è diverso dal semplice "uguale" (==).

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

Possiamo fare una copia vera usando copy.copy (). Quindi tutto funziona come previsto:

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

Infine, esiste una soluzione più pulita rispetto alla necessità di creare una copia completamente nuova di L1. La funzione inversa ():

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

Sfortunatamente, non riesco a descrivere adeguatamente come funziona reverse (). Restituisce un oggetto 'listreverseiterator' quando gli viene passato un elenco. Ai fini pratici, puoi pensarlo come creare una copia rovesciata del suo argomento. Questa è la soluzione che raccomando.


4

Se vuoi fare qualcos'altro durante l'iterazione, può essere utile ottenere sia l'indice (che ti garantisce di poterlo fare riferimento, ad esempio se hai un elenco di dadi) sia il contenuto effettivo dell'elemento dell'elenco.

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]

enumerateti dà accesso all'elemento e all'indice in una sola volta. reversedè così che gli indici che eliminerai in seguito non cambieranno su di te.


Perché ottenere l'indice è più rilevante nel caso in cui si abbia un elenco di dicts che nel caso di qualsiasi altro tipo di elenco? Questo non ha senso per quanto posso dire.
Mark Amery,


4

La maggior parte delle risposte qui vuole che tu crei una copia dell'elenco. Ho avuto un caso d'uso in cui l'elenco era piuttosto lungo (110K articoli) ed era invece più intelligente continuare a ridurre l'elenco.

Prima di tutto dovrai sostituire foreach loop con while loop ,

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

Il valore di inon viene modificato nel blocco if perché vorrai ottenere il valore del nuovo elemento DALLO STESSO INDICE, una volta eliminato il vecchio elemento.


3

Puoi provare a eseguire il ciclo continuo al contrario, quindi per some_list farai qualcosa del tipo:

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)

In questo modo l'indice è allineato e non risente degli aggiornamenti dell'elenco (indipendentemente dal fatto che si pop o meno l'elemento cur).


Il ciclo reversed(list(enumerate(some_list)))sarebbe più semplice del calcolo degli indici da soli.
Mark Amery,

@MarkAmery non pensa di poter modificare l'elenco in questo modo.
Queequeg,

3

Una possibile soluzione, utile se si desidera non solo rimuovere alcune cose, ma anche fare qualcosa con tutti gli elementi in un singolo ciclo:

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

Dovresti semplicemente usare le comprensioni. Sono molto più facili da capire.
Beefster,

E se volessi rimuovere le badcose, fare qualcosa con essa e anche fare qualcosa con le goodcose in un ciclo?
Alexey,

1
In realtà, ho capito che c'è un po 'di intelligenza qui nel fatto che fai una copia dell'elenco con una sezione aperta ( alist[:]) E dal momento che potresti fare qualcosa di fantasia, in realtà ha un caso d'uso. Una buona revisione è buona. Prendi il mio voto.
Beefster

2

Avevo bisogno di fare qualcosa di simile e nel mio caso il problema era la memoria: avevo bisogno di unire più oggetti del set di dati all'interno di un elenco, dopo aver fatto alcune cose con loro, come un nuovo oggetto, e dovevo liberarmi di ogni voce a cui mi stavo unendo evitare di duplicarli tutti e far esplodere la memoria. Nel mio caso, avere gli oggetti in un dizionario anziché in un elenco ha funzionato bene:

`` `

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

`` `


2

TLDR:

Ho scritto una libreria che ti permette di fare questo:

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'

È preferibile utilizzare un altro metodo, se possibile, che non richiede la modifica dell'iterabile durante l'iterazione su di esso, ma per alcuni algoritmi potrebbe non essere così semplice. E quindi se sei sicuro di volere davvero il modello di codice descritto nella domanda originale, è possibile.

Dovrebbe funzionare su tutte le sequenze mutabili non solo sugli elenchi.


Risposta completa:

Modifica: l'ultimo esempio di codice in questa risposta fornisce un caso d'uso per cui a volte potresti voler modificare un elenco in luogo piuttosto che usare una comprensione dell'elenco. La prima parte delle risposte serve come tutorial su come un array può essere modificato sul posto.

La soluzione segue da questo risposta (per una domanda correlata) di Senderle. Il che spiega come viene aggiornato l'indice dell'array durante l'iterazione di un elenco che è stato modificato. La soluzione seguente è progettata per tracciare correttamente l'indice dell'array anche se l'elenco viene modificato.

Scarica fluidIter.pyda qui https://github.com/alanbacon/FluidIterator , è solo un singolo file, quindi non è necessario installare git. Non esiste un programma di installazione, quindi sarà necessario assicurarsi che il file si trovi nel percorso Python. Il codice è stato scritto per Python 3 ed è non testato su Python 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))

Ciò produrrà il seguente output:

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]

Sopra abbiamo usato il popmetodo sull'oggetto lista fluidi. Altri metodi iterabili comuni sono anche implementate come del fluidL[i], .remove, .insert, .append, .extend. L'elenco può anche essere modificato usando le sezioni ( sortereverse metodi non sono implementati).

L'unica condizione è che devi solo modificare l'elenco in atto, se in qualsiasi momento fluidLo lfosse riassegnato a un oggetto elenco diverso il codice non funzionerebbe. L' fluidLoggetto originale verrebbe comunque utilizzato dal ciclo for ma diventerebbe fuori portata per noi da modificare.

vale a dire

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

Se vogliamo accedere al valore dell'indice corrente dell'elenco non possiamo usare enumerare, poiché ciò conta solo quante volte è stato eseguito il ciclo for. Invece useremo direttamente l'oggetto iteratore.

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

Questo produrrà quanto segue:

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]

La FluidIterableclasse fornisce solo un wrapper per l'oggetto elenco originale. L'oggetto originale è accessibile come una proprietà dell'oggetto fluido in questo modo:

originalList = fluidArr.fixedIterable

Altri esempi / test sono disponibili nella if __name__ is "__main__":sezione in fondo a fluidIter.py. Vale la pena guardarli perché spiegano cosa succede in varie situazioni. Come ad esempio: sostituire una grande sezione dell'elenco usando una sezione. O usando (e modificando) lo stesso iterabile in loop annidati.

Come ho affermato per iniziare: questa è una soluzione complicata che danneggerà la leggibilità del codice e renderà più difficile il debug. Pertanto, altre soluzioni come la comprensione dell'elenco menzionata nella risposta di David Raznick dovrebbero essere considerate per prime. Detto questo, ho trovato momenti in cui questa classe mi è stata utile ed è stata più facile da usare che tenere traccia degli indici degli elementi che devono essere eliminati.


Modifica: come menzionato nei commenti, questa risposta non presenta realmente un problema per il quale questo approccio fornisce una soluzione. Proverò ad affrontarlo qui:

La comprensione dell'elenco fornisce un modo per generare un nuovo elenco, ma questi approcci tendono a considerare ogni elemento in modo isolato piuttosto che lo stato corrente dell'elenco nel suo insieme.

vale a dire

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

Ma cosa succede se il risultato di testFuncdipende dagli elementi che sono newListgià stati aggiunti ? O gli elementi ancora dentrooldList potrebbero essere aggiunti successivamente? Potrebbe esserci ancora un modo per utilizzare la comprensione di un elenco, ma inizierà a perdere la sua eleganza e per me è più facile modificare un elenco in atto.

Il codice seguente è un esempio di un algoritmo che soffre del problema precedente. L'algoritmo ridurrà un elenco in modo che nessun elemento sia multiplo di qualsiasi altro elemento.

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)

L'output e l'elenco ridotto finale sono mostrati di seguito

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]

È difficile dire se questo è troppo ingegnerizzato perché non è chiaro quale problema sta cercando di risolvere; cosa ottiene la rimozione di elementi utilizzando questo approccio che some_list[:] = [x for x in some_list if not some_condition(x)]non raggiunge? Senza una risposta a questo, perché qualcuno dovrebbe credere che il download e l'utilizzo della libreria di 600 righe completa di errori di battitura e codice commentato sia una soluzione migliore al loro problema rispetto al one-liner? -1.
Mark Amery,

@MarkAmery. Il caso d'uso principale per quando questo è quando si tenta di determinare se un elemento deve essere rimosso (o aggiunto o spostato) in base non solo all'elemento stesso, ma allo stato di un altro elemento nell'elenco o allo stato dell'elenco come totale. Ad esempio, con la comprensione dell'elenco non è possibile scrivere qualcosa come some_list[:] = [x for x in some_list if not some_condition(y)]dove si ytrova un elemento di elenco diverso x. Né sarebbe possibile scrivere some_list[:] = [x for x in some_list if not some_condition(intermediateStateOf_some_list)].
Risonanza,

2

Il metodo più efficace è di lista, molte persone mostrano il loro caso, naturalmente, è anche un buon modo per ottenere un iteratortramite filter.

Filterriceve una funzione e una sequenza. Filterapplica a turno la funzione passata a ciascun elemento, quindi decide se conservare o scartare l'elemento a seconda che il valore restituito dalla funzione sia Trueo False.

C'è un esempio (ottieni le probabilità nella tupla):

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

Attenzione: non è inoltre possibile gestire gli iteratori. Gli iteratori sono talvolta migliori delle sequenze.


2

per il ciclo sarà iterare attraverso l'indice ..

considera di avere un elenco,

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

hai utilizzato la variabile elenco chiamata lis. e stai usando lo stesso per rimuovere ..

la tua variabile

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

durante la quinta iterazione,

il tuo numero 35 non era un numero primo, quindi l'hai rimosso da un elenco.

lis.remove(y)

e quindi il valore successivo (65) passa all'indice precedente.

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

così puntatore 4 ° iterazione fatto spostato su 5 ° ..

ecco perché il tuo loop non copre 65 poiché è stato spostato nell'indice precedente.

quindi non dovresti fare riferimento all'elenco in un'altra variabile che fa ancora riferimento all'originale anziché alla copia.

ite = lis #dont do it will reference instead copy

quindi copia dell'elenco usando list[::]

ora tu darai

[5, 7, 13, 29]

Il problema è che hai rimosso un valore da un elenco durante l'iterazione, quindi l'indice dell'elenco crollerà.

così puoi provare invece la comprensione.

che supporta tutti gli iterabili come, list, tuple, dict, string ecc


Questo mi ha aiutato a capire perché il mio codice non andava a buon fine.
Wahid Sadik,

2

Se si desidera eliminare elementi da un elenco durante l'iterazione, utilizzare un ciclo while in modo da poter modificare l'indice corrente e terminare l'indice dopo ogni eliminazione.

Esempio:

i = 0
length = len(list1)

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

    i += 1

1

Le altre risposte sono corrette che di solito è una cattiva idea eliminare da un elenco che stai ripetendo. L'iterazione inversa evita le insidie, ma è molto più difficile seguire il codice che lo fa, quindi di solito è meglio usare una comprensione dell'elenco o filter.

Vi è, tuttavia, un caso in cui è sicuro rimuovere elementi da una sequenza che stai ripetendo: se rimuovi solo un elemento mentre stai ripetendo. Questo può essere garantito usando a returno a break. Per esempio:

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

Questo è spesso più facile da comprendere di una comprensione dell'elenco quando si eseguono alcune operazioni con effetti collaterali sul primo elemento in un elenco che soddisfa alcune condizioni e quindi la rimozione di tale elemento dall'elenco immediatamente dopo.


1

Posso pensare a tre approcci per risolvere il tuo problema. Ad esempio, creerò un elenco casuale di tuple somelist = [(1,2,3), (4,5,6), (3,6,6), (7,8,9), (15,0,0), (10,11,12)]. La condizione che scelgo è sum of elements of a tuple = 15. Nell'elenco finale avremo solo quelle tuple la cui somma non è uguale a 15.

Quello che ho scelto è un esempio scelto a caso. Sentiti libero di cambiare l' elenco delle tuple e le condizioni che ho scelto.

Metodo 1.> Usa il framework che avevi suggerito (dove si compila un codice all'interno di un ciclo for). Uso un piccolo codice con delper eliminare una tupla che soddisfa tale condizione. Tuttavia, questo metodo mancherà una tupla (che soddisfa tale condizione) se due tuple posizionate consecutivamente soddisfano la condizione data.

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

Metodo 2.> Costruisci un nuovo elenco che contiene elementi (tuple) in cui la condizione data non è soddisfatta (è la stessa cosa della rimozione di elementi dell'elenco in cui la condizione data è soddisfatta). Di seguito è riportato il codice per questo:

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

Metodo 3.> Trova gli indici in cui è soddisfatta la condizione specificata, quindi usa Rimuovi elementi (tuple) corrispondenti a quegli indici. Di seguito è riportato il codice per questo.

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

Il metodo 1 e il metodo 2 sono più veloci del metodo 3 . Method2 e method3 sono più efficienti di method1. Io preferisco metodo2 . Per l'esempio di cui sopra,time(method1) : time(method2) : time(method3) = 1 : 1 : 1.7


0

Per tutto ciò che ha il potenziale per essere davvero grande, io uso il seguente.

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)

Dovrebbe essere significativamente più veloce di ogni altra cosa.


Da quello che ho misurato, NumPy inizia ad essere più veloce per elenchi di oltre 20 elementi e raggiunge un filtro> 12 volte più veloce per grandi elenchi di 1000 elementi e oltre.
Georgy

0

In alcune situazioni, in cui stai facendo molto di più che semplicemente filtrare un elenco un elemento alla volta, desideri che la tua iterazione cambi durante l'iterazione.

Ecco un esempio in cui la copia dell'elenco in anticipo non è corretta, l'iterazione inversa è impossibile e anche la comprensione dell'elenco non è un'opzione.

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

0

Se utilizzerai il nuovo elenco in un secondo momento, puoi semplicemente impostare elem su Nessuno e quindi giudicarlo nel ciclo successivo, in questo modo

for i in li:
    i = None

for elem in li:
    if elem is None:
        continue

In questo modo, non è necessario copiare l'elenco ed è più facile da capire.


-1

sopra un elenco di numeri e si desidera rimuovere tutti i numeri che sono divisibili per 3,

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

usando list comprehension, questo carea un nuovo elenco e crea nuovo spazio di memoria

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

usando la lambda filterfunzione, questo creerà un nuovo elenco risultante e consumerà spazio memeory

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

senza consumare spazio di memoria per il nuovo elenco e modificare l'elenco esistente

for index, value in enumerate(list_number):
    if list_number[index]%3==0:
        list_number.remove(value)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.