2013-06-25 22 views
76

在此代码中我试图创建一个函数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) 
+8

测试,然后除去有N^2的复杂性:刚除去炭,无论存在或不存在...(或使用其他建议的解决方案) – Don

+1

@Don:为O(n^2),其中n什么是输入文本的长度? – LarsH

+28

'remove_vowels'会比'anti_vowel'更好的名字 –

回答

151

您正在修改的列表你遍历,这是必然会导致一些不直观的行为。相反,制作一份清单的副本,以便不会从要迭代的内容中移除元素。

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中的字符串是可迭代:

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

'str'是可迭代的,'filter'可以说比list理解更清洁。 – TC1

+0

@ TC1有'filter'的情况,当然也有'str.translate'的情况。我个人认为,列表理解比这两者中的任何一个都更具可读性;因此我的选择:) –

32

引用from the docs

:有序时,正在由 环改性(这仅可导致产生可变的序列,即列表)一个微妙。一个 内部计数器用于跟踪下一个使用的项目,并且 在每次迭代时递增。当此计数器达到 时,循环终止的序列长度。这意味着如果 套件从序列中删除当前(或前一个)项目,则将跳过下一项目(因为它获取当前项目 已被处理的索引)。同样,如果套件在当前项目之前在序列中插入 项目,则当前项目将在下一次循环中再次处理 。这可能会导致讨厌的 的错误,可以通过使使用的 片的整个序列的临时副本是可以避免的,例如,

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

迭代使用[:]列表中的浅表副本。您在迭代时正在修改列表,这会导致某些字母被遗漏。

for循环跟踪指数的,所以当你在索引i删除项目,在i+1个位置的下一个项目转移到当前指数(i),因此在接下来的迭代中,你会真正挑i+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) 

其他替代方案:

列表理解:

使用str.join一个班轮和list 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!' 
+0

're.sub('[aeiou]','',flags = re.I)'更容易(尤其是如果字符列表增长更长) –

16

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

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

其他答案告诉你为什么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)) 
+0

可能是python3的注释可能是有用的:'text.translate(dict.fromkeys(map(ord,vowels)))' – Bakuriu

+0

@Bakuriu:确实;这同样适用于Python 2上的'unicode.translate()',它在任何情况下都是相同的类型。 –

4

List Comprehensions

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

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

输出

Hy lk Wrds! 
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是最好的一段路要走。

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'的地方会发生什么。请注意,我也改变了词“看”到“飞跃”,这样可以很清楚是怎么回事:

提前指向下一个字符(“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' 中的所有字符AFTE r'e'向左移动了一个位置,所以就好像remove已经将指针提前了。结果是你跳过了'a'。

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

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

+0

这并没有提供一个问题的答案。要批评或要求作者澄清,请在其帖子下方留言。 – RandomSeed

+0

@RandomSeed起初我也这么想,但实际上它确实回答了这个问题。 –

+0

@EduardLuca它可能会做OP想做的事(我不知道),但它没有回答这个问题:“这怎么可能?”。事实上,在这里很少有答案真正回答这个问题。 – RandomSeed

0

尽量不使用list()函数在串。这会让事情变得更加复杂。

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

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