1

I just started to learn Python in codacademy. I was trying to do anti-vowel function, but found the following problem with 'u'.

def anti_vowel(text):
    a = []
    for i in text:
        a.append(i)
    for item in a:
        if item in "aeiouAEIOU":
            a.remove(item)
    print ''.join(a)

print anti_vowel("Hey You!")
print anti_vowel("Hey look Words!")
print anti_vowel("aeiouAEIOU")

It printed

"Hy Yu!"
"Hy lk Words!"
"eoAIU"

Instead of

"Hy Y!"
"Hy lk Wrds!"
""

Somehow, some vowels was not removed es expected.

I found many alternatives for the function. However, please help me identify the mistake of the current code.

xyres
  • 20,487
  • 3
  • 56
  • 85
S.J.
  • 31
  • 3
  • 5
    Don't remove items from a list while iterating over the list, that's what's causing your weird bug. Rather make a new list from scratch that is missing the items. – Alex Hall Nov 26 '16 at 10:03
  • Also see the explanation and linked questions at [Removing items from a list while iterating over the list](http://sopython.com/canon/95/removing-items-from-a-list-while-iterating-over-the-list) – PM 2Ring Nov 26 '16 at 10:37
  • If you are just learning Python now you should be learning Python 3. Python 2 will no longer be supported after 2020. – PM 2Ring Nov 26 '16 at 11:09

8 Answers8

3

There's no need to use remove, and there's no need to iterate twice. Rather, as you iterate, check whether the item is a vowel and only append if it is not.

def anti_vowel(text):
    a = []
    for i in text:
        if i not in "aeiouAEIOU":
            a.append(i)
    print ''.join(a)
Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
1

When you look very closely at the remaining vowels, you can see that all those vowels remain that immediately follow one another. In your last example, a (removed) e (stays) i (removed) o (stays) and so on.

This is because you are iterating over a list and at the same time you are modifying it.

To solve the problem, you should make a copy of the list. Then you can iterate over the original one while modifying the copy.

Roland Illig
  • 40,703
  • 10
  • 88
  • 121
1

Removing items while iterating is not a good idea.

Do it in one line with a generator comprehension passed to str.join

def anti_vowel(text):
    return ''.join(item for item in text if item not in "aeiouAEIOU")

or maybe more performant using a set for faster letter lookup (not sure converting to lowercase would speed this up since it creates a new string for that)

s=set("aeiouAEIOU")
def anti_vowel(text):
    return ''.join(item for item in text if item not in s)
Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
  • While this is an excellent solution, but give OP a break - he just started learning Python. Anyway, take my +1. – xyres Nov 26 '16 at 10:09
  • thanks. Note that the recommended solution to avoid removing items on the current list is always list/generator comprehension. Maybe learning listcomps from the start is easier than learning them afterwards :) – Jean-François Fabre Nov 26 '16 at 10:11
  • 2
    Doing `if item.lower() not in s` would definitely be slower due to the time needed to create the new string. Using a set here may give a marginal speed benefit, especially if there are lots of strings to process, but searching a 10 char string is pretty fast. And if you were only searching 5 chars, using a set would almost certainly be slower, and that's not counting the time needed to build the set (OTOH, you could use a set literal to build the set at compile time, which is a little faster than converting a string). – PM 2Ring Nov 26 '16 at 10:47
  • I was thinking about `for item in text.lower()` instead. – Jean-François Fabre Nov 26 '16 at 11:46
  • But `''.join(item for item in text.lower() if item not in s)` doesn't preserve the case, which is probably not acceptable. – PM 2Ring Nov 27 '16 at 11:27
  • what was I thinking :) – Jean-François Fabre Nov 27 '16 at 12:42
1

Like others have already stated, you are modifying your list while iterating through it. I did want to suggest a python built-in option for this task, though for 3.x > Python > 2.6:

print "Hey You!".translate(None, "aeiouAEIOU")

In Python 3.x you'll need to take account for the standard Unicode string and translate it first:

translation = dict.fromkeys(map(ord, "aeiouAEIOU"), None)
print("Hey You!".translate(translation))
dummman
  • 189
  • 5
0
def anti_vowel(text):
    a = []

    for i in text:
        a.append(i)

    b = a[:]
    for item in a:
        if item in "aeiouAEIOU":
            b.remove(item)
    print (''.join(b))

print (anti_vowel("Hey You!"))
print (anti_vowel("Hey look Words!"))
print (anti_vowel("aeiouAEIOU"))
abhnin
  • 25
  • 4
0

Why not try recursively like this?

def anti_vowel(s):
    if not s:
        return s
    elif s[0] in "aeiouAEIOU":
        return anti_vowel(s[1:])
    return s[0] + anti_vowel(s[1:])

print (anti_vowel("Hey You!"))
print (anti_vowel("Hey look Words!"))
print (anti_vowel("aeiouAEIOU"))

Output:

Hy Y!
Hy lk Wrds!
Taufiq Rahman
  • 5,600
  • 2
  • 36
  • 44
  • performance is not at its best with string concatenation. – Jean-François Fabre Nov 26 '16 at 10:13
  • Thanks for the knowledge! But it's not noticeable with small strings like these. – Taufiq Rahman Nov 26 '16 at 10:14
  • of course not, but better avoid bad habits, because that's how people create software that work with a small data set and are unusable when the dataset grows bigger. Ex: If I pass "War and Peace" contents as string to this algorithm it will crawl. – Jean-François Fabre Nov 26 '16 at 10:15
  • I see.Do you know what I can do to improve it? like without changing much of the function. – Taufiq Rahman Nov 26 '16 at 10:17
  • I think you could use an helper function to handle data as lists and join afterwards. Still not optimal because of list concatenation which wouldn't be done in-place, but better. – Jean-François Fabre Nov 26 '16 at 10:19
  • I was hoping to replace the concatenation somehow – Taufiq Rahman Nov 26 '16 at 10:20
  • maybe don't return anything, work with the list passed as parameter in the aux function. Then you can use in-place addition (`extend`) – Jean-François Fabre Nov 26 '16 at 10:22
  • I have a program that computes the _powerset_ of some input and then feeds that to _set cover_. I should put it on code review. – Dan D. Nov 26 '16 at 10:23
  • @OP asked to identify the mistakes of the current code. As he already found alternatives to his solution I presume. – Sash Sinha Nov 26 '16 at 10:23
  • LOL @DanD Please consider cross-posting it to [Deliberately bad algorithms](http://forums.xkcd.com/viewtopic.php?f=12&t=111987) :) – PM 2Ring Nov 26 '16 at 10:54
  • Also, Python cannot optimize recursion, so it's best avoided unless you _really_ need it, eg when processing recursive data structures, like trees. – PM 2Ring Nov 26 '16 at 10:55
  • @Jean-FrançoisFabre On the topic of using `+` to build strings, _some_ cases of `+` concatenation have been optimized in CPython since version 2.5 for small-to-medium strings, but it does get slow for really large strings. Please see [this answer](http://stackoverflow.com/a/1350289/4014959) by Python core developer Alex Martelli. – PM 2Ring Nov 26 '16 at 11:03
  • The problem it solved was to divide a set of sets into a set of sets of sets with minimum cardinality where each set in a set in the set of sets was placed into a set that the set was disjoint with the members of. – Dan D. Nov 26 '16 at 11:45
0

you are removing item from list while traversing so thats creating a problem. Moreover there is no need to traverse the so many times.

you could use:

def anti_vowel(text):
    a = []
    for i in text:
        if i not in "aeiouAEIOU":
            a.append(i)
    print ''.join(a)

or I would say instead of using list use string like this :

def anti_vowel(text):
    a = ""
    for item in text:
        if item not in "aeiouAEIOU":
            a+=item
    print a

Edited:

using ''.join(list) is faster than using string cancatenation as pointed out by @jean in the comment section.

Karan Nagpal
  • 341
  • 2
  • 10
0

You could also join the result of a list comprehension, like:

def anti_vowel(text):
    return "".join([char for char in text if char not in "aeiouAEIOU"])

Be sure to let your functions return their results if you want the function to do more than just printing to the terminal (for example if you want to use the returned value somehow). There's no need to print the results of a function that prints its result and returns None (which is the case if there are no return statements inside).

The [char for char in text ...] part of the list comprehension is simply a for loop over the characters in the string named text. The [... if char not in "aeiouAEIOU"] excludes the characters who are present in the "aeiouAEIOU" string. Finally, "".join([ ... ]) merges the non-excluded characters together to form a string which by the return statement is returned.

internetional
  • 312
  • 2
  • 8