Петля "Забывает" для удаления некоторых элементов

в этом коде я пытаюсь создать функцию 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 в "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; первым аргументом является словарь, сопоставляющий Unicode ординалы (целочисленные значения) вместо новых значений. Используйте 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

Цитата из документов:

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

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: индекс = 0.

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

Итерация 2: индекс = 1.

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

Итерация 3: Индекс = 2.

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

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

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

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

Итерация 4: Индекс = 3.

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

....


Fix:

Перейдите к мелкой копии списка, чтобы исправить эту проблему:

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

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

Учет списка:

Один слой с использованием str.join и a 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)

Ответ 5

text = "Hey look Words!"

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

Выход

Hy lk Wrds!

Ответ 6

Вы повторяете список и удаляете элементы из него в одно и то же время.

Во-первых, мне нужно убедиться, что вы четко понимаете роль char в for char in textlist: .... Возьмите ситуацию, когда мы достигли буквы "l" . Ситуация не выглядит следующим образом:

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

Нет связи между char и позицией буквы "l" в списке. Если вы измените char, список не будет изменен. Ситуация выглядит примерно так:

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

Обратите внимание, что я сохранил символ ^. Это скрытый указатель, который использует код, управляющий циклом for char in textlist: ..., для отслеживания его положения в цикле. Каждый раз, когда вы вводите тело цикла, указатель продвигается, и буква, на которую ссылается указатель, копируется в char.

Ваша проблема возникает, когда у вас есть две гласные подряд. Я покажу вам, что происходит с точки, где вы достигаете "l" . Заметьте, что я также изменил слово "смотреть" на "прыжок", чтобы было ясно, что происходит:

перейти к следующему символу ('l') и скопировать в char

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

char ('l') не является гласным, поэтому ничего не делать

указать указатель на следующий символ ('e') и скопировать на char

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

char ('e') является гласным, поэтому удалите первое вхождение 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', '!']
                           ^

перейти к следующему символу ('p') и скопировать в char

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

Когда вы удалили 'e', ​​все символы после "e" переместились на одно место влево, так что это было как если бы remove продвинул указатель. В результате вы пропустили "a".

В общем, вам следует избегать изменения списков при их повторении. Лучше построить новый список с нуля, и понимание списков Python - идеальный инструмент для этого. Например.

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

Но если вы еще не узнали о пониманиях, возможно, лучший способ:

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)

Ответ 7

Список понятий:

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!"
>>> ''.join(c for c in text if c.lower() not in 'aeiou')
'Hy lk Wrds!'

или

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

однако str.translate - лучший способ пойти.

Ответ 9

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

Итак, ваше решение будет выглядеть так:

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)

Это довольно, не так ли? P

Ответ 10

Попробуйте не использовать функцию list() в строке. Это усложнит ситуацию.

В отличие от Java, в Python строки считаются массивами. Затем попробуйте использовать индекс для ключевого слова loop и del.

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