循环“忘记”删除一些项目
在这段代码中,我试图创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.join
和list 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]