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

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

你正在修改你正在迭代的列表,这肯定会导致一些不直观的行为。 相反,做一个列表的副本,所以你不要删除你正在迭代的元素。

 for char in textlist[:]: #shallow copy of the list # etc 

为了澄清你所看到的行为,请检查一下。 把print char, textlist在你的(原始)循环的开始。 你也许会期望,这将会在列表的旁边垂直打印出你的string,但是你实际得到的是这样的:

 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循环中的for x in y好处实际上只是语法糖:它仍然通过索引访问列表元素。 所以当你在迭代的时候从列表中移除元素的时候,你会开始跳过值(如上所示)。 因此,你看不到第二个"look" 。 你可以跳过它,因为当你删除前一个元素时,索引已经提前“超越”了它。 然后,当你到达"Words"o时,你去掉第一个出现的'o' ,这就是你之前跳过的那个。


正如其他人所提到的,列表parsing可能是更好(更干净,更清晰)的方式。 利用Pythonstring可迭代的事实:

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

其他答案告诉你为什么跳过项目,当你改变列表。 这个答案告诉你如何删除string中的字符,而不是显式的循环。

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

从文档引用:

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

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

使用[:]迭代列表的浅表副本。 您在修改列表的同时迭代它,这将导致一些字母被遗漏。

for循环跟踪索引,所以当你删除索引i的一个项目时,第i+1个位置的下一个项目转移到当前索引( i ),因此在下一个迭代中,你将实际selecti+2 th项目。

让我们举个简单的例子:

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

其他select:

列表理解:

单行使用str.joinlist comprehension

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

正则expression式:

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

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

首先,我需要确保你清楚地理解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' 

注意我已经保存了^符号。 这是隐藏的指针,pipe理for char in textlist: ...循环中的for char in textlist: ...代码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'。

一般来说,你应该避免修改列表,而迭代它们。 最好从头构build一个新的列表,Python的列表parsing是完成这个任务的理想工具。 例如

 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) 

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

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

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

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

 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

尽量不要在一个string上使用list()函数。 这会让事情变得更加复杂。

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

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