Elegant måte å fjerne elementer fra sekvensen i Python?

stemmer
51

Når jeg skriver kode i Python, jeg trenger ofte å fjerne elementer fra en liste eller en annen sekvens typen basert på noen kriterier. Jeg har ikke funnet en løsning som er elegant og effektiv, som å fjerne elementer fra en liste du nå gjentar gjennom er dårlig. For eksempel kan du ikke gjøre dette:

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

Jeg pleier å ende opp med å gjøre noe som dette:

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

Dette er ineffektiv, ganske stygg og muligens buggy (hvordan den håndtere flere 'John Smiths oppføringer?). Er det noen som har en mer elegant løsning, eller i det minste en mer effektiv en?

Hva med en som fungerer med ordbøker?

Publisert på 20/08/2008 klokken 17:41
kilden bruker
På andre språk...                            


14 svar

stemmer
53

To enkle måter å gjøre akkurat filtrering er:

  1. Ved hjelp av filter:

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

  2. Bruke liste oppfattelser:

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

Legg merke til at begge tilfeller holde verdiene som predikatet funksjonen evaluerer til True, så du må snu logikken (dvs. du sier "holde folk som ikke har etternavnet Smith" i stedet for "å fjerne folk som har etternavn Smith ").

Rediger Funny ... to personer individuelt lagt ut både av svarene jeg foreslått som jeg var oppslaget mine.

Svarte 20/08/2008 kl. 17:50
kilden bruker

stemmer
37

Du kan også veksle bakover over listen:

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

Dette har den fordelen at den ikke skaper en ny liste (som filtereller en liste forståelse) og bruker en iterator i stedet for en liste kopi (som [:]).

Legg merke til at selv om fjerning av elementene samtidig itera bakover er trygg, setter dem er noe vanskeligere.

Svarte 08/10/2008 kl. 01:24
kilden bruker

stemmer
28

Det åpenbare svaret er den som John og et par andre folk ga, nemlig:

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

Men det har den ulempen at det skaper en ny liste objekt, snarere enn å gjenbruke det opprinnelige objektet. Jeg gjorde noen profilering og eksperimentering, og den mest effektive metoden jeg kom opp med er:

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

Tilordne til "navn [:]" i utgangspunktet betyr "erstatte innholdet i navnelisten med følgende verdi". Det er forskjellig fra bare å tildele til navn, ved at den ikke skaper en ny liste objekt. Den høyre side av oppgaven er en generator uttrykk (legg merke til bruken av parenteser snarere enn hakeparenteser). Dette vil føre til Python å iterere over listen.

Noen kjappe profilering tyder på at dette er ca 30% raskere enn listen forståelse tilnærming, og ca 40% raskere enn filter tilnærming.

Påminnelse : mens denne løsningen er raskere enn den åpenbare løsningen, er det mer obskure, og er avhengig av mer avanserte Python teknikker. Hvis du bruker den, anbefaler jeg følger det med en kommentar. Det er trolig bare verdt å bruke i tilfeller der du virkelig bryr seg om resultatene av denne operasjonen (som er ganske fort uansett hva). (I det tilfellet hvor jeg har brukt dette, var jeg gjør A * bjelke søk, og brukte dette til å fjerne søkepunkter fra søkestråle.)

Svarte 09/01/2011 kl. 14:41
kilden bruker

stemmer
10

Ved hjelp av en liste forståelse

list = [x for x in list if x[-5:] != "smith"]
Svarte 20/08/2008 kl. 17:49
kilden bruker

stemmer
4

Det er tider når filtrering (enten ved hjelp av filter eller en liste forståelse) virker ikke. Dette skjer når et annet objekt holder en referanse til listen du endre og du trenger for å endre listen på plass.

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

Den eneste forskjellen fra den opprinnelige koden er bruken av names[:]i stedet for namesi den for sløyfen. På den måten koden gjentar mer enn en (grunne) kopi av listen og fjerning fungerer som forventet. Siden listen kopiering er grunt, er det ganske raskt.

Svarte 05/10/2008 kl. 11:48
kilden bruker

stemmer
3

filter ville være fantastisk for dette. Enkelt eksempel:

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

Edit: Corey liste forståelse er awesome også.

Svarte 20/08/2008 kl. 17:49
kilden bruker

stemmer
2

Hvis listen skal filtreres på-plass, og listen størrelsen er ganske stor, så algoritmer som er nevnt i de foregående svar, som er basert på list.remove (), kan være uegnet, fordi deres beregningskompleksitet er O (n ^ 2) . I dette tilfellet kan du bruke følgende ikke-så Pytonske funksjon:

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

Edit: Egentlig løsningen på https://stackoverflow.com/a/4639748/274937 er bedre enn min løsning. Det er mer Pytonske og fungerer raskere. Så, her er en ny filter_inplace () implementering:

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)]
Svarte 02/04/2012 kl. 16:20
kilden bruker

stemmer
2

For å svare på spørsmålet ditt om å jobbe med ordbøker, bør du være oppmerksom på at Python 3.0 vil inkludere Dict oppfattelser :

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

I mellomtiden, kan du gjøre en kvasi-dict forståelse på denne måten:

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

Eller som en mer direkte svar:

dict([(key, name) for key, name in some_dictionary.iteritems if name[-5:] != 'Smith'])
Svarte 07/10/2008 kl. 14:33
kilden bruker

stemmer
2

Begge løsninger, filter og forståelse krever bygge en ny liste. Jeg vet ikke nok av Python innvendige å være sikker, men jeg tror at en mer tradisjonell (men mindre elegant) tilnærming kan være mer effektiv:

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

Allikevel, for korte lister, stokk jeg med en av de to foreslåtte løsninger tidligere.

Svarte 20/08/2008 kl. 18:20
kilden bruker

stemmer
2
names = filter(lambda x: x[-5:] != "Smith", names);
Svarte 20/08/2008 kl. 17:48
kilden bruker

stemmer
1

Her er mitt filter_inplaceimplementering som kan brukes til å filtrere elementer fra en liste på stedet, kom jeg opp med dette på min egen selvstendig før du finner denne siden. Det er den samme algoritme som hva PabloG postet, bare gjort mer generell, slik at du kan bruke den til å filtrere lister på plass, er det også i stand til å fjerne fra listen basert på comparisonFuncfeil vei er satt True; en sortering av av reversert filteret hvis du vil.

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
Svarte 15/03/2013 kl. 14:12
kilden bruker

stemmer
1

I tilfelle av et sett.

toRemove = set([])  
for item in mySet:  
    if item is unwelcome:  
        toRemove.add(item)  
mySets = mySet - toRemove 
Svarte 07/12/2009 kl. 04:01
kilden bruker

stemmer
1

Filter og liste oppfattelser er ok for eksempel, men de har et par problemer:

  • De gjør en kopi av listen og returnere den nye, og som vil være ineffektiv når den opprinnelige listen er virkelig stor
  • De kan være veldig tungvint når kriteriene for å plukke elementer (i ditt tilfelle, hvis navn [-5:] == 'Smith') er mer komplisert, eller har flere forhold.

Den opprinnelige løsningen er faktisk mer effektiv for svært store lister, selv om vi kan være enig det er styggere. Men hvis du er redd for at du kan ha flere 'John Smith', kan det fikses ved å slette basert på posisjon og ikke på verdi:

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

Vi kan ikke velge en løsning uten tanke på størrelsen på listen, men for store lister jeg foretrekker din to-pass løsning i stedet for filter eller lister oppfattelser

Svarte 02/10/2008 kl. 18:44
kilden bruker

stemmer
-2

Vel, dette er helt klart et problem med datastrukturen du bruker. Bruk en hashtabellen for eksempel. Noen implementeringer støtter flere oppføringer per tast, slik at man kan enten pop nyeste element av, eller fjerne dem alle.

Men dette er, og hva du kommer til å finne løsningen er, eleganse gjennom en annen datastruktur, ikke algoritme. Kanskje du kan gjøre det bedre hvis det er sortert, eller noe, men iterasjon på en liste er den eneste metoden her.

Edit: en innser han ba om 'effektivitet' ... alle disse foreslåtte metodene bare iterere over listen, som er det samme som det han foreslo.

Svarte 20/08/2008 kl. 17:46
kilden bruker

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more