循环"忘记"删除一些项目

Par*_*dox 77 python string list

在这段代码中,我试图创建一个函数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)
Run Code Online (Sandbox Code Playgroud)

Hen*_*ter 153

您正在修改正在迭代的列表,这必然会导致一些不直观的行为.相反,请复制列表,这样就不会从正在迭代的内容中删除元素.

for char in textlist[:]: #shallow copy of the list
    # etc
Run Code Online (Sandbox Code Playgroud)

为了澄清您所看到的行为,请查看此信息.放在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!
Run Code Online (Sandbox Code Playgroud)

发生什么了?for x in yPython中的好循环实际上只是语法糖:它仍然通过索引访问列表元素.因此,当您在迭代它时从列表中删除元素时,您开始跳过值(如上所示).其结果是,你永远看不到第二个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')
Run Code Online (Sandbox Code Playgroud)


Mar*_*ers 66

其他答案告诉您为什么for在更改列表时跳过项目.这个答案告诉你如何在没有显式循环的情况下删除字符串中的字符.

用途str.translate():

vowels = 'aeiou'
vowels += vowels.upper()
text.translate(None, vowels)
Run Code Online (Sandbox Code Playgroud)

这将删除第二个参数中列出的所有字符.

演示:

>>> 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'
Run Code Online (Sandbox Code Playgroud)

在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)
Run Code Online (Sandbox Code Playgroud)

您还可以使用str.maketrans()静态方法生成该映射:

vowels = 'aeiou'
vowels += vowels.upper()
text.translate(text.maketrans('', '', vowels))
Run Code Online (Sandbox Code Playgroud)


Ash*_*ary 31

引用文档:

注意:当循环修改序列时有一个微妙的变化(这只能发生在可变序列,即列表中).内部计数器用于跟踪下一个使用的项目,并在每次迭代时递增.当该计数器达到序列的长度时,循环终止.这意味着如果套件从序列中删除当前(或前一个)项目,则将跳过下一个项目(因为它获取已经处理的当前项目的索引).同样,如果套件在当前项目之前的序列中插入项目,则下次循环时将再次处理当前项目.这可能导致令人讨厌的错误,可以通过使用整个序列的切片进行临时复制来避免,例如,

for x in a[:]:
    if x < 0: a.remove(x)
Run Code Online (Sandbox Code Playgroud)

使用重复列表的浅表副本[:].你在迭代它时修改一个列表,这将导致一些字母被遗漏.

for循环跟踪指数的,所以当你在索引中删除的项i,在下一项目i+1位置i转移到当前指数(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)
Run Code Online (Sandbox Code Playgroud)

迭代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']`
Run Code Online (Sandbox Code Playgroud)

正如您所见,另一个'o'移动到索引2,即当前索引,因此在下一次迭代中将跳过它.因此,这就是在迭代中跳过某些项目的原因.每当您删除项目时,都会从迭代中跳过下一个项目.

迭代4:索引= 3.

char = 'p' 因为它在索引3处.

....


固定:

迭代列表的浅表副本以解决此问题:

for char in textlist[:]:        #note the [:]
    if char.lower() in 'aeiou':
        textlist.remove(char)
Run Code Online (Sandbox Code Playgroud)

其他替代品:

列表理解:

单线使用str.join和a list comprehension:

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

正则表达式:

>>> import re
>>> text = "Hey look Words!"
>>> re.sub('[aeiou]', '', text, flags=re.I)
'Hy lk Wrds!'
Run Code Online (Sandbox Code Playgroud)


Ign*_*ams 16

您正在修改您正在迭代的数据.不要那样做.

''.join(x for x in textlist in x not in VOWELS)
Run Code Online (Sandbox Code Playgroud)


the*_*eye 8

text = "Hey look Words!"

print filter(lambda x: x not in "AaEeIiOoUu", text)
Run Code Online (Sandbox Code Playgroud)

产量

Hy lk Wrds!
Run Code Online (Sandbox Code Playgroud)


Pit*_*rou 6

您正在迭代列表并同时从中删除元素.

首先,我需要确保您清楚了解的作用charfor char in textlist: ....以我们达到字母"l"的情况为例.情况不是这样的:

['H', 'e', 'y', ' ', 'l', 'o', 'o', 'k', ' ', 'W', 'o', 'r', 'd', 's', '!']
                      ^
                    char
Run Code Online (Sandbox Code Playgroud)

char列表中字母"l"的位置和位置之间没有任何关联.如果修改char,则不会修改列表.情况更像这样:

['H', 'e', 'y', ' ', 'l', 'o', 'o', 'k', ' ', 'W', 'o', 'r', 'd', 's', '!']
                      ^
char = 'l'
Run Code Online (Sandbox Code Playgroud)

请注意,我保留了^符号.这是管理for char in textlist: ...循环的代码用于跟踪其在循环中的位置的隐藏指针.每次进入循环体时,指针都会前进,指针所引用的字母将被复制到char.

当你连续有两个元音时就会出现问题.我会告诉你从达到'l'的那一刻发生了什么.请注意,我还将"look"更改为"leap",以便更清楚地了解正在发生的事情:

将指针前进到下一个字符('l')并复制到 char

['H', 'e', 'y', ' ', 'l', 'e', 'a', 'p', ' ', 'W', 'o', 'r', 'd', 's', '!']
                   -> ^
char = 'l'
Run Code Online (Sandbox Code Playgroud)

char ('l')不是元音,所以什么都不做

将指针前进到下一个字符('e')并复制到 char

['H', 'e', 'y', ' ', 'l', 'e', 'a', 'p', ' ', 'W', 'o', 'r', 'd', 's', '!']
                        -> ^
char = 'e'
Run Code Online (Sandbox Code Playgroud)

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', '!']
                           ^
Run Code Online (Sandbox Code Playgroud)

将指针前进到下一个字符('p')并复制到 char

['H', 'e', 'y', ' ', 'l', 'a', 'p', ' ', 'W', 'o', 'r', 'd', 's', '!']
                             -> ^
char = 'p'
Run Code Online (Sandbox Code Playgroud)

当你删除'e'后,'e'后面的所有字符都向左移动了一个地方,所以好像remove已经推进了指针.结果是你跳过'a'.

通常,您应该避免在迭代时修改列表.最好从头开始构建一个新列表,而Python的列表推导是完成此任务的完美工具.例如

print ''.join([char for char in "Hey look Words" if char.lower() not in "aeiou"])
Run Code Online (Sandbox Code Playgroud)

但如果你还没有学过理解,最好的方法可能是:

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)
Run Code Online (Sandbox Code Playgroud)