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

时间:2013-06-25 14:09:15

标签: 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)

10 个答案:

答案 0 :(得分:153)

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

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循环实际上只是语法糖:它仍然通过索引访问列表元素。因此,当您在迭代它时从列表中删除元素时,您开始跳过值(如上所示)。因此,您永远不会在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')

答案 1 :(得分:66)

其他答案告诉您为什么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() static method生成该映射:

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

答案 2 :(得分:31)

引用from the docs

  

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

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

使用[:]迭代列表的浅表副本。你在迭代它时修改一个列表,这将导致一些字母丢失。

for循环跟踪索引,因此当您删除索引i处的项目时,i+1位置的下一个项目将转移到当前索引({{1} })因此在下一次迭代中你实际上会选择i项。

让我们举一个简单的例子:

i+2

迭代1:索引= 0。

>>> text = "whoops" >>> textlist = list(text) >>> textlist ['w', 'h', 'o', 'o', 'p', 's'] for char in textlist: if char.lower() in 'aeiou': textlist.remove(char) 因为它在索引0处。因为它不满足那个条件你会注意到。

迭代2:索引= 1。

char = 'W'因为它在索引1处。这里没什么可做的。

迭代3:索引= 2。

char = 'h'因为它在索引2处。因为这个项目满足条件所以它将被从列表中删除,并且它右边的所有项目将向左移动一个位置以填补空白。 / p>

现在char = 'o'变为:

textlist

正如您所见,另一个 0 1 2 3 4 `['w', 'h', 'o', 'p', 's']` 移动到索引2,即当前索引,因此在下一次迭代中将跳过它。因此,这就是在迭代中跳过某些项目的原因。每当你删除一个项目时,都会从迭代中跳过下一个项目。

迭代4:索引= 3。

'o'因为它在索引3处。

...


修正:

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

char = 'p'

其他替代方案:

列表理解:

使用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])

答案 3 :(得分:16)

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

''.join(x for x in textlist in x not in VOWELS)

答案 4 :(得分:8)

text = "Hey look Words!"

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

<强>输出

Hy lk Wrds!

答案 5 :(得分:6)

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

首先,我需要确保您清楚地了解charfor 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'的那一刻发生了什么。请注意,我还将“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已经推进了指针。结果是你跳过'a'。

通常,您应该避免在迭代时修改列表。最好从头开始构建一个新的列表,Python的列表推导是完成这个的完美工具。 E.g。

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)

答案 6 :(得分:4)

List Comprehensions

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

答案 7 :(得分:3)

其他人已经用你的代码解释了这个问题。对于您的任务,生成器表达式更容易,更不容易出错。

>>> 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是最好的方式。

答案 8 :(得分:0)

您不应该从迭代的列表中删除项目: 但是您可以使用列表推导语法从旧列表中创建新列表。列表理解在这种情况下非常有用。您可以阅读有关列表理解here

的信息

所以你的解决方案看起来像这样:

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

答案 9 :(得分:0)

尝试不对字符串使用list()函数。这将使事情变得更加复杂。

与Java不同,在Python中,字符串被视为数组。然后,尝试使用loop和del关键字的索引。

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