Python: Why for loop is behaving weirdly? - python

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.

Related

List not returning the proper values as an output

I am trying to write a small function (as an exercise). This function takes a list of values and returns the values that are odd numbers. I have gotten the function to give me the right answer with the print() function, but I am not able to do the same with a return statement.
def odd_nr(list1):
i = 0
for list1[i] in list1:
if list1[i] % 2 != 0:
print(list1[i])
i += 1
return list1
odd_nr([1,2,3,4,5,6])
The output is:
1
3
5
[1, 3, 5, 6, 5, 6]
I am not able to figure out why the return statement gives this output. I tried different indentations, I tried different variants of the return statement, but I just can't seem to get it to work.
What am I doing wrong?
try:
def odd_nr(list1):
results = []
for number in list1:
if number % 2 != 0:
print(number)
results.append(number)
return results
odd_nr([1,2,3,4,5,6])
Further Explanation:
Any function can take something and return something. This something can also be nothing, None.
Your function takes a list, and returns a list, but it is returning the same list that it is taking in.
The print statement is not a return value. Which means a print is not something that the function returns, it is a side-effect, a side door, for us humans to see, mostly.
To return a list of only odd numbers, you need to collect them in another list as you iterate through your original input list.
Then once you are done, return the list that has collected all the odd numbers. Hope this helps.
I also updated your code a bit, for list1[i] in list1, even though it works, it is hard to understand and it does so for the wrong reasons, see below. You can simply do for number in list1 and also not worry about incrementing any counters(i in your case).
Explanation on why for list1[i] in list works:
This is interesting. By choosing list1[i] as the current iteratee, we will be mutating our list while iterating; it is lucky that each iteratee is equal, in value to the list element it is mutating. Here is an example to illustrate what is happening. It is easy to see when we do not update i:
list1= [1,2,3,4]
i=0
for list1[i] in list1:
print(list1[i])
print(list1)
Output:
1
2
3
4
[4, 2, 3, 4]
You just need to return list1[:i] instead of returning the whole list1. Demo with that:
>>> odd_nr([1,2,3,4,5,6])
1
3
5
[1, 3, 5]
With your unusual but correct for list1[i] in list1 and the according update of i, you're moving all the odd numbers to the front of the list and counting them with i. All that's left to do is to only return that front portion.
Alternatively, delete the unwanted back portion with del list1[i:] before you do return list1.

for-loops being used as while-loops (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.

How does a for loop know to increment

When defining a Fibonacci sequence the following code was used:
def fibonacci(n):
current=0
after=1
for i in range(1,n):
current,after=after,current+after
return after
How does the for loop know to increment by one every time we pass through? I tried using a while loop while e<=n: and it returned an error as I hadn't defined e.
A for loop in python iterates through items or generators. In your example, range(1,n) will return a list of elements between [1, n) in python2, or a generator that will yield the same items in python3.
Essentially, a for loop can iterate any kind of iterables:
for item in [1, 6, 8, 9]:
print(item)
It will print 1, 6, 8 and 9.
Using range, there is a 3rd optional parameter which allows you to specify the increment:
range(1, 10, 1) # The defaut [1 .. 9] numbers
range(1, 10, 2) # From 1 until 9 with 2 of increment
for loop does not increment, instead it iterates
The for loop does not increment, instead it is asking so called iterable to provide values to work with.
In your example, the iterable is range(1,n).
In Python 2.x, the range(1, n) creates a list of values and the for loop is only asking the list to provide next value in each run, until the list gets exhausted from values.
In Python 3.x the range(1, n) is optimized and returns special iterable type <range>, which when asked by for loop to provide next value provide it.
By default if you don't specify your step i.e for i in range (start,stop,step) the increment is considered as one otherwise it's your step that you specified.

Python for loop?

I am having trouble understanding a Python for loop. For example, here is some code I've made while I'm learning:
board = []
for i in range (0,5):
board.append(["O"] * 5)
Don't worry about what the code does, I just don't understand what the "i" variable means.
Think of it as substitution.
range(0,5) is [0,1,2,3,4]. The for-loop goes through each element in the list, naming the element i.
for i in range(0,5):
# Starts with 0
print i # prints 0
# now goes back, goes through next element in list: 1.
Prints 0,1,2,3,4.
In your example, i is a placeholder. It is used simply just to loop something x amount of times (in this case, five as the length of range(0,5) is 5)
Also, have fun learning python at Codecademy (I recognise the task :p)
It's an iterator, you can see it as a bucket that stores the result of each iteration; the thing that adds confusion is the fact that it's simply unused in your script, this is another script with a "more involved" use of iterators.
fruits = ['banana', 'apple', 'strawberry', 'coconut', 'cherry']
for yup in fruits:
print(yup)
as you can see you can name it as you want, it's the syntax that makes that word an iterator.
It's an unused variable. Python syntax requires a variable in that position, but you don't do anything with it since you simply want to repeat an action 5 times.
Some people prefer the convention of naming an unused variable like this _:
for _ in range(5)
but this name can interfere with gettext.
In short, i refers to the current element in the list.
Your list is defined as: 0, 1, 2, 3, 4 and 5. Therefore i will iterate this list and assign itself to the next item, i is 0, next iteration i will be 1, next iteration i will be 2 etc.
Directly from python.org:
The for statement in Python differs a bit from what you may be used to in C or Pascal. Rather than always iterating over an arithmetic progression of numbers (like in Pascal), or giving the user the ability to define both the iteration step and halting condition (as C), Python’s for statement iterates over the items of any sequence (a list or a string), in the order that they appear in the sequence. For example (no pun intended)
words = ['cat', 'window', 'defenestrate']
for w in words:
print w, len(w)
Results in:
cat 3
window 6
defenestrate 12
http://docs.python.org/2/tutorial/controlflow.html
The for loop iterates over the given list of objects which is [0, 1, 2, 3, 4] obtained from range(0,5) and in every iteration, you need a variable to get the iterated value. That is the use i here. You can replace it with any variable to get the value.
for n in range(0, 5):
print n #prints 0, then 1, then 2, then 3,then 4 in each iteration
Another example:
for n in ('a', 'b', 'c'):
print n #prints a, then b, then c in each iteration
But in the code you have given, the variable i is not used. It is being used. just to iterate over the list of objects.
In c/java the for loop wiil be:
for(int i=0;i<=10;i++)
{
//for-loop-body
}
here for every iteration i will be incrementing +1 value till i reaches 10, after that it comes out of loop.
In same way,in python the for loop looks like:
for i in range(0,10):
//for-loop-body
here i performs same operation and i is just a variable to increment a value.

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

Categories

Resources