Vraag Elegante manier om items uit de reeks in Python te verwijderen? [duplicaat]


Deze vraag heeft hier al een antwoord:

Wanneer ik code schrijf in Python, moet ik vaak items uit een lijst of een ander reekstype verwijderen op basis van een aantal criteria. Ik heb geen oplossing gevonden die elegant en efficiënt is, omdat het verwijderen van items uit een lijst die je momenteel aan het itereren bent slecht is. U kunt dit bijvoorbeeld niet doen:

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

Ik eindig meestal met zoiets als dit:

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

Dit is inefficiënt, tamelijk lelijk en mogelijk buggy (hoe behandelt het meerdere 'John Smith'-items?). Heeft iemand een meer elegante oplossing, of op zijn minst een efficiëntere oplossing?

Hoe zit het met een die werkt met woordenboeken?


51
2017-08-20 17:41


oorsprong


antwoorden:


Twee eenvoudige manieren om alleen het filteren te bereiken zijn:

  1. Gebruik makend van filter:

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

  2. Gebruik van lijstbegrippen:

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

Merk op dat in beide gevallen de waarden worden bewaard waarvoor de predikaatfunctie evalueert True, dus je moet de logica omkeren (dat wil zeggen je zegt "bewaar de mensen die niet de achternaam Smith hebben" in plaats van "verwijder de mensen die de achternaam Smith hebben").

Bewerk Grappig ... twee mensen plaatsten elk afzonderlijk de twee antwoorden die ik voorstelde toen ik de mijne plaatste.


53
2017-08-20 17:50



U kunt ook achteruit over de lijst herhalen:

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

Dit heeft het voordeel dat het geen nieuwe lijst maakt (zoals filter of een lijstbegrip) en gebruikt een iterator in plaats van een lijstkopie (zoals [:]).

Merk op dat hoewel het verwijderen van elementen terwijl itereren naar achteren veilig is, het invoegen ervan iets lastiger is.


37
2017-10-08 01:24



Het voor de hand liggende antwoord is het antwoord dat John en een paar andere mensen gaven, namelijk:

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

Maar dat heeft als nadeel dat het een nieuw lijstobject creëert, in plaats van het originele object opnieuw te gebruiken. Ik heb wat profilering en experimenten gedaan en de meest efficiënte methode die ik bedacht is:

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

Toewijzen aan "namen [:]" betekent in feite "vervang de inhoud van de namenlijst door de volgende waarde". Het is iets anders dan alleen toewijzen aan namen, omdat het geen nieuw lijstobject maakt. De rechterkant van de opdracht is een generatoruitdrukking (let op het gebruik van haakjes in plaats van vierkante haken). Hierdoor wordt Python herhaald in de lijst.

Enige snelle profilering suggereert dat dit ongeveer 30% sneller is dan de benadering voor lijstbegrip en ongeveer 40% sneller dan de filterbenadering.

Caveat: hoewel deze oplossing sneller is dan de voor de hand liggende oplossing, is deze meer obscuur en vertrouwt hij op geavanceerdere Python-technieken. Als u het toch gebruikt, raad ik u aan een opmerking bij te voegen. Het is waarschijnlijk alleen de moeite waard om te gebruiken in gevallen waarin je echt om de uitvoering van deze specifieke operatie geeft (wat vrij snel is, wat er ook gebeurt). (In het geval dat ik dit gebruikte, deed ik een A * -bundelzoekactie en gebruikte dit om zoekpunten uit de zoekbundel te verwijderen.)


28
2018-01-09 14:41



Gebruik makend van een begrip van de lijst

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

10
2017-08-20 17:49



Er zijn momenten waarop filteren (filter of een lijstbegrip gebruikt) niet werkt. Dit gebeurt wanneer een ander object een verwijzing naar de lijst houdt die u aan het wijzigen bent en u de lijst op zijn plaats moet wijzigen.

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

Het enige verschil met de originele code is het gebruik van names[:] in plaats van names in de for-lus. Op die manier itereert de code over een (ondiepe) kopie van de lijst en de verwijderingen werken zoals verwacht. Omdat het kopiëren van de lijst ondiep is, is het redelijk snel.


4
2017-10-05 11:48



filter zou hier geweldig zijn. Eenvoudig voorbeeld:

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

Bewerk: Corey's lijstbegrip is ook geweldig.


3
2017-08-20 17:49



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

2
2017-08-20 17:48