for-loops being used as while-loops (python) - python

I noticed that in Python it is possible to mimic while-loops using for-loops (I know very little about coding). I was then trying to construct two identical while-loops using the two different formulations. It seems to me that it is the case here (is it?)
# for-loop formulation
index = [0]
for a in index:
if a < 6:
index.append(a + 1)
# while-loop formulation
index = [0]
while index[-1] < 6:
index.append(index[-1] + 1)
What confuses me is that I thought that once the list index is updated/changed the for-loop would restart from the beginning, as the list could have changed dramatically at any cycle. For example if you run
#3
index = [0]
for a in index:
print a
if a < 6:
index.append(a+1)
if a > 3:
index = [1, 3]
print index
you get the output
0
[0, 1]
1
[0, 1, 2]
2
[0, 1, 2, 3]
3
[0, 1, 2, 3, 4]
4
[1, 3]
5
[1, 3]
where the a is not part of the new index [1, 3] in the last step.
Hence three questions:
Are the two programs #1 and #2 at the beginning different (for example, is their computational effort different?)
What is happening in program #3 (why is the 'for a in index:' command apparently ignored in the last step?), and how does the for-loop react to changes in the updating index in general?
Are there general guidelines on using for loops as while loops (I noticed that I found it helpful in a specific case, so I was wondering for some general tips in this direction).

The first two are basically the same, in that they will run the same number of iterations under various circumstances, and will leave index in the same state. If you replace index.append(a+1) with index.append(a), the for loop will run forever; similarly, if you replace index.append(index[-1] + 1) with index.append(index[-1]), the while loop will run forever. It's a bit fraught to modify a list you're iterating over like this, but it's sometimes a reasonable tactic.
What's happening with the for loop, by the way, is that when you start it, an iterator is created that gives index[0], then index[1], then index[2], etc., until it gets to an index[N] that doesn't exist. This iterator doesn't know or care that you're changing the list, so if you make the list longer, it will happily go on longer.
In program #3, there's some confusion of scoping. The iterator that's created when you enter the for loop doesn't care that you're reassigning the variable index inside the for loop: it just keeps iterating over the original list. When you do index.append(a+1) the first few times, that does affect the original list; but as soon as you do index = [1, 3], index no longer refers to the original list, so nothing you do to it affects the remaining course of the iteration. The last value that gets added is 5, because once a gets to 4 and a+1 is appended to index, index is clobbered and nothing more gets appended to the original list.
Lastly, the clobbering of index persists outside the for loop: it will have the value [1, 3] when the loop is done.

Related

Can I not put a for loop inside a for loop like this? [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. :)

Python: Why for loop is behaving weirdly?

I am just trying to merge two sorted lists into one sorted list. I know it is a simple task and plenty solutions online, but my question is different. Here's my code:
def merge(list1, list2):
len1 = len(list1)
len2 = len(list2)
list3 = []
pointer = 0
for i in range(len1):
if (list1[i] >= list2[pointer]):
while (pointer < len2 and list1[i] >= list2[pointer]):
list3.append(list2[pointer])
pointer += 1
i -= 1
else:
list3.append(list1[i])
while (pointer < len2):
list3.append(list2[pointer])
pointer += 1
return list3
if __name__ == "__main__":
print(merge([1, 2, 3, 10, 11, 22], [4, 5, 6, 7, 20, 21, 30]))
I did debugging and I was confused to see that when I decrease the value i by 1, for example from 3 to 2, on the next iteration it goes back to 4. I have no idea why? You can check it by running the code and seeing the result. I just need explanation why that is happening. Thanks
I was confused to see that when I decrease the value i by 1, for example from 3 to 2, on the next iteration it goes back to 4. I have no idea why?
Because for i in range(x) means "execute the for body with i assuming the values of 0 through x-1". Assigning a different value to i does not affect its value in the next iteration.
In other words, for i in range(10) is not a translation of C's or JavaScript's for (i = 0; i < 10; i++). Instead, you can think of it as for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]. Seen like that, it is clear that changing one value of i will not affect the subsequent value, which is blindly taken out of a pre-generated list. If you need to modify the iteration progress based on changing conditions, you can write the C/JS-style loop explicitly:
i = 0
while i < len1:
# ... loop body goes here ...
i += 1
Written like this, modifying i in loop body will affect iteration in the way you expected.
You are editing i inside the for loop that runs on i. I don't believe it will work the way you intend it.
Also, you can simply merge the lists and sort the outcome with this:
list1 = [1,2,3,10,11,22]
list2 = [4,5,6,7,20,21,30]
list3 = list1 + list2
list3.sort()
print(list3)
Hope this helps.
This is because range() is a generator function. It does not create the list of numbers, as you might expect, but generates a new number as you need it. And, even if it created the list, the numbers would be taken from the list, one after the other, regardless how you modify them. You can think of the result of range() in a sense as 'read-only'. user4815162342 below is right, you should not confuse it with a C-style loop. More like a Fortran loop, where the number of iterations is computed in advance.
From https://pynative.com/python-range-function/:
Python 3’s range uses the generator. Python 3’s range() will produce value when for loop iteration asked for it. i.e., it The range() doesn’t produce all numbers at once.
Python range() function returns an immutable sequence object of integers, so it is possible to convert range() output to the Python list. Use list class to convert range output to list. Let’s understand this with the following example.

Remove a list while iterating in python without copying to new list

I want to remove elements from my list while iterating the list. I don't think copying the list and performing the operations on either one will solve my problem.
I have a nested list,
Here as soon as I get the leftmost or the rightmost values of the list == maximum I append it to a new list Lst1 and pop the element from the original list else break from the loop.
lst= [[4, 3, 2, 1, 3, 5]]
lst1=[]
for i in range(len(lst)):
if lst[0][i]==max(lst[0]):
lst1.append(lst[0][i])
lst.remove(lst[0][i])
elif lst[0][maxsize_lst-1]==max(lst[0]):
lst1.append(lst[0][maxsize_lst-1])
lst.remove(lst[0][maxsize_lst-1])
else :
print("NO")
break;
I'm getting the below errors and sometimes I get index out of range probably because i'm removing the element and iterating the list again
ValueError: list.remove(x): x not in list
The output of list 1 should look something like:
5 4 3 3 2 1
EDIT
The final list is coming in the descending order but it's not a sorting problem. Here i will be first picking either the leftmost or rightmost element first and check if it is == max(lst). if any of them follows true i'm removing that element.Now my list would be having one less element. If it was leftmost pop then i would resume from index 1 to last ,vice versa if it was rightmost i would again do the same search from index 0 to maxsize-2 to do the same search. If nothing follows like leftmost or rightmost != Max(lst) then break and print No
There is a much simple solution:
lst = [[4, 3, 2, 1, 3, 5]]
print(sorted(lst[0], reverse=True))
Result:
[5, 4, 3, 3, 2, 1]
First of all, you want to remove values from your nested list at the level where they are: lst.remove(x) only searches in lst, not in lst and all possible lists nested in lst. That will solve your problem with ValueError.
Second, simply running your example by hand tells you why it isn't working: you are never updating maxsize_lst, therefore as soon as you pop out an item this value is no longer valid. A simple solution would be to use python's negative indexing system to access the last value of your list: lst[-1]. But even then, if your goal is to get all values in your list sorted, your code cannot do it: on the first step of your example already,
with i=0, you remove 5 from the list (last item, max of values)
next step, i=1, and you will never again access the value at i=0
But then maybe that's not a problem for you, it isn't clear what you want to achieve with your code...
Edit: I re-read your question, if what you want is actually to pop the left/rightmost value when it is a maximum from your old list to your new list, then you shouldn't be iterating over your list with a for loop but rather using a while loop like that:
size_lst = len(lst[0])
while size_lst > 0:
if lst[0][0] == max(lst[0]):
# Leftmost element max of the list
lst1.append(lst[0].pop(0) # Pop leftmost element of lst[0] into lst1
size_lst -= 1 # Record that you decreased the size of your list
elif lst[0][-1] == max(lst[0]):
# Same with the rightmost element
lst1.append(lst[0].pop(-1)
size_lst -= 1
else:
break
It looks like you're sorting the first list. This can be achieved much more easily. The sorted function will automatically sort it from least to greatest, and then you can use the reversed function to sort greatest to least. Try:
lst1 = reversed(sorted(lst[0]))
EDIT: If you need to use the method put forward in the original code, I caught a mistake in your for loop. You are taking the length of lst, not the sublist, the code should be the following:
for i in range(len(lst[0])):
Also, I don't know if you established a variable maxsize_list, but you can get the last element of the list easily with lst[0][-1]. Finally, your error is being caused by you trying to remove lst[0][-1] from lst, not lst[0]. This is your code without syntax errors. I believe there is a symantic error that occurs when the maximum is at the end.
lst= [[4,3,2,1,3,5]]
lst1=[]
for i in range(len(lst[0])):
if lst[0][i]==max(lst[0]):
lst1.append(lst[0][i])
lst[0].remove(lst[0][i])
elif lst[0][-1]==max(lst[0]):
lst1.append(lst[0][-1])
lst[0].remove(lst[0][-1])
else :
print("NO")
break;
#PRMoureu's comment is a big hint to the answer about what's going wrong.
In your sample data, your list is of size 6. You're iterating over the indices, so from 0 through 5. As you progress through your loop, you remove things from your list, but then you continue to look for it. So at some point, you look at lst[0][i] for an i that no longer exists, and that's why you get your error. This will happen as long as you're using the index.
But you don't need the index into the list. You need the value at it. So the recommendation is a very good idea: simply iterate on the list itself, instead of on its indices. This will give you code like this:
lst= [[4, 3, 2, 1, 3, 5]]
lst1=[]
for val in lst[0]:
print(val)
if val == max(lst[0]):
print("a")
lst1.append(val)
lst[0].remove(val)
print(lst[0])
# this shouldn't even be necessary; you should be able to sort just as well without it
elif lst[0][-1]==max(lst[0]):
print("b")
lst1.append(lst[0][-1])
lst[0].remove(lst[0][-1])
else :
print("NO")
break;
Note, python wouldn't use a construct like maxsize_lst. Instead, it would just use lst[0][-1] to get the last element. I fixed the several places where you were referring to lst instead of lst[0], and made your lst definition actually be valid by putting commas between the values.
When I run this code, I get "NO" as a result. Leave the print statements in to understand why. The first time, you have a 4. It isn't the max, so you look to see if the last value is a max. It is, so it gets added. The second time, you have a three, which is again not your max. Nor is the last remaining value (the other 3), so it says "NO" and aborts. You've got the idea by using a break statement, but you need another loop around it that would continue until your list is empty.
To get this to work, you need an outer loop similar to as follows:
lst= [[4, 3, 2, 1, 3, 5]]
lst1=[]
reset = True
while len(lst[0]) != 0 and reset:
print(lst[0], lst1)
reset = False
for val in lst[0]:
print(val)
if val == max(lst[0]):
print("a")
lst1.append(val)
lst[0].remove(val)
reset = True
break
elif lst[0][-1]==max(lst[0]):
print("b")
lst1.append(lst[0][-1])
lst[0].remove(lst[0][-1])
reset = True
break
else :
print("NO")
break
Note that I did need to add a break even when popping from the left side. Without that, the end result was that lst1 had a value of [5, 4, 3, 3, 2], and lst[0] still had [1] in it.

How to use more than one condition in Python for loop?

How to use more than one condition in Python for loop?
for example in java:
int[] n={1,2,3,4,6,7};
for(int i=0;i<n.length && i<5 ;i++){
//do sth
}
How dose the python for loop do this?
The Python for loop does not, itself, have any support for this. You can get the same effect using a break statement:
n = [1, 2, 3, 4, 6, 7]
for i in n:
if i >= 5:
break
# do something with i
In Python, a for is really a foreach that iterates over some "iterator" or some "iterable object". This is even true when you just want to repeat a specific number of times:
for i in range(1, 8):
# do something with i
In Python 2.x, the above for loop builds a list with the numbers 1 through 7 inclusive, then iterates over the list; in Python 3.x, the above loop gets an "iterator object" that yields up the values 1 through 7 inclusive, one at a time. (The difference is in the range() function and what it returns. In Python 2.x you can use xrange() to get an iterator object instead of allocating a list.)
If you already have a list to iterate over, it is good Python to iterate over it directly rather than using a variable i to index the list. If you still need an index variable you can get it with enumerate() like so:
n = [3, 5, 10, "cat", "dog", 3.0, 4.0] # list can contain different types
for i, value in enumerate(n):
# we only want to process the first 5 values in this list
if i >= 5:
break
# do something with value
EDIT: An alternate way to solve the above problem would be to use list slicing.
for value in n[:5]:
# do something with value
This works if n is a list. The for loop will set value to successive items from the list, stopping when the list runs out or 5 items have been handled, whichever comes first. It's not an error to request a slice of longer length than the actual list.
If you want to use the above technique but still allow your code to work with iterators, you can use itertools.islice():
from itertools import islice
for value in islice(n, 5):
# do something with value
This will work with a list, an iterator, a generator, any sort of iterable.
And, as with list slicing, the for loop will get up to 5 values and it's not an error to request an islice() longer than the number of values the iterable actually has.
The direct equivalent of your Java code is a while loop:
n = [1, 2, 3, 4, 6, 7]
i = 0
while i < len(n) and i < 5:
# do sth
i += 1
You could also do:
n = [1, 2, 3, 4, 6, 7]
for x in n[:5]:
# do sth
Here is one way to have two or more conditions with the for loop, which is what the question actually asks. The point I am trying to make is that it can be done, but isn't very pythonic and it's generally not a good idea to try to rewrite code from another language line by line.
from itertools import takewhile, count
n=[1,2,3,4,6,7]
for i in takewhile(lambda i:i<len(n) and i<5, count()):
print(i)
You can write a while loop with the same sort of logic (but in Python, && is spelled and, and || is spelled or); or you can use a for loop that iterates over a sequence and then add extra logic to break out of the loop.
In Python, we prefer not to use integers to index into a container. This would actually break a lot of our code, because we do not check the data types at compile-time, and some containers cannot be indexed into. Wanting to index into a container is already a design smell: the fact that everything is in a sequential container is supposed to mean that none of the elements are "special", or we would have kept them separate. Iteration is iteration, and we should not go out of our way to make it more complicated.
Assuming that you want the ith item of n somewhere in the loop, enumerate saves us from typing n[i] all over the place - the value will be stored in the variable item
n = [1,2,3,4,6,7]
for i, item in enumerate(n):
if i>=5:
break
# do something
print item # for example
Note that the loop will terminate automatically if the length of n is less than 5
Python's for is not like the for in languages based on C syntax. In Python, for iterates over a sequence, whereas in C it loops while a condition is true. This is a profound difference.
The C-like for can be replaced roughly with the following code:
i = 0;
while (i < n.length && i < 5) {
// do sth
i++;
}
(There are some complications from break and continue, but let's ignore those for now.)
This rewrite also indicates a way to do what you want in Python: use while:
i = 0
while i < len(n) and i < 5:
// do something
i += 1
In your particular case, however, it is easiest to use for with a suitable list of indexes:
for i in range(min(len(n), 5)):
// do something
range will return a list of integers (0, 1, 2, ...) and what you want is to have the list go up to 5, or the length of your array, whichever is smaller. The above code achieves that.
The for statement in Python iterates a "list" of objects (I put list in quotes because I mean it in the generic sense, it can iterate over anything that is iterable).
To code a conditional loop (rather than iterating until completion), use while:
n = [1, 2, 3, 4, 6, 7]
i = 0
while i < len(n) and i < 5:
# do stuff
i += 1
Now just to be complete, your example could also be written as:
n = [1, 2, 3, 4, 6, 7]
for i in range(0,min(len(n),5)):
# do stuff
or:
n = [1, 2, 3, 4, 6, 7]
for i in range(0,len(n)):
if i >= 5:
break
# do stuff

Why does Python skip elements when I modify a list while iterating over it?

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. :)

Categories

Resources