Domanda

Questa domanda ha già una risposta qui:

Quando scrivo codice in Python, spesso ho bisogno di rimuovere elementi da un elenco o da un altro tipo di sequenza in base ad alcuni criteri.Non ho trovato una soluzione che sia elegante ed efficiente, poiché rimuovere elementi da un elenco su cui stai attualmente eseguendo l'iterazione non è corretto.Ad esempio, non puoi farlo:

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

Di solito finisco per fare qualcosa del genere:

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

Questo è inefficiente, abbastanza brutto e forse pieno di bug (come gestisce più voci "John Smith"?).Qualcuno ha una soluzione più elegante, o almeno più efficiente?

Che ne dici di uno che funzioni con i dizionari?

È stato utile?

Soluzione

Due semplici modi per eseguire solo il filtraggio sono:

  1. Utilizzando filter:

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

  2. Utilizzando le comprensioni delle liste:

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

Si noti che entrambi i casi mantengono i valori per i quali la funzione predicato valuta True, quindi devi invertire la logica (es.dici "mantieni le persone che non hanno il cognome Smith" invece di "rimuovi le persone che hanno il cognome Smith").

Modificare Divertente...due persone hanno pubblicato individualmente entrambe le risposte che ho suggerito mentre pubblicavo la mia.

Altri suggerimenti

Puoi anche scorrere all'indietro l'elenco:

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

Questo ha il vantaggio di non creare un nuovo elenco (come filter o una lista di comprensione) e utilizza un iteratore invece di una copia della lista (come [:]).

Tieni presente che sebbene la rimozione di elementi durante l'iterazione all'indietro sia sicura, inserirli è leggermente più complicato.

La risposta ovvia è quella data da John e da un paio di altre persone, vale a dire:

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

Ma questo ha lo svantaggio di creare un nuovo oggetto elenco, invece di riutilizzare l'oggetto originale.Ho fatto un po' di profilazione e sperimentazione e il metodo più efficiente che ho trovato è:

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

Assegnare a "nomi[:]" significa sostanzialmente "sostituire il contenuto dell'elenco dei nomi con il seguente valore".È diverso dalla semplice assegnazione dei nomi, in quanto non crea un nuovo oggetto elenco.Il lato destro dell'assegnazione è un'espressione generatrice (notare l'uso delle parentesi anziché delle parentesi quadre).Ciò farà sì che Python esegua l'iterazione nell'elenco.

Una rapida profilazione suggerisce che questo è circa il 30% più veloce dell'approccio di comprensione dell'elenco e circa il 40% più veloce dell'approccio del filtro.

Avvertimento:sebbene questa soluzione sia più veloce di quella ovvia, è più oscura e si basa su tecniche Python più avanzate.Se lo usi, ti consiglio di accompagnarlo con un commento.Probabilmente vale la pena usarlo solo nei casi in cui ti interessa davvero la prestazione di questa particolare operazione (che è piuttosto veloce, qualunque cosa accada).(Nel caso in cui l'ho usato, stavo eseguendo la ricerca del raggio A* e l'ho usato per rimuovere i punti di ricerca dal raggio di ricerca.)

Utilizzando una comprensione dell'elenco

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

Ci sono momenti in cui il filtraggio (utilizzando il filtro o la comprensione dell'elenco) non funziona.Ciò accade quando qualche altro oggetto contiene un riferimento all'elenco che stai modificando ed è necessario modificare l'elenco sul posto.

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

L'unica differenza rispetto al codice originale è l'uso di names[:] invece di names nel ciclo for.In questo modo il codice scorre su una copia (superficiale) dell'elenco e le rimozioni funzionano come previsto.Poiché la copia dell'elenco è superficiale, è abbastanza veloce.

il filtro sarebbe fantastico per questo.Esempio semplice:

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

Modificare: Anche la comprensione dell'elenco di Corey è fantastica.

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

Entrambe le soluzioni, filtro E comprensione richiede la creazione di un nuovo elenco.Non ne so abbastanza degli interni di Python per esserne sicuro, ma io pensare che un approccio più tradizionale (ma meno elegante) potrebbe essere più efficiente:

names = ['Jones', 'Vai', 'Smith', 'Perez']

item = 0
while item <> len(names):
    name = names [item]
    if name=='Smith':
        names.remove(name)
    else:
        item += 1

print names

In ogni caso, per gli elenchi brevi, mi attengo a una delle due soluzioni proposte in precedenza.

Per rispondere alla tua domanda su come lavorare con i dizionari, dovresti notare che Python 3.0 includerà comprensioni dettate:

>>> {i : chr(65+i) for i in range(4)}

Nel frattempo, puoi eseguire una comprensione quasi-dict in questo modo:

>>> dict([(i, chr(65+i)) for i in range(4)])

O come risposta più diretta:

dict([(key, name) for key, name in some_dictionary.iteritems if name[-5:] != 'Smith'])

Se l'elenco deve essere filtrato sul posto e la dimensione dell'elenco è piuttosto grande, gli algoritmi menzionati nelle risposte precedenti, basati su list.remove(), potrebbero non essere adatti, perché la loro complessità computazionale è O(n^2) .In questo caso puoi utilizzare la seguente funzione no-so Python:

def filter_inplace(func, original_list):
  """ Filters the original_list in-place.

  Removes elements from the original_list for which func() returns False.

  Algrithm's computational complexity is O(N), where N is the size
  of the original_list.
  """

  # Compact the list in-place.
  new_list_size = 0
  for item in original_list:
    if func(item):
      original_list[new_list_size] = item
      new_list_size += 1

  # Remove trailing items from the list.
  tail_size = len(original_list) - new_list_size
  while tail_size:
    original_list.pop()
    tail_size -= 1


a = [1, 2, 3, 4, 5, 6, 7]

# Remove even numbers from a in-place.
filter_inplace(lambda x: x & 1, a)

# Prints [1, 3, 5, 7]
print a

Modificare:In realtà, la soluzione a https://stackoverflow.com/a/4639748/274937 è superiore alla mia soluzione.È più pitonico e funziona più velocemente.Quindi, ecco una nuova implementazione filter_inplace():

def filter_inplace(func, original_list):
  """ Filters the original_list inplace.

  Removes elements from the original_list for which function returns False.

  Algrithm's computational complexity is O(N), where N is the size
  of the original_list.
  """
  original_list[:] = [item for item in original_list if func(item)]

Le comprensioni del filtro e dell'elenco vanno bene per il tuo esempio, ma presentano un paio di problemi:

  • Fanno una copia del tuo elenco e restituiscono quello nuovo, e ciò sarà inefficiente quando l'elenco originale è davvero grande
  • Possono essere davvero complicati quando i criteri per scegliere gli elementi (nel tuo caso, se nome[-5:] == 'Smith') sono più complicati o presentano diverse condizioni.

La tua soluzione originale è in realtà più efficiente per elenchi molto grandi, anche se possiamo convenire che è più brutta.Ma se temi di poter avere più "John Smith", puoi risolvere il problema eliminando in base alla posizione e non al valore:

names = ['Jones', 'Vai', 'Smith', 'Perez', 'Smith']

toremove = []
for pos, name in enumerate(names):
    if name[-5:] == 'Smith':
        toremove.append(pos)
for pos in sorted(toremove, reverse=True):
    del(names[pos])

print names

Non possiamo scegliere una soluzione senza considerare la dimensione dell'elenco, ma per elenchi di grandi dimensioni preferirei la soluzione a 2 passaggi anziché il filtro o le comprensioni degli elenchi

Nel caso di un set.

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

Ecco il mio filter_inplace che può essere utilizzata per filtrare gli elementi da un elenco sul posto, l'ho inventato da solo prima di trovare questa pagina.È lo stesso algoritmo pubblicato da PabloG, solo reso più generico in modo da poterlo utilizzare per filtrare gli elenchi sul posto, è anche in grado di rimuovere dall'elenco in base al comparisonFunc se è impostato invertito True;una sorta di filtro invertito, se vuoi.

def filter_inplace(conditionFunc, list, reversed=False):
    index = 0
    while index < len(list):
        item = list[index]

        shouldRemove = not conditionFunc(item)
        if reversed: shouldRemove = not shouldRemove

        if shouldRemove:
            list.remove(item)
        else:
            index += 1

Bene, questo è chiaramente un problema con la struttura dei dati che stai utilizzando.Usa una tabella hash per esempio.Alcune implementazioni supportano più voci per chiave, quindi è possibile estrarre l'elemento più recente o rimuoverli tutti.

Ma questa è, e la soluzione che troverai, è l'eleganza attraverso una diversa struttura dei dati, non un algoritmo.Forse puoi fare meglio se è ordinato, o qualcosa del genere, ma l'iterazione su un elenco è il tuo unico metodo qui.

modificare: si capisce che chiedeva "efficienza"...tutti questi metodi suggeriti si limitano a scorrere l'elenco, che è lo stesso di quello suggerito.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top