Confusing behavior of Python for statement - python

I have the following python code:
x = range(0,10)
print x
for number in x:
print(number)
if number%2<> 0:
x.remove(number)
print x
Oddly, the out put is this:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
0
1
3
5
7
9
[0, 2, 4, 6, 8]
The first and last lines are right, but why are 2,4,6, and 8 not printed out? The print statement is not inside the if statement!
I'm using python(x,y) on windows 7. Also, I'm new to Python...I'm used to C++

You're removing items from the list (x.remove) while iterating over it (for number in x).
for-in maintains an index separately, and that is why modifying the list gives unexpected behavior.

The list is iterated using its index, but when you remove elements you skip some indices.
E.g:
[0,1,2,...] # (iterator is at second - print 1)
remove
[0,2,3,...] # (iterator is still at second)
iterator advances
[0,2,3,...] # (iterator is at third - print 3)

Add some print statements for clarity:
x = range(10)
for index, number in enumerate(x):
print "x is ", x
print "element is", number
print "index is ", index
print
if number % 2 == 0:
x.remove(number)
And the output:
x is [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
element is 0
index is 0
x is [1, 2, 3, 4, 5, 6, 7, 8, 9]
element is 2
index is 1
x is [1, 3, 4, 5, 6, 7, 8, 9]
element is 4
index is 2
x is [1, 3, 5, 6, 7, 8, 9]
element is 6
index is 3
x is [1, 3, 5, 7, 8, 9]
element is 8
index is 4
As you can see, index keeps going up by 1, even though you remove elements from the list. This is what causes the loop to skip elements.
As others have pointed out, looping over a list and removing elements from it isn't a good idea. Loop over a copy instead:
for number in x[:]:
Or:
for number in list(x):
Better yet, make a new list with a list comprehension:
[number for number in x if number % 2 == 0]

Basically you can have weird behavior when you iterate something while removing at the same time. What's happening is that you're skipping some values due to them being shifted to indexes that you already iterated over.
A better way of doing what you want (filter out some items), would be to use a list comprehension, for instance:
[x for x in range(10) if x%2==0]
You could simply use the range step to only create even numbers, but the above solution let's you filter out on any condition.
The reason why some numbers aren't printed is that the values are changing positions while you loop and remove them. When you remove the 1, you can imagine all the values being shifted by one position, the iterator is pointing to where the 2 used to be, but now the value there is 3, so the 2 is never printed. And this goes on for the rest of the values.

As Mark Rushakoff mentions, you shouldn't modify something while you're iterating over it. Change your for number in x to for number in x[:] and it will work as you expect, though. In this case you're iterating over a copy.

Don't modify a list you're iterating over. Others suggest copying the list or collecting a new list of things to remove. Instead, collect the ones you want to remain. This is faster than copying the list and removing from the copy not being iterated over, and faster than collecting the ones to remove and then removing them.
evens = []
for number in x:
if number%2 == 0:
evens += [number]
print(evens)

Related

How remove() function was executed in python?

Actually I was working with this code:
m=[0,1,2,3,4,5,6,7]
for i in m:
m.remove(i)
print(m)
This looks pretty simple.
for i in m:
print(i)
will print every elements in the list m. So the for part in our code will extract every elements of the list and remove it one by one. So the output that I except is [].
But I got [1,3,5,7].
How it can be the output and where I was wrong?
when you use (for i in m), i starts from index zero so i is the element of index 0 which its value is 0 then in for loop you'll delete zero value.
now your list is [1,2,3,4,5,6,7] and i is the element of index 1 which its value is 2.
after deleting 2 your list would be like [2,3,4,5,6,7], now i is the element of index 2 which its value is 4 and ....
here is how this for loop works and why the output is like that.
to remove all element in a list you can use:
for i in range(len(m)):
m.remove(m[0])
or
m.clear()
If you know how to use a debugger, that would be the ideal way to diagnose this, otherwise, try running this code to see what's going on:
x = [0, 1, 2, 3, 4, 5, 6, 7]
for i in x.copy():
x.remove(i)
print(x)
print(x)
m = [0, 1, 2, 3, 4, 5, 6, 7]
for i in m:
m.remove(i)
print(m)
print(m)
During a given execution, the for loop accesses an element and then deletes it. For example, it first deletes the element at index 0, with value 0, leaving you with [1, 2, 3, 4, 5, 6, 7]. Then it moves on to the element at index 1, value 2, and deletes that, leaving you with [1, 3, 4, 5, 6, 7].
In general, avoid messing around with a list in Python. If you have to delete all elements, use m.clear(). If you have to delete some elements or do some other stuff, prefer to use list comprehension to create a new object.
What happens here is, you're removing items in the list on the go.
Eg,
On the first remove() call, it removes 0 from the list (current
index is 0).
On the second remove() call, the current index is 1,
but the list contains [1,2,3,...]. Now the the index 1 contains "2"
instead of "1". Because 0 has been removed and the size of list is
just changed.
So to clear list in python, you can use,
m.clear()
i don`t know why it is not remove each item, but i worked on it and it works like this:
m=[0,1,2,3,4,5,6,7]
while m != []:
[m.remove(i) for i in m]
print(m)

How do I prevent python skipping an integer during an iteration after removing an integer from the list? [duplicate]

This question already has answers here:
How to remove items from a list while iterating?
(25 answers)
Closed 2 years ago.
I am attempting to remove duplicates from a list of numbers; however, the code doesn't remove all the duplicates. After debugging, I realized that an integer is skipped if the previous iteration results in the removal of that integer (neither of the 2's in the list are removed as they are preceded by 5 which was a duplicate):
numbers_list = [5, 2, 546, 7, 3, 5, 2, 7, 29, 6, 5, 7]
for item in numbers_list:
if numbers_list.count(item) > 1:
numbers_list.remove(item)
else:
pass
print(numbers_list)
EDIT: I know there are other ways to remove duplicates from a list but I want to know how to ensure that iterations aren't skipped.
Just don't modify a list during iteration -- strange things happen, as you observed. Changing the list affects the iterator, and the results of that are, I think, implementation-defined. Doing it this way is considered bad style, exactly because of those weird effects. The easiest solution for your concrete problem of removing duplicates, which is equivalent to ensuring uniqueness, is to use a set:
unique_list = list(set(numbers_list))
You can leave out the list part if you are only interested in the result as an iterable. However, this (likely) won't preserve the orginal order in numbers_list -- if you need the order, some different approach is needed, like this:
def makeunique(l):
i = 0
while i < len(l):
j = i + 1
while j < len(l):
if l[j] == l[i]:
del l[j]
j += 1
i += 1
return l
(This has quadratic complexity, though.)
if you need to remove duplicates you can use this
numbers_list = [5, 2, 546, 7, 3, 5, 2, 7, 29, 6, 5, 7]
list_no_dubl = list(set(numbers_list))
print(list_no_dubl)
If you want to remove duplicates you can do:
mylist = list(dict.fromkeys(mylist))
If you don't want to skip iterations you can use a while, for example:
counter = 0
while counter < len(mylist):
# if you want to remove an item, don't increase counter, otherwise yes
counter++
The function set() constructs a set object, which ensures that there are only unique, unordered collection of objects.
The function list() converts the set object back into a list.
numbers_list = [5, 2, 546, 7, 3, 5, 2, 7, 29, 6, 5, 7]
deduped_list = list(set(numbers_list))
print(deduped_list)
>>[2, 3, 546, 5, 6, 7, 29]

Why wasn't the last number in that list removed?

This is my Python program:
def highest_even(li):
for num in li:
if num % 2 != 0:
li.remove(num)
return li
print(highest_even([10,2,3,4,5,80,15,99]))
And the output is :
[10, 2, 4, 80, 99]
I want to know why 99 wasn't deleted.
Thanks.
It's generally a bad idea to modify a list that you're iterating over, all sorts of strange things can happen. The particular strange thing that's happening here has to do with the way Python iterates over lists.
Think of Python maintaining the position of the current item for iterating. When you delete an item, the others all shift "left" in the list but Python still advances the position. That means, if you have two odd numbers in a row (like 15 and 99)(a), deleting the 15 moves 99 to the left (to where the 15 was) but the next iteration will be looking at where the 99 was before shifting, not where it is now.
For example, consider the list [1, 3, 6, 8], these are the steps Python will take:
List Pos newList newPos comment
---- --- ------- ------ -------
[1, 3, 6, 8] 0 [3, 6, 8] 1 Odd 1, delete, advance.
[3, 6, 8] 1 [3, 6, 8] 2 Even 6, leave, advance.
[3, 6, 8] 2 [3, 6, 8] 3 Even 8, leave, advance.
[3, 6, 8] 3 iteration finished.
You can see that consecutive odd numbers cause a problem here, it will only delete alternate ones.
As to the solution: if, as your code suggests, you just want the even numbers in the list, you can use the much more succinct (and Pythonic) list comprehension (no need even for a function to do this):
myList = [10, 2, 3, 4, 5, 80, 15, 99]
evens = [item for item in myList if item % 2 == 0]
# gives [10, 2, 4, 80]
And, for completeness, since your function name seems to indicate you want the highest even number, that would be something like:
biggestEven = max([item for item in myList if item % 2 == 0])
# gives 80
(a) Your problem actually has nothing to do with the fact the 99 is at the end of the list, any consecutive odd numbers, anywhere in the list, would cause the same issue.
do not modify a list you iterate in
you can copy before iterate on
def highest_even(li):
for num in li.copy():
if num % 2 != 0:
li.remove(num)
return li
print(highest_even([10,2,3,4,5,80,15,99]))
Execution :
[10, 2, 4, 80]
As so many comments mention: it's unsafe (or at least it presents some unexpected behavior) to modify a list as you iterate over it. The usual fix here for lists that aren't giant is simply to copy the list when you go to iterate on it.
for num in li[:]:
# everything as before
That little slice syntax makes Python take the list li, create a new list of its entire contents, and iterate over that, instead. Now since you're removing things from li, but iterating over the copy of li made by slicing it, there's no issue.
this is because of you iterated through the list while editing it.
only_even = []
for n in lst:
if not n % 2:
only_even.append(n)
other methods
only_even = [n for n in lst if not n % 2]
only_even = list(filter(lambda x: not n % 2, lst))
You should never update the data structure you are iterating in, to maintain the invariants of the loops.
If you print the li, and num right after the for num in li:, you'll see that after you remove the element from the list, the next element is skipped, meaning that the indexed is moved forward, same thing happens to 99 element.
You can check it here.
def highest_even(li):
for num in li:
print(li, num)
if num % 2 != 0:
li.remove(num)
return li
gives the output:
In [3]: highest_even([10,2,3,4,5,80,15,99])
([10, 2, 3, 4, 5, 80, 15, 99], 10)
([10, 2, 3, 4, 5, 80, 15, 99], 2)
([10, 2, 3, 4, 5, 80, 15, 99], 3)
([10, 2, 4, 5, 80, 15, 99], 5)
([10, 2, 4, 80, 15, 99], 15)
Out[3]: [10, 2, 4, 80, 99]
You should not iterate through a list and delete the elements of the same list.
Since the index is used iterate in a loop.
0- 10
1- 2
2- 3
3- 4
4- 5
5- 80
6- 15
7- 99
And as when you delete the elements from list it skips the next element.
In your example, for the indexes 0 & 1 nothing changes. But when index =3 and as per the condition this element is removed and the list would be updated to [10,2,4,5,80,15,99].
After index =3 the next index is 4 and li[4] equals 5 and not 4. And your condition is not even checked for the element 4. It just happened to be even to be correct. Having some odd instead of 4 would again give you wrong output.
Same is the case with the last element 99. Since the previous element 15 or index = 6 is removed the length of the list reduced by 1 and it does not loop for index = 4 as index reached its max value of updated list is 5(after removing 3,5 & 15).
You shouldn't update a list while iterating over it. But you can make it work by going backwards or you will be cutting a tree branch while sitting on it.
li = [10,2,3,4,5,80,15,99]
for i in range(len(li) - 1, -1, -1):
if (i%2 != 0 ):
del li[i]
print(li)
What's happening there is that you are changing a list while you iterate over it, but the iterator on that list will not get updated with your changes.
you can try this:
for i in range len(li):

Iterating portions of lists - PYTHON

Bit of a generic noob question. I am heavily dealing with long lists of integer/float values and wasting a lot of time.
my_list = [1,2,3,4,5,6,7,8,9,10.....] etc.
Say I want to pass a portion of that list to a function. It could be the first 3 elements, then the following 3 etc....it could also be in groups of 4,5,6....it might even be required that I take different a different amount of elements each time.
def myfunc(x,y,z):
do something
return something
What is the most efficient way to iterate by a specified number of values, as efficiency is always appreciated and these simple iterations are the places where I can gain something.
len_ml = len(my_list)
for i in range(0, len_ml, 3):
chunk = my_list[i:min(len_ml, i+3)]
This is a way. I am not sure that it is the best, though.
With a list you can get just the items you want with list[start:end].
So to skip the first list[1:] or last list[:-1] or first 3 and last 3 list[3:-3]
if you don't know how may items are in the list to start you can do a len(list) to get number. So if you had X items and wanted 3 groups :
numberofgroups = len(list) / 3
To do for loop over only certain ones:
start_index=1
end_index=-1
for item in my_list[start_index:end_index]
print item
>>>my_list = [1,2,3,4,5,6,7,8,9,10]
>>>group_len = 3 #you can change the length as per your requirement i.e 2,3,4,5,6...
>>>for i in range(0,len(my_list)):
if i*group_len < len(my_list):
my_list[i*group_len:(i+1)*group_len]
else:
break;
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
[10]
Result for group_len = 5
[1, 2, 3, 4, 5]
[6, 7, 8, 9, 10]

Twist? on removing items from a list while iterating in python

I know there are a lot of similar questions regarding this topic and I've researched enough to make me think this twist on the question hasn't been discussed or is just hard to find. I understand you can't just remove items from a list you are iterating unless you use some kind of copy or something. The example I come across time and
list=[1,2,3,4,5,6,7,8,9,10]
for x in list[:]:
if x==3:
list.remove(x)
list.remove(7)
This should remove 3, and 7 from the list. However, what if I have the following:
for x in list[:]:
if x==3:
list.remove(x)
list.remove(7)
if x==7:
list.remove(9)
This iteration removes 3,7, and 9. Since 7 "should" have been removed from a prior iteration, I don't actually want 9 to be removed (since there shouldn't be a 7 in the list anymore). Is there any way to do this?
You could add another check to the if statement:
if x == 7 and x in list:
list.remove(9)
Remember that you are removing from the original list and iterating over a copy. The copy still has the 7 in it, so the 9 will be removed as well.
Try this.
list1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for x in list1:
if x == 3:
list1.remove(x)
list1.remove(7)
if x == 7:
list1.remove(9)
print list1
Output: [1, 2, 4, 5, 6, 8, 9, 10]
Instead of iterating over a new list, you can work on the same one.

Categories

Resources