Вопрос-Ответ

Loop "Forgets" to Remove Some Items [duplicate]

Цикл "забывает" удалить некоторые элементы

в этом коде я пытаюсь создать функцию anti_vowel, которая удалит все гласные (aeiouAEIOU) из строки. Я думаю, что это должно работать нормально, но когда я запускаю его, пример текста "Эй, посмотри Слова!" возвращается как "Hy lk Words!". Он "забывает" удалить последнюю букву "o". Как это может быть?

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)
Переведено автоматически
Ответ 1

Вы изменяете список, по которому выполняете итерацию, что неизбежно приведет к некоторому неинтуитивному поведению. Вместо этого создайте копию списка, чтобы не удалять элементы из того, через что вы выполняете итерацию.

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

Чтобы прояснить поведение, которое вы видите, проверьте это. Поместите print char, textlist в начало вашего (исходного) цикла. Возможно, вы ожидаете, что при этом ваша строка будет напечатана вертикально, рядом со списком, но на самом деле вы получите следующее:

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!

Так что же происходит? Хороший for x in y цикл в Python на самом деле просто синтаксический сахар: он по-прежнему обращается к элементам списка по индексу. Поэтому, когда вы удаляете элементы из списка во время итерации по нему, вы начинаете пропускать значения (как вы можете видеть выше). В результате вы никогда не увидите второй o в "look"; вы пропускаете его, потому что индекс продвинулся "дальше" него, когда вы удалили предыдущий элемент. Затем, когда вы переходите к o in "Words", вы переходите к удалению первого вхождения 'o', которое вы пропустили ранее.


Как упоминали другие, понимание списков, вероятно, является еще лучшим (более чистым, понятнее) способом сделать это. Используйте тот факт, что строки Python могут повторяться:

def remove_vowels(text): # function names should start with verbs! :)
return ''.join(ch for ch in text if ch.lower() not in 'aeiou')
Ответ 2

Другие ответы объясняют вам, почему for пропускает элементы при изменении списка. Вместо этого в этом ответе рассказывается, как вы должны удалять символы в строке без явного цикла.

Использование str.translate():

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

При этом удаляются все символы, перечисленные во втором аргументе.

ДЕМОНСТРАЦИЯ:

>>> 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'

В Python 3 str.translate() метод (Python 2: unicode.translate()) отличается тем, что он не принимает параметр deletechars; вместо этого первым аргументом является словарь, преобразующий порядковые числа в Юникоде в новые значения. Используйте None для любого символа, который необходимо удалить:

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

Вы также можете использовать str.maketrans() статический метод для создания такого сопоставления:

vowels = 'aeiou'
vowels += vowels.upper()
text.translate(text.maketrans('', '', vowels))
Ответ 3

Цитирую из документации:


Примечание: Существует тонкость, когда последовательность изменяется циклом (это может происходить только для изменяемых последовательностей, то есть списков). Внутренний счетчик используется для отслеживания того, какой элемент используется следующим, и он увеличивается на каждой итерации. Когда этот счетчик достигает длины последовательности, цикл завершается. Это означает, что если набор удаляет текущий (или предыдущий) элемент из последовательности, следующий элемент будет пропущен (поскольку он получает индекс текущего элемента, который уже был обработан). Аналогично, если набор вставляет элемент в последовательность перед текущим элементом, текущий элемент будет обработан снова в следующий раз в цикле. Это может привести к неприятным ошибкам, которых можно избежать, создав временную копию с использованием фрагмента всей последовательности, например,


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

Выполните итерацию по мелкой копии списка с помощью [:]. Вы изменяете список во время итерации по нему, это приведет к пропуску некоторых букв.

for Цикл отслеживает индекс, поэтому, когда вы удаляете элемент в индексе i, следующий элемент в i+1-й позиции сдвигается на текущий индекс (i) и, следовательно, на следующей итерации вы фактически выберете i+2-й элемент.

Давайте рассмотрим простой пример:

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

Итерация 1: Index = 0.

char = 'W' поскольку он имеет индекс 0. Поскольку он не удовлетворяет этому условию, вы должны отметить.

Итерация 2: Index = 1.

char = 'h' как и в индексе 1. Здесь больше нечего делать.

Итерация 3: Index = 2.

char = 'o' как и в индексе 2. Поскольку этот элемент удовлетворяет условию, он будет удален из списка, а все элементы справа от него сместятся на одно место влево, чтобы заполнить пробел.

теперь textlist становится :

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

Как вы можете видеть, другой 'o' перемещен в индекс 2, то есть текущий индекс, поэтому он будет пропущен на следующей итерации. Итак, по этой причине некоторые элементы в вашей итерации просто пропускаются. Всякий раз, когда вы удаляете элемент, следующий элемент пропускается с итерации.

Итерация 4: Index = 3.

char = 'p' как и в индексе 3.

....


Исправлено:

Выполните итерацию по мелкой копии списка, чтобы устранить эту проблему:

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

Другие альтернативы:

Понимание списка:

Однострочное использование str.join и list comprehension:

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

регулярное выражение:

>>> import re
>>> text = "Hey look Words!"
>>> re.sub('[aeiou]', '', text, flags=re.I)
'Hy lk Wrds!'
Ответ 4

Вы изменяете данные, с которыми выполняете итерацию. Не делайте этого.

''.join(x for x in textlist in x not in VOWELS)
python string list