for loops in Python - how to modify i inside the loop - python

This code was written in Python 3.6 in Jupyter Notebooks. In other languages, I am pretty sure I built loops that looked like this:
endRw=5
lenDF=100 # 1160
for i in range(0, lenDF):
print("i: ", i)
endIndx = i + endRw
if endIndx > lenDF:
endIndx = lenDF
print("Range to use: ", i, ":", endIndx)
# this line is a mockup for an index that is built and used
# in the real code to do something to a pandas DF
i = endIndx
print("i at end of loop", i)
In testing though, i does not get reset to endIndx and so the loop does not build the intended index values.
I was able to solve this problem and get what I was looking for by building a while loop like this:
endRw=5
lenDF=97 # 1160
i = 0
while i < lenDF:
print("i: ", i)
endIndx = i + endRw
if endIndx > lenDF:
endIndx = lenDF
print("Range to use: ", i, ":", endIndx)
# this line is a mockup for an index that is built and used
# in the real code to do something to a pandas DF
i = endIndx
print("i at end of loop: ", i)
Question: is there a way to modify the i from inside the for loop in python? Is there a way to do what I did with the while loop using a for loop in Python?
Solved the problem with while but just curious about this.

You can modify the loop variable in a for loop, the problem is that for loops in Python are not like "old-style" for loops in e.g. Java, but more like "new-style" for-each loops.
In Python, for i in range(0, 10): does not behave like for (int i = 0; i < 10; i++) {, but like for (int i : new int[] {0, 1, ..., 10}}.
That is, in each iteration of the loop, the loop head will not modify the loop variable (e.g. increment it), but assign a new value to it, i.e. the next value from the given iterable (a range in your case). Thus, any modification that you did in the previous iteration are overwritten.
If you want to loop a known number of iterations or for every item in an iterable, use a for loop, but if you want to loop until a certain condition (no longer) holds, as in your case, use while.

for loops operate on iterables. In for i in range(0, lenDF), i is assigned the next value in the range on each round of the loop regardless of how it is used in the loop. The question then, is whether there is a clean way to write an iterable that does what you want. In this case, all you want is to advance by a fixed step and adjust the final step length to account for the end of data.
endRw=5
lenDF=97 # 1160
for i in range(0, lenDF, endRw):
endIndx = min(i+endRw, lenDF)
print("Range to use: ", i, ":", endIndx)

This answer is unlikely to be useful, but since you were just curious:
The closest I think to what you want to do would be using a generator and its send method:
>>> def jumpable_range(start, stop):
... i = start
... while i <= stop:
... j = yield i
... i = i + 1 if j is None else j
...
>>> R = jumpable_range(2, 10)
>>>
>>> for i in R:
... if i==5:
... i = R.send(8)
... print(i)
...
2
3
4
8
9
10
>>>

Taking the original question literally:
#Tobias_k provides a good explanation of when to use while versus for loops, and the use case of this question fits while better (at least for Python). In short: you cannot directly modify the i in for i in because of how this code works under the covers in Python. So while should be used for a use case where you need to change your counter inside a loop (in Python).
#tdelaney provides a good answer in terms of refactoring the code using a Python for loop given the way Python behaves (the accepted answer to this question).
#PaulPanzer provides concepts that, while over-complicated, are useful to students to explore new concepts; but the answer solves the for loop problem by using a while loop inside an iterator and calling that into the for loop.
Even so, the concepts explored that play to the use of yield and iterators are worth exploring. If we take these concepts and attempt to re-write the original code to exploit them, this is what that code would look like:
def jumpable_range(start, stop):
i = start
while i <= stop:
j = yield i
i = i + 1 if j is None else j
endRw=5
lenDF=97 # 1160
Q = jumpable_range(0,lenDF)
for i in Q:
print("i: ", i)
endIndx = i + endRw
if endIndx > lenDF:
endIndx = lenDF
if i == endIndx: break
print("Range to use: ", i, ":", endIndx)
# this line is a mockup for an index that is built and used
# in the real code to do something to a pandas DF
i = Q.send(endIndx-1)
print("i at end of loop", i)

You always can set the value of i in a for loop. The problem is your setting value statement is before the implicit for loop setting value statement and covered by latter. You cannot change the rule to make latter statement useless. You shouldn't do this even you can. Just change to use proper conditional variable.

Related

How can I make a range from 1 to infinity in python [duplicate]

In C, I would do this:
int i;
for (i = 0;; i++)
if (thereIsAReasonToBreak(i))
break;
How can I achieve something similar in Python?
Using itertools.count:
import itertools
for i in itertools.count(start=1):
if there_is_a_reason_to_break(i):
break
In Python 2, range() and xrange() were limited to sys.maxsize. In Python 3 range() can go much higher, though not to infinity:
import sys
for i in range(sys.maxsize**10): # you could go even higher if you really want
if there_is_a_reason_to_break(i):
break
So it's probably best to use count().
def to_infinity():
index = 0
while True:
yield index
index += 1
for i in to_infinity():
if i > 10:
break
Simplest and best:
i = 0
while not there_is_reason_to_break(i):
# some code here
i += 1
It may be tempting to choose the closest analogy to the C code possible in Python:
from itertools import count
for i in count():
if thereIsAReasonToBreak(i):
break
But beware, modifying i will not affect the flow of the loop as it would in C. Therefore, using a while loop is actually a more appropriate choice for porting that C code to Python.
Reiterating thg435's comment:
from itertools import takewhile, count
def thereIsAReasonToContinue(i):
return not thereIsAReasonToBreak(i)
for i in takewhile(thereIsAReasonToContinue, count()):
pass # or something else
Or perhaps more concisely:
from itertools import takewhile, count
for i in takewhile(lambda x : not thereIsAReasonToBreak(x), count()):
pass # or something else
takewhile imitates a "well-behaved" C for loop: you have a continuation condition, but you have a generator instead of an arbitrary expression. There are things you can do in a C for loop that are "badly behaved", such as modifying i in the loop body. It's possible to imitate those too using takewhile, if the generator is a closure over some local variable i that you then mess with. In a way, defining that closure makes it especially obvious that you're doing something potentially confusing with your control structure.
If you want to use a for loop, it's possible to combine built-in functions iter (see also this answer) and enumerate for an infinite for loop which has a counter. We're using iter to create an infinite iterator and enumerate provides the counting loop variable. The start value is zero by default, but you can set a different start value with the start argument.
for i, _ in enumerate(iter(bool, True), start=1):
input(i)
Which prints:
1
2
3
4
5
...
If you're doing that in C, then your judgement there is as cloudy as it would be in Python :-)
For a loop that exits on a simple condition check at the start of each iteration, it's more usual (and clearer, in my opinion) to just do that in the looping construct itself. In other words, something like (if you need i after loop end):
int i = 0;
while (! thereIsAReasonToBreak(i)) {
// do something
i++;
}
or (if i can be scoped to just the loop):
for (int i = 0; ! thereIsAReasonToBreak(i); ++i) {
// do something
}
That would translate to the Python equivalent:
i = 0
while not there_is_a_reason_to_break(i):
# do something
i += 1
Only if you need to exit in the middle of the loop somewhere (or if your condition is complex enough that it would render your looping statement far less readable) would you need to worry about breaking.
When your potential exit is a simple one at the start of the loop (as it appears to be here), it's usually better to encode the exit into the loop itself.
def infinity():
i=0
while True:
i+=1
yield i
for i in infinity():
if there_is_a_reason_to_break(i):
break
def natural_numbers():
yield from map(sum, enumerate(iter(int,1)))
for i in natural_numbers():
if there_is_a_reason_to_break(i):
break;

In a while loop of Python, shall we put the increment sentence at the end or not?

I am a beginner of Python, and now learning while loop. If we want to print out all the even numbers less than 20, there will be two code examples:
Code Example 1
i=0
while i<9:
i=i+1
print(i*2)
Code Example 2
i=1
while i<10:
print(i*2)
i=i+1
Both seem to print out correct. However, in other programming language like C, PHP, Java, we often put the increment sentence i=i+1 or i++ at the end of the while loop.
So, in common case, which code of the above will be used most? Is there any offical instruction of Python that indicates which is better?
Thanks
Manually incrementing a variable by one in a while loop is relatively rarely done in Python. The idiomatic approach is to use a for loop instead, e.g. for i in range(1, 10):
That said, if you do use a while, incrementing the value at the end of the block is more common. This is because it's arguably easier to recognize what the end points of your loop are. Compare the following loops:
seq = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]
i = 0
while i < 10:
print(seq[i])
i += 1
j = -1
while j < 9:
j += 1
print(seq[j])
Both of these loops print seq[0] through seq[9]. But the j = -1 approach is a little more awkward, since at first glance you might think it involves negative indices.
More common use of while is with break
i = 1
while True:
print(i**2)
i += 1
if i > 10:
break
I would say for loop is better to use as you don't need to worry about the increment.
for i in range(10):
print (i*2)

How do I double my step variable for each for-loop iteration in Python 3?

I'm new to Python, and I'm playing around with recursive functions just for practice.
I made the following algorithm which takes a number as x and halves it until it is equal to 1. n is the number of times x has been halved.
def binary(x, n = 0):
print(n,":",x)
x = x // 2
n += 1
if x > 0:
binary(x, n)
return x
return x
I'm trying to make a loop that will call binary() with multiple values for x. I want my step to be doubled each iteration. I have it working with a while loop like the one below.
i = 1
while i < 1000000000:
print("when x is", i, ":")
binary(i)
i += i
For some reason though, I can't seem to achieve the same thing with a For loop. Here's what I have now.
for i in range(1,1000):
print("when x is", i, ":")
binary(i)
i += i
In the code above, i += i does not seem to effect the i in my header. I know that range() takes a third parameter called step, but I've tried this:
for i in range(1,1000, i += i):
# statements
This gives me a name error, and says "i is not defined".
Most of my experience with programming is in JavaScript and C#. In both of those languages I wouldn't of had any trouble doing this.
How would I get this to work in a For loop using Python 3?
The third parameter of range is indeed step. But you should use it with a computed value like:
for i in range(1,1000,2):
#statements
The reason why your i += i didn't worked is because, under the hood, the for-loop is executing something similar to i = next(...) at the end of an iteration, overiding your last increment.
Edit
You can achieve the desired effect using a generator, but it kinda kills the "trying to avoid while-loops" thing:
def myrange(start, stop):
i = start
while i < stop:
yield i
i += i
for i in myrange(1, 1000):
print(i)
Anyway, while-loops are perfectly valid constructs and I’d personnally go with one in this case. Do not forget that the for-loop has a rather different semantic in python than in both languages you’re used to. So trying to use a for-loop because you are able to do so with javascript seems like a bad idea if all what you need is a while-loop.
range can step by a fixed amount, but not a variable amount. Use a while-loop to increment i by i:
i += i
You could replace the while-loop with an iterator, such as:
import itertools as IT
for i in (2**i for i in IT.count()):
if i >= 1000000000: break
print("when x is", i, ":")
binary(i)
but I don't think this has any advantage over a simple while-loop.
If all you're doing is doubling i, then why not just raise it to the power?
for p in range(int(1000000000**0.5)):
print(binary(2**p)

Index Error in python

I am new to programming, and I am trying to write a Vigenère Encryption Cipher using python. The idea is very simple and so is my function, however in this line:
( if((BinKey[i] == 'b')or(BinKey[i+1] == 'b')): )
It seems that I have an index problem, and I can't figure out how to fix it.
The error message is:
IndexError: string index out of range
I tried to replace the i+1 index by another variable equal to i+1, since I thought that maybe python is re-incrementing the i, but it still won't work.
So my questions are:
How to fix the problem, and what have I done wrong?
Looking at my code, what can I learn to improve my programming skills?
I want to build a simple interface to my program (which will contain all the encryption ciphers), and all I came up with from Google is pyqt, but it just seems too much work for a very simple interface, so is there a simpler way to build an interface? (I am working with Eclipse Indigo and pydev with Python3.x)
The Vigenère Encryption function (which contains the line that causes the problem) is:
def Viegner_Encyption_Cipher(Key,String):
EncryptedMessage = ""
i = 0
j = 0
BinKey = Bin_It(Key)
BinString = Bin_It(String)
BinKeyLengh = len(BinKey)
BinStringLengh = len(BinString)
while ((BinKeyLengh > i) and (BinStringLengh > j)):
if((BinKey[i] == 'b')or(BinKey[i+1] == 'b')):
EncryptedMessage = EncryptedMessage + BinKey[i]
else:
EncryptedMessage = EncryptedMessage + Xor(BinKey[i],BinString[j])
i = i + 1
j = j + 1
if (i == BinKeyLengh):
i = i+j
return EncryptedMessage
This is the Bin_It function:
def Bin_It(String):
TheBin = ""
for Charactere in String:
TheBin = TheBin + bin(ord(Charactere))
return TheBin
And finally this is the Xor function:
def Xor(a,b):
xor = (int(a) and not int(b)) or (not int(a) and int(b))
if xor:
return chr(1)
else:
return chr(0)
In your while condition, you are ensuring that i < len(BinKey). This means that BinKey[i] will be valid, but BinKey[i+1] will not be valid at the last iteration of your loop, as you will then be accessing BinKey[len(BinKey)], which is one past the end of your string. Strings in python start at 0 and end at len-1 inclusive.
To avoid this, you can update your loop criterion to be
while BinKeyLength > i+1 and ...:
You can either change
while ((BinKeyLengh > i) and (BinStringLengh > j)):
to
while ((BinKeyLengh > i-1) and (BinStringLengh > j)):
or change
if((BinKey[i] == 'b')or(BinKey[i+1] == 'b')):
to
if((BinKey[i] == 'b') or (BinKeyLengh > i-1 and BinKey[i+1] == 'b')):
This will avoid trying to go into BinKey[BinKeyLength], which is out of scope.
Looking at my code, what can I learn to improve my programming skills?
Looping over an index is not idiomatic Python. It's better to loop over the elements of an iterator where possible. After all, that's usually what you're interested in: for i in... is often followed by my_list[i].
In this example, you should use the built-in function zip (or itertools.izip if your code is lazy, though this isn't necessary in Python 3), which gives you pairs of values from two or more iterators, and stops when the shortest one is exhausted.
for key_char, string_char in zip(BinKey, BinString): # takes values sequentially from
# BinKey and BinString
# and binds them to the names
# key_char and string_char
# do processing on key_char and string_char
If you really must do a while loop on an index, then put the test the other way around so it's clearer what you're doing. Compare
while len(BinKey) > i and len(BinString) > j: # this looks like len(BinKey) and
# len(BinString) are varying and you're
# comparing them to static variables i and j
with
while i < len(BinKey) and j < len(BinString): # this looks like you're varying i and j
# and comparing them to len(BinKey) and len(BinString)
Which better communicates the purpose of the loop?
Finally, the clause
if (i == BinKeyLengh):
i = i+j
doesn't seem to do anything. If i == BinKeyLength then the while loop will stop in a moment anyway.
I think your error, as Python interpreter says, is that you accessing invalid array positions.
In order to solve this, unlike it's being said, you should change your code to
while (BinKeyLength > i+2 and ...):
This is because in the last step, BinKeyLength = i+2, then i+1 is BinKeyLength-1, which is the last position of your array.
Concerning your programming skills I recommend you two things:
Be the code. Sounds mystic but the most important thing that is missing here is figuring out which indices numbers are used.
As it has been said by rubik, follow some style guides like PEP8 style guide.

How do I increment a counter inside a while test of Python

How would you translate the following Java idiom to Python?
Comparable[] a, int lo, int hi;
int i = lo, j = hi+1;
Comparable v = a[lo];
while (a[++i] < v) if (i == hi) break;
My problem is that in the while test I cannot have a ++i or i += 1.
The problem you can't do that way in Python is a restriction of Python syntax. Let's get what does while look like from documentation:
while_stmt ::= "while" expression ":" suite
["else" ":" suite]
As You can see you must put expression before ":", while x += 1 is a statement (and statements doesn't return any value so cannot be used as a condition).
Here's what does this code look like in Python:
i += 1
while a[i] < v:
if i == hi:
break
i += 1
Though it works, it's most likely not a Python way to solve your problem. When you have a collection and you want to use indices you have to look forward to redesigning your code using for loop and enumerate built-in function.
P.S.
Anyway, direct code porting between languages with absolutely different philosophies is not a good way to go.
The Java code sets i to the index of either the first element >= a[lo], or to hi, whichever appears first. So:
v = a[lo]
for i in range(lo+1, hi+1):
if a[i] >= v:
break
If you want to iterate over all the objects in a list or any "iterable" thing, use "for item in list". If you need a counter as well, use enumerate. If you want a range of numbers use range or xrange.
But sometimes you really do want a loop with a counter that just goes up which you're going to break out of with break or return, just like in the original poster's example.
For these occasions I define a simple generator just to make sure I can't forget to increment the counter.
def forever(start=0):
count = start
while True:
yield count
count += 1
Then you can just write things like:
for count in forever():
if do_something() == some_value:
break
return count
The list class has a built-in method that does this kind of searching, but for whatever reason it only compares for equality. Of course, we can hack that:
class hax:
def __init__(self, value): self.value = value
def __eq__(self, other): return other >= self.value
a.index(hax(a[lo]), lo + 1, hi + 1)
... but please don't :)
Anyway, not only should you not be trying to port the code directly, as #Rostyslav suggests - you shouldn't really be trying to port the problem directly. There's something very strange about a Python program that uses lists in a way that would allow a problem like this to come up.

Categories

Resources