python - index - Bucle "Olvida" para eliminar algunos elementos
string python (10)
Citando de los documentos :
Nota : Hay una sutileza cuando la secuencia está siendo modificada por el ciclo (esto solo puede ocurrir para secuencias mutables, es decir, listas). Se usa un contador interno para realizar un seguimiento del elemento que se utiliza a continuación, y esto se incrementa en cada iteración. Cuando este contador ha alcanzado la longitud de la secuencia, el ciclo termina. Esto significa que si el conjunto borra el elemento actual (o uno anterior) de la secuencia, se saltará el siguiente elemento (ya que obtiene el índice del elemento actual que ya se ha tratado). Del mismo modo, si el conjunto inserta un elemento en la secuencia anterior al elemento actual, el elemento actual se tratará nuevamente la próxima vez a través del ciclo. Esto puede conducir a errores desagradables que se pueden evitar haciendo una copia temporal utilizando una porción de la secuencia completa, por ejemplo,
for x in a[:]:
if x < 0: a.remove(x)
Itera sobre una copia superficial de la lista usando [:]
. Está modificando una lista mientras la itera, esto provocará que se pierdan algunas letras.
El bucle for
realiza un seguimiento del índice, de modo que cuando elimina un elemento en el índice i
, el siguiente elemento en la posición i+1
cambia al índice actual ( i
) y, por lo tanto, en la siguiente iteración elegirá el i+2
este artículo
Tomemos un ejemplo fácil:
>>> text = "whoops"
>>> textlist = list(text)
>>> textlist
[''w'', ''h'', ''o'', ''o'', ''p'', ''s'']
for char in textlist:
if char.lower() in ''aeiou'':
textlist.remove(char)
Iteración 1: índice = 0.
char = ''W''
tal como está en el índice 0. Como no cumple esa condición, lo notará.
Iteración 2: índice = 1.
char = ''h''
como está en el índice 1. No hay nada más que hacer aquí.
Iteración 3: índice = 2.
char = ''o''
tal como está en el índice 2. Como este elemento satisface la condición, se eliminará de la lista y todos los elementos a la derecha se desplazarán un lugar hacia la izquierda para llenar el espacio.
ahora la lista de textlist
convierte en:
0 1 2 3 4
`[''w'', ''h'', ''o'', ''p'', ''s'']`
Como puede ver, la otra ''o''
movió al índice 2, es decir, el índice actual, por lo que se omitirá en la siguiente iteración. Por lo tanto, esta es la razón por la cual se saltan algunos elementos en su iteración. Cada vez que quita un elemento, el siguiente elemento se omite de la iteración.
Iteración 4: índice = 3.
char = ''p''
como está en el índice 3.
....
Fijar:
Itere sobre una copia superficial de la lista para solucionar este problema:
for char in textlist[:]: #note the [:]
if char.lower() in ''aeiou'':
textlist.remove(char)
Otras alternativas
Lista de comprensión:
Una línea con str.join
y una 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!''
Esta pregunta ya tiene una respuesta aquí:
en este código, estoy intentando crear una función anti_vowel que eliminará todas las vocales (aeiouAEIOU) de una cadena. Creo que debería funcionar bien, pero cuando lo ejecuto, el texto de muestra "Hey look Words!" se devuelve como "Hy lk Words!" Se "olvida" eliminar la última ''o''. ¿Cómo puede ser esto?
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)
Está iterando sobre una lista y eliminando elementos de ella al mismo tiempo.
En primer lugar, necesito asegurarme de que entiendes claramente el papel de char
en el for char in textlist: ...
Tome la situación en la que hemos llegado a la letra ''l''. La situación no es así:
[''H'', ''e'', ''y'', '' '', ''l'', ''o'', ''o'', ''k'', '' '', ''W'', ''o'', ''r'', ''d'', ''s'', ''!'']
^
char
No hay un vínculo entre el char
y la posición de la letra ''l'' en la lista. Si modifica char
, la lista no se modificará. La situación es más como esto:
[''H'', ''e'', ''y'', '' '', ''l'', ''o'', ''o'', ''k'', '' '', ''W'', ''o'', ''r'', ''d'', ''s'', ''!'']
^
char = ''l''
Tenga en cuenta que he conservado el ^
símbolo. Este es el puntero oculto que el código que administra el for char in textlist: ...
usa para realizar un seguimiento de su posición en el ciclo. Cada vez que ingresa al cuerpo del bucle, el puntero se avanza y la letra a la que hace referencia el puntero se copia en char
.
Tu problema ocurre cuando tienes dos vocales en sucesión. Te mostraré lo que sucede desde el punto donde llegas a ''l''. Tenga en cuenta que también cambié la palabra "mirar" por "saltar" para aclarar lo que está sucediendo:
avance el puntero al siguiente carácter (''l'') y copie a char
[''H'', ''e'', ''y'', '' '', ''l'', ''e'', ''a'', ''p'', '' '', ''W'', ''o'', ''r'', ''d'', ''s'', ''!'']
-> ^
char = ''l''
char
(''l'') no es una vocal, así que no hagas nada
avance el puntero al siguiente caracter (''e'') y copie a char
[''H'', ''e'', ''y'', '' '', ''l'', ''e'', ''a'', ''p'', '' '', ''W'', ''o'', ''r'', ''d'', ''s'', ''!'']
-> ^
char = ''e''
char
(''e'') es una vocal, entonces borra la primera ocurrencia de char
(''e'')
[''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'', ''!'']
^
avance el puntero al siguiente caracter (''p'') y copie a char
[''H'', ''e'', ''y'', '' '', ''l'', ''a'', ''p'', '' '', ''W'', ''o'', ''r'', ''d'', ''s'', ''!'']
-> ^
char = ''p''
Cuando eliminaste la ''e'' todos los caracteres después de que la ''e'' se moviera un lugar hacia la izquierda, entonces era como si remove
hubiera avanzado el puntero. El resultado es que saltaste la ''a''.
En general, debe evitar modificar listas mientras itera sobre ellas. Es mejor construir una nueva lista desde cero, y las listas de Python son la herramienta perfecta para hacerlo. P.ej
print ''''.join([char for char in "Hey look Words" if char.lower() not in "aeiou"])
Pero si aún no has aprendido sobre las comprensiones, la mejor manera es probablemente:
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)
Está modificando los datos que está iterando. No hagas eso.
''''.join(x for x in textlist in x not in VOWELS)
Estás modificando la lista sobre la que estás iterando, lo que seguramente dará como resultado un comportamiento poco intuitivo. En cambio, haga una copia de la lista para que no elimine elementos de lo que está iterando.
for char in textlist[:]: #shallow copy of the list
# etc
Para aclarar el comportamiento que estás viendo, mira esto. Coloque print char, textlist
al comienzo de su ciclo (original). Es posible que espere que esto imprima su cadena verticalmente, junto con la lista, pero lo que realmente obtendrá es esto:
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!
Entonces, ¿qué está pasando? Lo bueno for x in y
loop en Python es en realidad solo azúcar sintáctico: todavía accede a los elementos de la lista por índice. Por lo tanto, cuando elimina elementos de la lista mientras los itera, comienza a omitir valores (como puede ver más arriba). Como resultado, nunca verá el segundo en "look"
; lo omite porque el índice lo ha "pasado" cuando eliminó el elemento anterior. Luego, cuando llegas a la o
en "Words"
, vas a eliminar la primera aparición de ''o''
, que es la que omitiste antes.
Como otros han mencionado, la lista de comprensiones es probablemente una forma mejor (más clara, más clara) de hacer esto. Aproveche el hecho de que las cadenas de Python son iterables:
def remove_vowels(text): # function names should start with verbs! :)
return ''''.join(ch for ch in text if ch.lower() not in ''aeiou'')
Intente no usar la función list () en una cadena. Hará las cosas mucho más complicadas.
A diferencia de Java, en Python, las cadenas se consideran como matrices. Luego, intente usar un índice para las palabras clave loop y del.
for x in range(len(string)):
if string[x].lower() in "aeiou":
del string[x]
No debe eliminar elementos de la lista por la que realiza la iteración: pero puede crear una lista nueva a partir de la lista con sintaxis de comprensión de listas. La comprensión de listas es muy útil en esta situación. Puedes leer sobre la comprensión de la lista here
Entonces tu solución se verá así:
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)
Es lindo, ¿no es así? P
Otras respuestas le dicen por qué salta artículos mientras altera la lista. Esta respuesta le dice cómo debe eliminar los caracteres en una cadena sin un bucle explícito, en su lugar.
Use str.translate()
:
vowels = ''aeiou''
vowels += vowels.upper()
text.translate(None, vowels)
Esto elimina todos los caracteres enumerados en el segundo argumento.
Manifestación:
>>> 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''
En Python 3, el método str.translate()
(Python 2: unicode.translate()
) difiere en que no toma un parámetro deletechars ; el primer argumento es un diccionario que asigna los ordinales Unicode (valores enteros) a los nuevos valores en su lugar. Use None
para cualquier personaje que deba eliminarse:
# Python 3 code
vowels = ''aeiou''
vowels += vowels.upper()
vowels_table = dict.fromkeys(map(ord, vowels))
text.translate(vowels_table)
También puede usar el método estático str.maketrans()
para producir esa asignación:
vowels = ''aeiou''
vowels += vowels.upper()
text.translate(text.maketrans('''', '''', vowels))
Otros ya han explicado el problema con su código. Para su tarea, una expresión de generador es más fácil y menos propensa a errores.
>>> 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!''
sin embargo, str.translate
es la mejor manera de hacerlo.
vowels = ''aeiou''
text = ''Hey look Words!''
result = [char for char in text if char not in vowels]
print ''''.join(result)
text = "Hey look Words!"
print filter(lambda x: x not in "AaEeIiOoUu", text)
Salida
Hy lk Wrds!