0

I created a program where I have a list of elements which are string, int and float. I wanted to remove elements from the list which are not float or the strings which have any vowels in them.

mylist = ['hey', 20.0, 'hey', 'hop', 77, 'orange', 11, 'yay', 19, 'hello', 'xyz', 'mop', 11, 30.5, 90, 'hat', 'on']
index = 0
while (index < len(mylist)):
    var = mylist[index]
    if type(var) is int:
        mylist.pop(index)
    if type(var) is str:
        i = 0
        while (i < len(var)):
            char = var[i]
            if (char == 'a' or char == 'e' or char == 'i' or char == 'u' or char == 'o'):
                mylist.pop(index)
            i = i + 1
    index = index + 1
print(mylist)

Everything's working fine - the float values remain intact and the int values get popped but

  1. somehow the elements at certain places are not being popped out of the list even when they are int and
  2. all strings are getting checked fine but the strings with only 'o' somehow bypass the pop statement.
Borisonekenobi
  • 469
  • 4
  • 15
  • 2
    You increment `index = index + 1` every time through the outer loop, but that causes you to miss items. Say that you start off with int `10` at index 0 and int `42` at index 1. On the first iteration, your code pops `10` out of the list, so `42` moves to index 0. But on the next iteration, `index` is 1 and so `42` never gets processed. See: https://stackoverflow.com/questions/1207406/how-to-remove-items-from-a-list-while-iterating – slothrop Aug 17 '23 at 14:11
  • Also, the `while` loop looks wrong - the `i=i+1` would need to be inside the loop. – slothrop Aug 17 '23 at 14:11
  • 1
    Thank you so much @slothrop! The while loop was alright, just a typing error I made in the post. The index detail really helped me out though :D – Suhani Gurjar Aug 17 '23 at 14:18

3 Answers3

1

The main problem in your current code is that you're modifying a list while looping through it! In the code below, I've changed it so that I have a separate list which will be the outcome of this code.

mylist=['hey', 20.0, 'hey', 'hop', 77, 'orange', 11, 'yay', 19, 'hello', 'xyz', 'mop', 11, 30.5, 90, 'hat', 'on']
newList = []

for var in mylist:
    if type(var) is int:
        continue
    elif type(var) is str:
        hasVowel = False
        for char in var:
            if (char in 'aeiou'):
                hasVowel = True
                break
        if not hasVowel:
            newList.append(var)
    else:
        newList.append(var)
print(newList)

I've changed the while loops into for loops to make the code a bit simpler, so I'll explain the code in case anyone needs it.

Firstly, I keep your list and then create an empty list newList which will be the output. Then I use a for-each loop to loop through all the elements in mylist. If the current element, var, is of type int, then I just continue to the next element, if it's of type str I loop through all the characters in var and check if any of them are a, e, i, o, or u. If they are I use the hasVowel variable to mark that the current var has vowels in it. Afterwards, I check this variable and if it's False I add the current var to newList. Finally, if var is any other type (not int or str), I just add it to newList.

If you want to at the end of the code apply all the changes to mylist, you can add this line of code:

mylist = list(newList)

The reason I use list() instead of just setting it equal to newList, is in case any changes are made to newList later in the code it will not affect mylist.

Hope this helps :)

Borisonekenobi
  • 469
  • 4
  • 15
0

Never modify a list that you're iterating over. Depending on exactly what you're doing, it may or may not work.

In this case there's no need to modify the original list. Just write a function that determines if an element in the list is required (or not). Then use a list comprehension to build a new list. The discrete function makes the list comprehension less cumbersome.

Something like this:

VOWELS = set('aeiouAEIOU')

def keep(v):
    match v:
        case float():
            return True
        case str():
            return not any(c in VOWELS for c in v)
        case _:
            return False

mylist = ['hey', 20.0, 'hey', 'hop', 77, 'orange', 11, 'yay', 19, 'hello', 'xyz', 'mop', 11, 30.5, 90, 'hat', 'on']

newlist = [e for e in mylist if keep(e)]

print(newlist)

Output:

[20.0, 'xyz', 30.5]
DarkKnight
  • 19,739
  • 3
  • 6
  • 22
  • Thankyou! Although i did not want to create a new list but rather to make changes to the same list only. Your solution helped me fix mine! – Suhani Gurjar Aug 17 '23 at 15:36
  • @SuhaniGurjar So, instead of creating a new list just assign the list comprehension to *mylist*. This will be significantly more efficient than modifying the list in place – DarkKnight Aug 17 '23 at 16:06
  • Or do `mylist[:] = newlist` to mutate the object referred to by `mylist` (rather than reassigning), so that any other part of the program with a reference to `mylist` sees the change. – slothrop Aug 17 '23 at 16:46
-1

It's ignoring anything in the index immediately after something you removed because you're incrementing index even when you pop something from the list (effectively skipping over the next thing in the index). The issue only appears to only ignore strings containing the letter o because, coincidentally, those strings happen to be held in these blind spots in your list.

Consider this following code intended to remove even numbers from a list:

Your approach (always incrementing index)

a = [0,8,1,2,8,3,4,8]

i = 0
while(i < len(a)):
  v = a[i]
  if ( v % 2 == 0):
    a.pop(i) # pop the item at index i if it is even
  # always increment i
  i += 1 

print("outcome from always incrementing i")
print(*a) # 8 1 8 3 8 (incorrect - 'why is it not removing eights???')

The 'correct' approach (only incrementing i on iterations in which nothing is popped)

b = [0,8,1,2,8,3,4,8]

i = 0
while (i < len(b)):
  v = b[i]
  if (v % 2 == 0):
    b.pop(i) # pop item at index 'i' if it's even
  else:
    # only increment 'i' if we did not pop it
    i += 1

print ("outcome from only incrementing i if not popping")
print(*b) # 1 3 (correct - all even numbers are removed, including eights)

At first glance, the first example appears to just 'not remove 8s' (like how your code appeared to 'not remove words containing the letter o') - but, as you can see from the minor change made to the index incrementation in the second example, the issue was caused by your approach leaving blind spots in the list - blind spots which just so happened to contain 8s in this example, or strings with an o in your code.

So, if you were to adjust your code to only increment index when not removing stuff from the list, your code should start to work as intended.

11belowstudio
  • 137
  • 11
  • 1
    This is woefully inefficient. Just use a list comprehension and assign it to the original list variable. *b = [n for n in b if n % 2]* Also... "yeet"? – DarkKnight Aug 17 '23 at 16:11
  • @DarkKnight The intent was to help OP to understand *exactly why* their code was not working via replacing their code with an MRE. Sure, it may be a 'woefully inefficient' way of removing even numbers from a list, but '*just showing OP a list comprehension of this oversimplified MRE*' wouldn't have helped. – 11belowstudio Aug 17 '23 at 16:47
  • 1
    Maybe OP isn't looking for the most efficient solution at this moment, but your solution goes a bit off track and doesn't explain very well what the problem is. Also..."yeet"? – Borisonekenobi Aug 17 '23 at 17:10
  • @Borisonekenobi I have attempted to reword the explanation a bit, hopefully it's a bit clearer now – 11belowstudio Aug 17 '23 at 17:23