2013-06-25 8 views
76

in questo codice Sto tentando di creare una funzione anti_vowel che rimuoverà tutte le vocali (aeiouAEIOU) da una stringa. Penso che lo sia funzionante, ma quando lo eseguo, il testo di esempio "Ehi guarda parole!" viene restituito come "Hy lk Words!". "Si dimentica" di rimuovere l'ultima "o". Come può essere?Loop "Forgets" per rimuovere alcuni elementi

text = "Hey look Words!" 

def anti_vowel(text): 

    textlist = list(text) 

    for char in textlist: 
     if char.lower() in 'aeiou': 
      textlist.remove(char) 

    return "".join(textlist) 

print anti_vowel(text) 
+8

Test e quindi rimuovendo ha una complessità N^2: è sufficiente rimuovere il carattere, se è presente o meno ... (o utilizzare altre soluzioni suggerite) – Don

+1

@Don: O (n^2) dove n è cosa, la lunghezza del testo di input? – LarsH

+28

'remove_vowels' sarebbe un nome migliore di' anti_vowel' –

risposta

151

si sta modificando la lista si sta scorrendo sopra, che è destinato a comportare un comportamento non intuitivo. Invece, crea una copia dell'elenco in modo da non rimuovere elementi da ciò che stai iterando.

for char in textlist[:]: #shallow copy of the list 
    # etc 

Per chiarire il comportamento che stai vedendo, check this out. Inserisci print char, textlist all'inizio del ciclo (originale). Ci si aspetterebbe, forse, che questo sarebbe stampare la stringa in verticale, a fianco della lista, ma quello che ci troveremo a ottenere è questo:

H ['H', 'e', 'y', ' ', 'l', 'o', 'o', 'k', ' ', 'W', 'o', 'r', 'd', 's', '!'] 
e ['H', 'e', 'y', ' ', 'l', 'o', 'o', 'k', ' ', 'W', 'o', 'r', 'd', 's', '!'] 
    ['H', 'y', ' ', 'l', 'o', 'o', 'k', ' ', 'W', 'o', 'r', 'd', 's', '!'] # ! 
l ['H', 'y', ' ', 'l', 'o', 'o', 'k', ' ', 'W', 'o', 'r', 'd', 's', '!'] 
o ['H', 'y', ' ', 'l', 'o', 'o', 'k', ' ', 'W', 'o', 'r', 'd', 's', '!'] 
k ['H', 'y', ' ', 'l', 'o', 'k', ' ', 'W', 'o', 'r', 'd', 's', '!'] # Problem!! 
    ['H', 'y', ' ', 'l', 'o', 'k', ' ', 'W', 'o', 'r', 'd', 's', '!'] 
W ['H', 'y', ' ', 'l', 'o', 'k', ' ', 'W', 'o', 'r', 'd', 's', '!'] 
o ['H', 'y', ' ', 'l', 'o', 'k', ' ', 'W', 'o', 'r', 'd', 's', '!'] 
d ['H', 'y', ' ', 'l', 'k', ' ', 'W', 'o', 'r', 'd', 's', '!'] 
s ['H', 'y', ' ', 'l', 'k', ' ', 'W', 'o', 'r', 'd', 's', '!'] 
! ['H', 'y', ' ', 'l', 'k', ' ', 'W', 'o', 'r', 'd', 's', '!'] 
Hy lk Words! 

Allora, cosa sta succedendo? Il bel loop for x in y in Python è in realtà solo zucchero sintattico: accede ancora agli elementi della lista per indice. Quindi, quando rimuovi elementi dalla lista mentre lo stai iterando, inizi a saltare i valori (come puoi vedere sopra). Di conseguenza, non vedi mai il secondo o in "look"; lo salti perché l'indice ha avanzato "passato" quando hai eliminato l'elemento precedente. Quindi, quando si arriva a o in "Words", si va a rimuovere la prima occorrenza di 'o', che è quella saltata prima.


Come altri hanno già detto, la comprensione delle liste è probabilmente un modo ancora migliore (più pulito, più chiaro) per farlo. Sfruttate il fatto che le stringhe Python sono iterabile:

def remove_vowels(text): # function names should start with verbs! :) 
    return ''.join(ch for ch in text if ch.lower() not in 'aeiou') 
+0

'str' è iterabile,' filter' sarebbe probabilmente più pulito di una lista di comprensione. – TC1

+0

@ TC1 C'è un caso per 'filter' e, naturalmente, per' str.translate'. Personalmente ritengo che le comprensioni delle liste siano più leggibili di una di queste due; quindi la mia scelta :) –

32

Citando from the docs:

Nota: C'è una sottigliezza quando la sequenza viene modificata dal ciclo (questo può avvenire solo per le sequenze mutabili, cioè liste). Un contatore interno viene utilizzato per tenere traccia di quale elemento viene utilizzato in seguito, e questo viene incrementato su ogni iterazione. Quando questo contatore ha raggiunto la lunghezza della sequenza che termina il ciclo. Ciò significa che se la suite elimina l'elemento corrente (o precedente) dalla sequenza, verrà saltato l'elemento successivo (poiché ottiene l'indice dell'articolo corrente che è già stato trattato). Allo stesso modo, se la suite inserisce un elemento nella sequenza prima dell'elemento corrente, l'elemento corrente sarà nuovamente trattato la volta successiva attraverso il ciclo. Questo può portare a brutte bug che possono essere evitati facendo una copia temporanea utilizzando una fetta di tutta la sequenza, per esempio,

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

iterare su una copia dei riferimenti elenco utilizzando [:]. Stai modificando un elenco mentre lo stai ripetendo, questo causerà la perdita di alcune lettere.

Il ciclo for registra indice, in modo che quando si rimuove un elemento di indice i, l'elemento successivo al i+1 ° posizione si sposta verso l'indice corrente (i) e, quindi, nella successiva iterazione ci troveremo a scegliere il i+2 l'articolo.

consente di dare un esempio semplice:

>>> text = "whoops" 
>>> textlist = list(text) 
>>> textlist 
['w', 'h', 'o', 'o', 'p', 's'] 
for char in textlist: 
    if char.lower() in 'aeiou': 
     textlist.remove(char) 

Iterazione 1: Indice = 0.

char = 'W' in quanto è al indice 0. Dato che non soddisfa tale condizione si farà rilevando.

Iterazione 2: Indice = 1.

char = 'h' in quanto è al di indice 1. Non c'è altro da fare qui.

Iterazione 3: Indice = 2.

char = 'o' in quanto è al di indice 2. Essendo la voce soddisfa la condizione così sarà rimosso dalla lista e tutti gli elementi alla sua destra si sposterà uno posizionare a sinistra per riempire il vuoto.

ora textlist diventa:

0 1 2 3 4 
`['w', 'h', 'o', 'p', 's']` 

Come si può vedere l'altro 'o' si trasferisce a indice 2, cioè l'indice corrente così sarà saltato nella successiva iterazione. Quindi, questo è il motivo per cui alcuni elementi vengono saltati nella tua iterazione. Ogni volta che rimuovi un oggetto, l'elemento successivo viene saltato dall'iterazione.

Iterazione 4: Indice = 3.

char = 'p' in quanto è al di indice 3.

....


Fix:

iterazioni su una copia superficiale dell'elenco per risolvere questo problema:

for char in textlist[:]:  #note the [:] 
    if char.lower() in 'aeiou': 
     textlist.remove(char) 

Altre alternative:

lista di comprensione:

Un one-liner utilizzando str.join e un list comprehension:

vowels = 'aeiou' 
text = "Hey look Words!" 
return "".join([char for char in text if char.lower() not in vowels]) 

regex:

>>> import re 
>>> text = "Hey look Words!" 
>>> re.sub('[aeiou]', '', text, flags=re.I) 
'Hy lk Wrds!' 
+0

're.sub ('[aeiou]', '', flags = re.I)' è più facile (specialmente se la lista di caratteri cresce più a lungo –

16

Stai modificando i dati su cui stai iterando. Non farlo.

65

Altre risposte spiegano perché for salta gli elementi mentre si modifica l'elenco. Questa risposta ti dice come rimuovere i caratteri in una stringa senza un ciclo esplicito, invece.

Uso str.translate():

vowels = 'aeiou' 
vowels += vowels.upper() 
text.translate(None, vowels) 

Questo cancella tutti i caratteri elencati nel secondo argomento.

Dimostrazione:

>>> text = "Hey look Words!" 
>>> vowels = 'aeiou' 
>>> vowels += vowels.upper() 
>>> text.translate(None, vowels) 
'Hy lk Wrds!' 
>>> text = 'The Quick Brown Fox Jumps Over The Lazy Fox' 
>>> text.translate(None, vowels) 
'Th Qck Brwn Fx Jmps vr Th Lzy Fx' 

In Python 3, il metodo str.translate() (Python 2: unicode.translate()) differenzia in quanto non richiedono deletechars parametro; il primo argomento è un dizionario che mappa gli ordinali Unicode (valori interi) a nuovi valori.Utilizzare None per qualsiasi carattere che deve essere cancellato:

# Python 3 code 
vowels = 'aeiou' 
vowels += vowels.upper() 
vowels_table = dict.fromkeys(map(ord, vowels)) 
text.translate(vowels_table) 

È anche possibile utilizzare il str.maketrans() static method per la produzione che la mappatura:

vowels = 'aeiou' 
vowels += vowels.upper() 
text.translate(text.maketrans('', '', vowels)) 
+0

Probabilmente una nota per python3 potrebbe essere utile: 'text.translate (dict.fromkeys (map (ord, vocow)))' – Bakuriu

+0

@Bakuriu: Infatti; lo stesso vale per 'unicode.translate()' su Python 2, che è lo stesso tipo in ogni caso. –

4

List Comprehensions:

vowels = 'aeiou' 
text = 'Hey look Words!' 
result = [char for char in text if char not in vowels] 
print ''.join(result) 
8
text = "Hey look Words!" 

print filter(lambda x: x not in "AaEeIiOoUu", text) 

uscita

Hy lk Wrds! 
3

Altri hanno già spiegato il problema con il codice. Per il tuo compito, un'espressione di generatore è più semplice e meno incline agli errori.

>>> text = "Hey look Words!" 
>>> ''.join(c for c in text if c.lower() not in 'aeiou') 
'Hy lk Wrds!' 

o

>>> ''.join(c for c in text if c not in 'AaEeIiOoUu') 
'Hy lk Wrds!' 

tuttavia, str.translate è il modo migliore per andare.

6

Si sta iterando su un elenco ed eliminando elementi da esso allo stesso tempo.

Prima di tutto, devo assicurarmi di capire chiaramente il ruolo di char in for char in textlist: .... Prendi la situazione in cui abbiamo raggiunto la lettera "l". La situazione è non in questo modo:

['H', 'e', 'y', ' ', 'l', 'o', 'o', 'k', ' ', 'W', 'o', 'r', 'd', 's', '!'] 
        ^
        char 

Non v'è alcun legame tra char e la posizione della lettera 'L' nella lista.Se modifichi char, l'elenco non verrà modificato. La situazione è più simile a questo:

['H', 'e', 'y', ' ', 'l', 'o', 'o', 'k', ' ', 'W', 'o', 'r', 'd', 's', '!'] 
        ^
char = 'l' 

Si noti che ho mantenuto il simbolo ^. Questo è il puntatore nascosto che il codice che gestisce il ciclo for char in textlist: ... utilizza per tenere traccia della sua posizione nel ciclo. Ogni volta che si immette il corpo del ciclo, il puntatore viene avanzato e la lettera a cui fa riferimento il puntatore viene copiata in char.

Il tuo problema si verifica quando hai due vocali in successione. Ti mostrerò cosa succede dal punto in cui raggiungi "l". Si noti che ho anche cambiato la parola "look" per "salto", per renderlo più chiaro quello che sta succedendo:

puntatore anticipo per carattere successivo ('l') e copiare char

['H', 'e', 'y', ' ', 'l', 'e', 'a', 'p', ' ', 'W', 'o', 'r', 'd', 's', '!'] 
        ->^
char = 'l' 

char ('l') non è una vocale, in modo da non fare nulla

puntatore anticipo per carattere successivo ('e') e copiare char

['H', 'e', 'y', ' ', 'l', 'e', 'a', 'p', ' ', 'W', 'o', 'r', 'd', 's', '!'] 
         ->^
char = 'e' 

char ('e') è una vocale, in modo da eliminare la prima occorrenza di char ('e') puntatore

['H', 'e', 'y', ' ', 'l', 'e', 'a', 'p', ' ', 'W', 'o', 'r', 'd', 's', '!'] 
         ^

['H', 'e', 'y', ' ', 'l',  'a', 'p', ' ', 'W', 'o', 'r', 'd', 's', '!'] 
         ^

['H', 'e', 'y', ' ', 'l', <- 'a', 'p', ' ', 'W', 'o', 'r', 'd', 's', '!'] 
         ^

['H', 'e', 'y', ' ', 'l', 'a', 'p', ' ', 'W', 'o', 'r', 'd', 's', '!'] 
         ^

anticipo per carattere successivo ('p') e copiare char

['H', 'e', 'y', ' ', 'l', 'a', 'p', ' ', 'W', 'o', 'r', 'd', 's', '!'] 
          ->^
char = 'p' 

quando è stato rimosso il 'e' tutti i personaggi afte r la 'e' si è spostata di un posto a sinistra, quindi era come se lo remove avanzi il puntatore. Il risultato è che hai saltato oltre la 'a'.

In generale, è necessario evitare di modificare gli elenchi durante l'iterazione su di essi. È meglio costruire una nuova lista da zero, e le comprovate liste di Python sono lo strumento perfetto per farlo. Per esempio.

print ''.join([char for char in "Hey look Words" if char.lower() not in "aeiou"]) 

Ma se non avete imparato a conoscere ancora comprensioni, il modo migliore è probabilmente:

text = "Hey look Words!" 

def anti_vowel(text): 

    textlist = list(text) 
    new_textlist = [] 

    for char in textlist: 
    if char.lower() not in 'aeiou': 
     new_textlist.append(char) 

    return "".join(new_textlist) 

print anti_vowel(text) 
0

Si consiglia di non eliminare gli elementi dalla lista, scorrendo: ma si può fare nuova lista dal vecchio con sintassi di comprensione delle liste. La comprensione delle liste è molto utile in questa situazione. Si può leggere su di lista here

Quindi soluzione sarà simile a questa:

text = "Hey look Words!" 

def anti_vowel(text): 
    return "".join([char for char in list(text) if char.lower() not in 'aeiou']) 

print anti_vowel(text) 

E 'abbastanza, non è vero: P

+0

Questo non fornisce una risposta alla domanda. Per criticare o richiedere chiarimenti da un autore, lascia un commento sotto il loro post. – RandomSeed

+0

@RandomSeed L'ho pensato anch'io all'inizio, ma in realtà risponde alla domanda. –

+0

@EduardLuca Potrebbe fare ciò che l'OP voleva fare (non ne ho idea), ma non risponde alla domanda: "Come può essere?". In effetti, pochissime risposte qui in realtà rispondono a questa domanda. – RandomSeed

0

Cercate di non utilizzare la funzione list() su un stringa. Renderà le cose molto più complicate.

A differenza di Java, in Python, le stringhe sono considerate come matrici.Quindi, provare a utilizzare un indice per la parola chiave loop and del.

for x in range(len(string)): 
    if string[x].lower() in "aeiou": 
     del string[x] 
Problemi correlati