循环“忘记”删除某些项目

2024-09-28 17:00:51 发布

您现在位置:Python中文网/ 问答频道 /正文

在这段代码中,我试图创建一个反元音函数,它将从字符串中删除所有元音(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)

Tags: 函数字符串代码textin文本示例单词
3条回答

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

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!

怎么了?Python中漂亮的for x in y循环实际上只是语法上的糖:它仍然按索引访问列表元素。因此,当您在列表上迭代时从列表中删除元素时,就开始跳过值(如上图所示)。因此,在"look"中永远看不到第二个o;您跳过它是因为当您删除前一个元素时,索引已经“超过”了它。然后,当您到达"Words"中的o时,您将删除'o'的第一个匹配项,这是您之前跳过的一个匹配项。


正如其他人所提到的,列表理解可能是一种更好(更干净、更清晰)的方法。利用Python字符串是可iterable的这一事实:

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

引用from the docs

Note: There is a subtlety when the sequence is being modified by the loop (this can only occur for mutable sequences, i.e. lists). An internal counter is used to keep track of which item is used next, and this is incremented on each iteration. When this counter has reached the length of the sequence the loop terminates. This means that if the suite deletes the current (or a previous) item from the sequence, the next item will be skipped (since it gets the index of the current item which has already been treated). Likewise, if the suite inserts an item in the sequence before the current item, the current item will be treated again the next time through the loop. This can lead to nasty bugs that can be avoided by making a temporary copy using a slice of the whole sequence, e.g.,

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处。

。。。。


修复:

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

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

其他替代方案:

列表理解:

使用str.joinlist 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!'

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

使用^{}

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)

您还可以使用^{} static method生成该映射:

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

相关问题 更多 >