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


этот вопрос уже есть ответ здесь:

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

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)
10 76

10 ответов:

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

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

другие ответы говорят вам, почему 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))

цитирую документы:

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

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

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

The for цикл отслеживает индекс, поэтому при удалении элемента в index i следующий пункт на i+1th позиция сдвигается на текущий индекс (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.

....


исправления:

повторите над a мелкая копия из списка, чтобы исправить эту проблему:

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

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

''.join(x for x in textlist in x not in VOWELS)
text = "Hey look Words!"

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

выход

Hy lk Wrds!

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

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

['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: ... loop использует для отслеживания своего положения в цикле. Каждый раз, когда вы вводите тело цикла, указатель выдвигается, и буква, на которую ссылается указатель, копируется в char.

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

предварительный указатель на следующий символ ('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 выдвинул указатель. В результате вы пропустили мимо "а".

в общем, вы должны избегать изменения списков при их повторении. Лучше построить новый список с нуля, и список 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)

Списочные Включения:

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

другие уже объяснили проблему с вашим кодом. Для вашей задачи выражение генератора проще и менее подвержено ошибкам.

>>> 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 - это лучший способ пойти.

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

Так что ваше решение будет выглядеть так:

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

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

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

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