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 trialKimmo Ojala
Python Web Development Techdegree Student 8,257 PointsRemoving 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
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
Treehouse TeacherHi 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!
edited to remove a reference to an answer that no longer exists in this thread
Chris Freeman
Treehouse Moderator 68,441 PointsRepeat 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
1,408 PointsLol, 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
Treehouse Moderator 68,441 PointsThis 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
Python Web Development Techdegree Student 8,257 PointsThanks 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
Treehouse TeacherHi 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 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.
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!
Chris Freeman
Treehouse Moderator 68,441 PointsThe 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
1,408 PointsI 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
Treehouse TeacherHi 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!
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.
Thomas Bråten
1,408 PointsBig thanks Chris!
Animesh Bhadra
2,391 PointsHi 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
1,408 PointsHuge 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
Treehouse TeacherJust 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
Ben Jakuben
Treehouse TeacherBen Jakuben
Treehouse TeacherGreat explanation! :)
Jennifer Nordell
Treehouse TeacherJennifer Nordell
Treehouse TeacherFor 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.