Element was not removed from list in Python 3 - python

d = 3
cl = [1,3,4]
for i in cl:
if i <= d:
cl.remove(i)
print(cl)
output >> [3,4]
The number 3 should not be in the list as it passed the i <= d condition so cl.remove should've been called but it is in the output. What am I doing wrong here?

It happens because you're removing elements from list while iterating over it. It could be solved like this
[num for num in cl if num > d]

This works
d = 3
cl = [1, 3, 4]
for i in range(len(cl)):
if cl[i-1] <= d:
cl.remove(cl[i-1])
print(cl)
Please can you accept if it works...

This is the result of mutating a data structure during iteration. The for loop essentially creates an iterator over your list, with each item calling next(iterator). However, popping items off changes what the iterator is looking at
a = [1, 2, 3, 4]
it = iter(a)
# First iteration
next(it)
1
# remove element
a.pop(0)
1
# we skipped 2!
next(it)
3
Why? Well, we effectively changed what element the iterator is pointing to by removing the element we were currently on. We were looking at the first element in the sequence, but that was removed, so now the second element is the first one. So the call to next then points to the following element. This winds up looking like it was skipped when it wasn't, you just unintentionally had elements shuffled forward.
To avoid this, it's best to create a new list by filtering, as #AlexanderLekontsev suggests in his answer. This avoids mutating while iterating. You can do this with a standard loop with append like so:
newlist = []
for num in cl:
if num >= d:
newlist.append(num)

Essentially, because you are removing elements from the list while iterating over it, you have skipped over the value 3.
In the first iteration of your for-loop, you remove the value 1. Because of how iterations work in Python, in your second iteration of the for-loop, you are looking for next(next(cl)). But, cl has been updated to [3,4], since you removed 1. So now next(next(cl)) = next(3) = 4 and so you've skipped over 3.
You can resolve this by creating a new list and updating it as you go along. An easy way to do this using list comprehension is simply [num for num in cl if num > d].

Related

The same list has different lengths please help me understand [duplicate]

I'm currently developing a program in python and I just noticed that something was wrong with the foreach loop in the language, or maybe the list structure. I'll just give a generic example of my problem to simplify, since I get the same erroneous behavior on both my program and my generic example:
x = [1,2,2,2,2]
for i in x:
x.remove(i)
print x
Well, the problem here is simple, I though that this code was supposed to remove all elements from a list. Well, the problem is that after it's execution, I always get 2 remaining elements in the list.
What am I doing wrong? Thanks for all the help in advance.
Edit: I don't want to empty a list, this is just an example...
This is a well-documented behaviour in Python, that you aren't supposed to modify the list being iterated through. Try this instead:
for i in x[:]:
x.remove(i)
The [:] returns a "slice" of x, which happens to contain all its elements, and is thus effectively a copy of x.
When you delete an element, and the for-loop incs to the next index, you then skip an element.
Do it backwards. Or please state your real problem.
I think, broadly speaking, that when you write:
for x in lst:
# loop body goes here
under the hood, python is doing something like this:
i = 0
while i < len(lst):
x = lst[i]
# loop body goes here
i += 1
If you insert lst.remove(x) for the loop body, perhaps then you'll be able to see why you get the result you do?
Essentially, python uses a moving pointer to traverse the list. The pointer starts by pointing at the first element. Then you remove the first element, thus making the second element the new first element. Then the pointer move to the new second – previously third – element. And so on. (it might be clearer if you use [1,2,3,4,5] instead of [1,2,2,2,2] as your sample list)
Why don't you just use:
x = []
It's probably because you're changing the same array that you're iterating over.
Try Chris-Jester Young's answer if you want to clear the array your way.
I know this is an old post with an accepted answer but for those that may still come along...
A few previous answers have indicated it's a bad idea to change an iterable during iteration. But as a way to highlight what is happening...
>>> x=[1,2,3,4,5]
>>> for i in x:
... print i, x.index(i)
... x.remove(i)
... print x
...
1 0
[2, 3, 4, 5]
3 1
[2, 4, 5]
5 2
[2, 4]
Hopefully the visual helps clarify.
I agree with John Fouhy regarding the break condition. Traversing a copy of the list works for the remove() method, as Chris Jester-Young suggested. But if one needs to pop() specific items, then iterating in reverse works, as Erik mentioned, in which case the operation can be done in place. For example:
def r_enumerate(iterable):
"""enumerator for reverse iteration of an iterable"""
enum = enumerate(reversed(iterable))
last = len(iterable)-1
return ((last - i, x) for i,x in enum)
x = [1,2,3,4,5]
y = []
for i,v in r_enumerate(x):
if v != 3:
y.append(x.pop(i))
print 'i=%d, v=%d, x=%s, y=%s' %(i,v,x,y)
or with xrange:
x = [1,2,3,4,5]
y = []
for i in xrange(len(x)-1,-1,-1):
if x[i] != 3:
y.append(x.pop(i))
print 'i=%d, x=%s, y=%s' %(i,x,y)
If you need to filter stuff out of a list it may be a better idea to use list comprehension:
newlist = [x for x in oldlist if x%2]
for instance would filter all even numbers out of an integer list
The list stored in the memory of a computer. This deals with the pointer to a memory artifact. When you remove an element, in a by-element loop, you are then moving the pointer to the next available element in the memory address
You are modifying the memory and iterating thru the same.
The pointer to the element moves through the list to the next spot available.
So in the case of the Size being 5...enter code here
[**0**,1,2,3,4]
remove 0 ---> [1,**2**,3,4] pointer moves to second index.
remove 2 ---> [1,3,**4**] pointer moves to 3rd index.
remove 4 ---> [1,3]
I was just explaining this to my students when they used pop(1). Another very interesting side-effect error.
x=[1,**2**,3,4,5]
for i in x:
x.pop(1)
print(x,i)
[1, **3**, 4, 5] 1 at index 0 it removed the index 1 (2)
[1, **4**, 5] 3 at index 1 it removed the index 1 (3)
[1, 5] 5 at index 2 it removed the index 1 (4)
heh.
They were like why isnt this working... I mean... it did... exactly what you told it to do. Not a mind reader. :)

delete element by deleting it from list

Let's say I have:
a = 1
b = 2
C = 'r'
my_list = [a,b,c]
Now let's say that a, b and c are unknown and I don't know their names.
If I do:
for x in my_list: del x
it doesn't work. a, b, c have not been deleted.
Can someone explain me why?
As #Coldspeed mentions in his comment, the variable x which you delete is not the same as the element in the list object.
Similar behaviour will be seen if you try to assign to x:
for x in my_list: x='bla' #does not modify anything in my_list
However, as the items are references to the same memory block, the comparison x is my_list[0] will equate to True in the first loop iteration.
As such, it is possible to perform operations on the list through usage of the shared reference, for example:
for x in my_list[:]: my_list.remove(x) #results in an empty list
Care has to be taken to first create a copy of the list and iterate over these items though, as was done in the previous lines. If you are hasty and loop over the items of a dynamically changing list, you will run into some more python magic.
for x in my_list: my_list.remove(x) #the first element gets deleted, then the second element in the list, which now has length 2, is deleted.
#Final result is the list [2] remaining
you have multiple issues here:
1. variable in list
a = 1
b = 2
my_list = [a,b]
assigns the values 1 and 2 to the list, not the vars. You can use mutable objects to get you desire: Immutable vs Mutable types
2. deleting a copy from a listvalue
for x in my_list:
del x
like in 1. x is just the value from the list (e.g. 1, 2, 'c'), but even worse, its a additional reference count to the memory.
Deleting it results in decreasing the counter, not deleting the value from memory, since at least one more counter is given by the original list (and in your case the vars (a,b,c) from the beginning).
More Info: Arguments are passed by assignment
3. deleting while iterating
for x in my_list:
del x
contains an other problem. If you would change the code to mylist.remove(x), to at least remove the entrie from the list, you would also skip every second member of the list. Quick Example:
li = [1,2,3]
for x in li:
li.remove(x)
first iteration would be x = 1. Deleting 1 from li results in li = [2,3]. Then the loop continous with the second position in the list: x=3 and deleting it. 2 was skipped.
This can be avoided by using a copy of the list using the [:] operator:
for x in li[:]:
li.remove(x)
This finaly results in an empty list

Creating a function that removes duplicates in list

I'm trying to manually make a function that removes duplicates from a list. I know there is a Python function that does something similar (set()), but I want to create my own. This is what I have:
def remove(lst):
for i in range(len(lst)):
aux = lst[0:i] + lst[i+1:len(lst)]
if lst[i] in aux:
del(lst[i])
return lst
I was trying something like creating a sub-list with all the items except the one the for is currently on, and then check if the item is still in the list. If it is, remove it.
The problem is that it gives me an index out of range error. Does the for i in range(len(lst)): line not update every time it starts over? Since I'm removing items from the list, the list will be shorter, so for a list that has 10 items and 2 duplicates, it will go up to index 9 instead of stopping on the 7th.
Is there anyway to fix this, or should I just try doing this is another way?
I know this does not fix your current script, but would something like this work?
def remove(lst):
unique=[]
for i in lst:
if i not in unique: unique.append(i)
return unique
Just simply looping through, creating another list and checking for membership?
The problem is you are manipulating the list as you are iterating over it. This means that when you reach the end of the list, it is now shorter because you're removed elements. You should (generally) avoid removing elements while you are looping over lists.
You got it the first time: len(lst) is evaluated only when you enter the loop. If you want it re-evaluated, try the while version:
i = 0
while i < len(lst):
...
i += 1
Next, you get to worry about another problem: you increment i only when you don't delete an item. When you do delete, shortening the list gets you to the next element.
i = 0
while i < len(lst):
aux = lst[0:i] + lst[i+1:len(lst)]
if lst[i] in aux:
del(lst[i])
else:
i += 1
I think that should solve your problem ... using the logic you intended.
def remove(lst):
new_list = []
for i in lst:
if i not in new_list:
new_list.append(i)
return new_list
You should append the values to a secondary list. As Bobbyrogers said, it's not a good idea to iterate over a list that is changing.
You can also try this:
lst = [1,2,3,3,4,4,5,6]
lst2 = []
for i in lst:
if i not in lst2:
lst2.append(i)
print(lst2)
[1, 2, 3, 4, 5, 6]

How can I rewrite this list comprehension as a for loop?

How can I rewrite this using nested for loops instead of a list comprehension?
final= [[]]
for i in array_list:
final.extend([sublist + [i] for sublist in final])
return final
If you try to iterate over final as you extend it, it creates an infinite loop. Because every time you go to the next element, you add another element, so you never reach the end of the list.
If you want to do the inner loop as a for loop instead of a list comprehension, you need to iterate over a copy of final.
final = [[]]
for i in [1, 2, 3]:
for sublist in final[:]:
final.extend([sublist + [i]])
Your solution looks to be a very good for loop one. A one-liner using itertools is possible, but ugly
list(itertools.chain(list(itertools.combinations(arr, i)) for i in range(len(arr) + 1)))
EDIT:
Prettier:
list(itertools.chain(*[itertools.combinations(arr, i) for i in range(len(arr)+1)]))
This code also seems to give the same results as your code.
final = [[]]
for i in array_list:
for sublist in list(final):
final.extend([sublist + [i]])
return final
It seems like your code takes the elements of the last iteration and combines them with the element of the current iteration (See 'Example' below). In order to do this with a traditional for loop, you need to prevent the list that is being looped over from being updated while looping. I do this by breaking the link with the 'final' list variable. This can be done with the list() function or by slicing it (then you would need to replace with as proposed by Morgan).
Example
For the array_list of [1,2,3] you get the following. Each line (except for the lines) is a new element of the 'final' list variable.
[]
--------------------------------
1 > array_list[0]
--------------------------------
2 > array_list[1]
1, 2 > array_list[0] + array_list[1]
--------------------------------
3 > array_list[2]
1, 3 > array_list[0] + array_list[2]
2, 3 > array_list[1] + array_list[2]
1, 2, 3 > array_list[0] + array_list[1] + array_list[2]

Searching for substring in element in a list an deleting the element

I have a list and I am trying to delete the elements that have 'pie' in them. This is what I've done:
['applepie','orangepie', 'turkeycake']
for i in range(len(list)):
if "pie" in list[i]:
del list[i]
I keep getting list index out of range, but when I change the del to a print statement it prints out the elements fine.
Instead of removing an item from the list you're iterating over, try creating a new list with Python's nice list comprehension syntax:
foods = ['applepie','orangepie', 'turkeycake']
pieless_foods = [f for f in foods if 'pie' not in f]
Deleting an element during iteration, changes the size, causing IndexError.
You can rewrite your code as (using List Comprehension)
L = [e for e in L if "pie" not in e]
Something like:
stuff = ['applepie','orangepie', 'turkeycake']
stuff = [item for item in stuff if not item.endswith('pie')]
Modifying an object that you're iterating over should be considered a no-go.
The reason to why you get a error is because you change the length of the list when you delete something!
Example:
first loop: i = 0, length of list will become 1 less because you delete "applepie" (length is now 2)
second loop: i = 1, length of list will now become just 1 because we delete "orangepie"
last/third loop: i = 2, Now you should see the problem, since i = 2 and the length of the list is only 1 (to clarify only list[0] have something in it!).
So rather use something like:
for item in in list:
if "pie" not in item:
new list.append(item)
Another but longer way would be to note down the indexes where you encounter pie and delete those elements after the first for loop

Categories