Error "index out of range" when working with strings in a for loop in python
Question:
I’m very new to python and I’m practicing different exercises.
I need to write a program to decode
a string. The original string has been modified by adding, after each vowel (letters ’a’, ’e’, ’i’, ’o’ and ’u’), the letter ’p’
and then that same vowel again.
For example, the word “kemija”
becomes “kepemipijapa”
and the word “paprika”
becomes “papapripikapa”
.
vowel = ['a', 'e', 'i', 'o', 'u']
input_word = list(input())
for i in range(len(input_word)):
if input_word[i] in vowel:
input_word.pop(i + 1)
input_word.pop(i + 2)
print(input_word)
The algorithm I had in mind was to detect the index for which the item is a vowel and then remove the following 2 items after this item ,so if input_word[0] == 'e'
then the next 2 items (input_word[1], input_word[2])
must be removed from the list. For the sample input zepelepenapa
, I get this error message : IndexError: pop index out of range
even when I change the for loop to range(len(input_word) - 2)
,again I get this same error.
thanks in advance
Answers:
pop()
removes an item at the given position in the list and returns it. This alters the list in place.
For example if I have:
my_list = [1,2,3,4]
n = my_list.pop()
will return n = 4
in this instance. If I was to print my_list
after this operation it would return [1,2,3]
. So the length of the list will change every time pop()
is used. That is why you are getting IndexError: pop index out of range
.
So to solve this we should avoid using pop() since it’s really not needed in this situation. The following will work:
word = 'kemija'
vowels = ['a', 'e', 'i', 'o', 'u']
new_word = []
for w in word:
if w in vowels:
new_word.extend([w,'p',w])
# alternatively you could use .append() over .extend() but would need more lines:
# new_word.append(w)
# new_word.append('p')
# new_word.append(w)
else:
new_word.append(w)
decoded_word = ''.join(new_word)
print(decoded_word)
The loop will run a number of times equal to the original length of input_word
, due to range(len(input_word))
. An IndexError
will occur if input_word
is shortened inside the loop, because the code inside the loop tries to access every element in the original list input_word
with the expression input_word[i]
(and, for some values of input_word
, the if
block could even attempt to pop items off the list beyond its original length, due to the (i + 1)
and (i + 2)
).
Hardcoding the loop definition with a specific number like 2
, e.g. with range(len(input_word) - 2)
, to make it run fewer times to account for removed letters isn’t a general solution, because the number of letters to be removed is initially unknown (it could be 0, 2, 4, …).
Here are a couple of possible solutions:
- Instead of removing items from
input_word
, create a new list output_word
and add letters to it if they meet the criteria. Use a helper list skip_these_indices
to keep track of indices that should be "removed" from input_word
so they can be skipped when building up the new list output_word
:
vowel = ['a', 'e', 'i', 'o', 'u']
input_word = list("zepelepenapa")
output_word = []
skip_these_indices = []
for i in range(len(input_word)):
# if letter 'i' shouldn't be skipped, add it to output_word
if i not in skip_these_indices:
output_word.append(input_word[i])
# check whether to skip the next two letters after 'i'
if input_word[i] in vowel:
skip_these_indices.append(i + 1)
skip_these_indices.append(i + 2)
print(skip_these_indices) # [2, 3, 6, 7, 10, 11]
print(output_word) # ['z', 'e', 'l', 'e', 'n', 'a']
print(''.join(output_word)) # zelena
- Alternatively, use two loops. The first loop will keep track of which letters should be removed in a list called
remove_these_indices
. The second loop will remove them from input_word
:
vowel = ['a', 'e', 'i', 'o', 'u']
input_word = list("zepelepenapa")
remove_these_indices = []
# loop 1 -- find letters to remove
for i in range(len(input_word)):
# if letter 'i' isn't already marked for removal,
# check whether we should remove the next two letters
if i not in remove_these_indices:
if input_word[i] in vowel:
remove_these_indices.append(i + 1)
remove_these_indices.append(i + 2)
# loop 2 -- remove the letters (pop in reverse to avoid IndexError)
for i in reversed(remove_these_indices):
# if input_word has a vowel in the last two positions,
# without a "p" and the same vowel after it,
# which it shouldn't based on the algorithm you
# described for generating the coded word,
# this 'if' statement will avoid popping
# elements that don't exist
if i < len(input_word):
input_word.pop(i)
print(remove_these_indices) # [2, 3, 6, 7, 10, 11]
print(input_word) # ['z', 'e', 'l', 'e', 'n', 'a']
print(''.join(input_word)) # zelena
I’m very new to python and I’m practicing different exercises.
I need to write a program to decode
a string. The original string has been modified by adding, after each vowel (letters ’a’, ’e’, ’i’, ’o’ and ’u’), the letter ’p’
and then that same vowel again.
For example, the word “kemija”
becomes “kepemipijapa”
and the word “paprika”
becomes “papapripikapa”
.
vowel = ['a', 'e', 'i', 'o', 'u']
input_word = list(input())
for i in range(len(input_word)):
if input_word[i] in vowel:
input_word.pop(i + 1)
input_word.pop(i + 2)
print(input_word)
The algorithm I had in mind was to detect the index for which the item is a vowel and then remove the following 2 items after this item ,so if input_word[0] == 'e'
then the next 2 items (input_word[1], input_word[2])
must be removed from the list. For the sample input zepelepenapa
, I get this error message : IndexError: pop index out of range
even when I change the for loop to range(len(input_word) - 2)
,again I get this same error.
thanks in advance
pop()
removes an item at the given position in the list and returns it. This alters the list in place.
For example if I have:
my_list = [1,2,3,4]
n = my_list.pop()
will return n = 4
in this instance. If I was to print my_list
after this operation it would return [1,2,3]
. So the length of the list will change every time pop()
is used. That is why you are getting IndexError: pop index out of range
.
So to solve this we should avoid using pop() since it’s really not needed in this situation. The following will work:
word = 'kemija'
vowels = ['a', 'e', 'i', 'o', 'u']
new_word = []
for w in word:
if w in vowels:
new_word.extend([w,'p',w])
# alternatively you could use .append() over .extend() but would need more lines:
# new_word.append(w)
# new_word.append('p')
# new_word.append(w)
else:
new_word.append(w)
decoded_word = ''.join(new_word)
print(decoded_word)
The loop will run a number of times equal to the original length of input_word
, due to range(len(input_word))
. An IndexError
will occur if input_word
is shortened inside the loop, because the code inside the loop tries to access every element in the original list input_word
with the expression input_word[i]
(and, for some values of input_word
, the if
block could even attempt to pop items off the list beyond its original length, due to the (i + 1)
and (i + 2)
).
Hardcoding the loop definition with a specific number like 2
, e.g. with range(len(input_word) - 2)
, to make it run fewer times to account for removed letters isn’t a general solution, because the number of letters to be removed is initially unknown (it could be 0, 2, 4, …).
Here are a couple of possible solutions:
- Instead of removing items from
input_word
, create a new listoutput_word
and add letters to it if they meet the criteria. Use a helper listskip_these_indices
to keep track of indices that should be "removed" frominput_word
so they can be skipped when building up the new listoutput_word
:
vowel = ['a', 'e', 'i', 'o', 'u']
input_word = list("zepelepenapa")
output_word = []
skip_these_indices = []
for i in range(len(input_word)):
# if letter 'i' shouldn't be skipped, add it to output_word
if i not in skip_these_indices:
output_word.append(input_word[i])
# check whether to skip the next two letters after 'i'
if input_word[i] in vowel:
skip_these_indices.append(i + 1)
skip_these_indices.append(i + 2)
print(skip_these_indices) # [2, 3, 6, 7, 10, 11]
print(output_word) # ['z', 'e', 'l', 'e', 'n', 'a']
print(''.join(output_word)) # zelena
- Alternatively, use two loops. The first loop will keep track of which letters should be removed in a list called
remove_these_indices
. The second loop will remove them frominput_word
:
vowel = ['a', 'e', 'i', 'o', 'u']
input_word = list("zepelepenapa")
remove_these_indices = []
# loop 1 -- find letters to remove
for i in range(len(input_word)):
# if letter 'i' isn't already marked for removal,
# check whether we should remove the next two letters
if i not in remove_these_indices:
if input_word[i] in vowel:
remove_these_indices.append(i + 1)
remove_these_indices.append(i + 2)
# loop 2 -- remove the letters (pop in reverse to avoid IndexError)
for i in reversed(remove_these_indices):
# if input_word has a vowel in the last two positions,
# without a "p" and the same vowel after it,
# which it shouldn't based on the algorithm you
# described for generating the coded word,
# this 'if' statement will avoid popping
# elements that don't exist
if i < len(input_word):
input_word.pop(i)
print(remove_these_indices) # [2, 3, 6, 7, 10, 11]
print(input_word) # ['z', 'e', 'l', 'e', 'n', 'a']
print(''.join(input_word)) # zelena