Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

Python Python Collections (2016, retired 2019) Lists Disemvowel

Kimmo Ojala
seal-mask
.a{fill-rule:evenodd;}techdegree
Kimmo Ojala
Python Web Development Techdegree Student 8,257 Points

Removing vowels from word in python

My codes seems to work (I tested it in workspace) but I get the error message that the tester got back unexpected letters. Did I not understand the instructions correctly or what's wrong with my code?

BR, Kimmo

disemvowel.py
def disemvowel(word):
    List = list(word)
    Vowels = ["a", "e", "i", "o", "u"]
    for letter in List:
        if letter.lower() in Vowels:
            List.remove(letter)
    word = ''.join(List)
    return word

8 Answers

Jennifer Nordell
seal-mask
STAFF
.a{fill-rule:evenodd;}techdegree
Jennifer Nordell
Treehouse Teacher

Hi there! The reason the code in the original answer posted here worked and your code doesn't is that your code doesn't take into account what happens when you remove an item. Let's say that you are at index 2 and you remove that item. The item in index 3 moves down to the 2 place, and the 4 moves down to the 3 place. But your next iteration, you'll be looking at what's in place 3. So essentially, you are skipping a check on a letter.

I encourage you to run your script in workspaces and include this line

print(disemvowel("treehouse"))

What will be returned is "trhuse". Essentially, using the remove function messes up where the function is currently looking as far as index.

Hope this helps! :sparkles:

edited to remove a reference to an answer that no longer exists in this thread

Jennifer Nordell
seal-mask
.a{fill-rule:evenodd;}techdegree
Jennifer Nordell
Treehouse Teacher

For anyone reading this thread, please note that an answer was originally given here with working code. The question then became why that solution worked and this solution does not. This answer is an attempt to explain the results.

Chris Freeman
MOD
Chris Freeman
Treehouse Moderator 68,457 Points

Repeat this mantra: "Never modify the iterable of a for loop"

As Jennifer Nordell mentioned it messes up the indexing and causes skipped iterable elements.

Workaround: iterate over a copy instead

# use built-in copy method
for letter in List.copy():
# or sliced copy – you learn about these next
for letter in List[:]:

A note about coding style:

  • variable names should be all lowercased names. Capitalization is used for class names.
  • a list is a built-in type and shouldn't be used for a variable name. Better to use a description of what the list contains, such as, "letters". This leads to the more readable:

    for letter in letters:
    
Thomas Bråten
Thomas Bråten
1,408 Points

Lol, ah yes, it does! The solution doesn't use a list as you say. Mine felt awkwardly complex compared to this:

def disemvowel(word):
    vowels = "aeiouAEIOU";

   for char in vowels:
        word = word.replace(char, "")

    return word
Chris Freeman
Chris Freeman
Treehouse Moderator 68,457 Points

This may seem to be the elegant solution but it is very inefficient when the text is large. It creates a new copy of the string every time a replace occurs. It involves 10 scans of the text. (10n) comparisons.

A for loop scan would be more efficient:

for letter in word:
    if letter.lower() not in vowels:
        keep_list.append(letter)

Later you'll learn about regular expression (regex) replacements that can solve this in a single pass. See re.sub() in re docs.

Kimmo Ojala
seal-mask
.a{fill-rule:evenodd;}techdegree
Kimmo Ojala
Python Web Development Techdegree Student 8,257 Points

Thanks Jennifer and Chris! I have a little of a hard time understanding why the script works if there are two vowels in a row and they're the same vowel, but not if there are different vowels in a row. I will, however, keep in mind not to mess with the iterable of a for loop :-).

Jennifer Nordell
seal-mask
.a{fill-rule:evenodd;}techdegree
Jennifer Nordell
Treehouse Teacher

Hi there! First, I'd like to thank you for convincing me to dive further into the mutation of iterables than I was ever inclined to do :smiley: And I have an alternate version of your code here, which I hope can help explain better what's going on. It's not that the two e's are together, but rather two vowels together, and I'll try to explain as best I can.

:warning: I apologize for the length of this reply.

def disemvowel(word):
    word_list = list(word)
    vowels = ["a", "e", "i", "o", "u"]

    for letter in word_list:
        print("I'm checking this letter:  {}".format(letter), end="\t")
        if letter.lower() in vowels:
            word_list.remove(letter)
            print("I'm removing this letter: {}".format(letter))
        else:
            print("But I'm NOT removing {}".format(letter))
    word = ''.join(word_list)
    return word


print(disemvowel("treehouse is awesome"))

When I run this code this is the output:

I'm checking this letter:  t    But I'm NOT removing t
I'm checking this letter:  r    But I'm NOT removing r
I'm checking this letter:  e    I'm removing this letter: e
I'm checking this letter:  h    But I'm NOT removing h
I'm checking this letter:  o    I'm removing this letter: o
I'm checking this letter:  s    But I'm NOT removing s
I'm checking this letter:  e    I'm removing this letter: e
I'm checking this letter:  i    I'm removing this letter: i
I'm checking this letter:       But I'm NOT removing  
I'm checking this letter:  a    I'm removing this letter: a
I'm checking this letter:  e    I'm removing this letter: e
I'm checking this letter:  o    I'm removing this letter: o
I'm checking this letter:  e    I'm removing this letter: e
trhus s wsme

So let's start letter by letter.

  • We check "t" but it's not removed
  • We check "r" but it's not removed
  • We check "e" and it's removed. The e after it falls into its place and is not checked. Currently, we have "trehouse is awesome"
  • We check "h" but it's not removed
  • We check "o" and it's removed. The "u" falls into its place and is not checked. Currently, we have "trehuse is awesome"
  • We check "s" but it's not removed.
  • We check "e" and it's removed. But this is where it gets interesting. It's the first instance of "e" in the list that's removed which means it's removing the e after the "tr". Currently, we have "trhuse is awesome". The space falls into its place and isn't checked.
  • We check "i" and it is removed. The "s" falls into its place and is not checked. Currently we have "trhuse s awesome".
  • We check a space and it is not removed.
  • We check "a" and it is removed. The "w" falls into its place and is not checked. Currently we have "trhuse s wesome".
  • We check "e" and it is removed. The first instance of "e" is the one after "trhus" so that one is removed. The "s" falls into its place and is not checked. Currently, we have "trhus s wesome".
  • We check "o" and it is removed. The "m" falls into its place and isn't checked. Currently we have "trhus s wesme".
  • We check "e" and it is removed. The next occurrence of "e" is is between "w" and "s" so that is removed. Currently we have "trhus s wsme"

Bottom line is that in this case some checks are skipped entirely. We have five instances of the letter "e", but only four checks for the letter "e". The thing removed is always the first occurrence of that object inside the list.

Hope this helps! :sparkles:

Chris Freeman
Chris Freeman
Treehouse Moderator 68,457 Points

The pattern here is the character after a removed letter is skipped because the indexes are shifted by one on each letter removal.

Starting with "treehouse is awesome"
I'm checking this letter:  e    I'm removing this letter: e
==> skipping second e
I'm checking this letter:  o    I'm removing this letter: o
==> skipping u
I'm checking this letter:  e    I'm removing this letter: e
==> skipping first Space
I'm checking this letter:  i    I'm removing this letter: i
==> skipping s
I'm checking this letter:  a    I'm removing this letter: a
==> skipping w
I'm checking this letter:  e    I'm removing this letter: e
==> skipping s
I'm checking this letter:  o    I'm removing this letter: o
==> skipping m
I'm checking this letter:  e    I'm removing this letter: e
==> end of list, nothing left to skip
Thomas Bråten
Thomas Bråten
1,408 Points

I don't know if anyone will see this, but I'm struggling hard to understand this. I followed a thread to this solution, so that is why I'm posting here.

def disemvowel(word):
    vowelList = ['a', 'e', 'i', 'o', 'u']
    wordList = list(word)
    for letter in wordList:
        if letter.lower() in vowelList:
            wordList.remove(letter)

    word = ''.join(wordList)
    return word

print(disemvowel('cAeUcdkpZs'))

My problem is, if I feed the function with the "Treehouse is awesome" string as suggested, this works. But if I use a string like this, which the task gives, it doesn't work. So I see its mentioned using a copy of the list. But I don't understand how that changes anything, cause will I not just have a copy of the list with the same problem of removing one character changes the "numbering" of the next? Bad wording, but if I remove vowels from 'RyOrE', the first one is O then the "indexing" of each one is changed? I guess indexing is the right thing to call it, since its a list?

I'm struggling, there is something I'm just not understanding. Very happy if anyone can elaborate. I don't know whats missing from my understanding.

Jennifer Nordell
seal-mask
.a{fill-rule:evenodd;}techdegree
Jennifer Nordell
Treehouse Teacher

Hi there! Try not to imagine it as a list for a moment. Imagine a deck of cards. Now I ask you to pull one out at random from the deck. What happens to the deck? It collapses to fill the empty space. The cards don't stay in place with a blank space in the middle. This is much like what is happening here.

Let's take a look at a small example:

my_string = "Treehouse"

my_string = list(my_string)

print(my_string[4])
my_string.remove("h")
print(my_string[4])

The result is that "h" and "o" are printed. When "h" is removed then the "o" falls down into its place. But that place is only ever being checked once. This means the check for "o" will be skipped because when it moves along to the index of 5, It will find a "u".... not an "o". Right now your code is essentially saying check at an index of 2. Is it a vowel? Remove it. Move to index 3. But whatever was in three is now in two so there is a check being skipped. It never reevaluates what is at that index after the removal.

Now to mitigate this you can make a copy of the list. You iterate over one and change the other.

So using your code, this is how I would do it:

def disemvowel(word):
    vowelList = ['a', 'e', 'i', 'o', 'u']
    wordList = list(word)  #the original list
    word_list_copy = wordList[:]  #make a copy just for editing
    for letter in wordList:  #loop over the original list
        if letter.lower() in vowelList:
            word_list_copy.remove(letter)  #remove from the copy

    word = ''.join(word_list_copy)  #join the copy
    return word

Hope this helps! :sparkles:

edited for additional comment

The big idea to take away from this is to not mutate the thing you're iterating over. You want to keep the indexing consistent. So here I kept the original intact and looped over it so that every letter is checked. The copy you can mutate as much as you like because indexing is irrelevant there.

Hi All,

I tried this solution with a try except block, can you suggest any better way of doing this.

def disemvowel(word):
    myList = list(word)
    vowels = ['a','e','i','o','u','A', 'E', 'I', 'O','U']

    for x in vowels:
        exitLoop = False
        while not exitLoop:
            try:
                myList.remove(x)
            except ValueError:
                exitLoop = True

    word = "".join(myList)
    return word

We could also modify it if we could use list.count() api.

Thomas Bråten
Thomas Bråten
1,408 Points

Huge thanks Jennifer. I did find a much more elegant solution which used a for loop instead. It seemed elegant in that it was less complex anyways. Thank you for taking the time of explaining this in such detail. The gotcha for me in your explanation was "iterate over one and change the other". Often it feels like its two steps ahead and one back hehe.

Edit: Yes, after looking at your code I finally understand it!

Jennifer Nordell
seal-mask
.a{fill-rule:evenodd;}techdegree
Jennifer Nordell
Treehouse Teacher

Just to be clear, your solution does use a for loop. So I'm a bit confused. Also note, that there are solutions to this that don't require a list at all :smiley: