How to change index of a for loop? - python

Suppose I have a for loop:
for i in range(1,10):
if i is 5:
i = 7
I want to change i if it meets certain condition. I tried this but didn't work.
How do I go about it?

For your particular example, this will work:
for i in range(1, 10):
if i in (5, 6):
continue
However, you would probably be better off with a while loop:
i = 1
while i < 10:
if i == 5:
i = 7
# other code
i += 1
A for loop assigns a variable (in this case i) to the next element in the list/iterable at the start of each iteration. This means that no matter what you do inside the loop, i will become the next element. The while loop has no such restriction.

A little more background on why the loop in the question does not work as expected.
A loop
for i in iterable:
# some code with i
is basically a shorthand for
iterator = iter(iterable)
while True:
try:
i = next(iterator)
except StopIteration:
break
# some code with i
So the for loop extracts values from an iterator constructed from the iterable one by one and automatically recognizes when that iterator is exhausted and stops.
As you can see, in each iteration of the while loop i is reassigned, therefore the value of i will be overridden regardless of any other reassignments you issue in the # some code with i part.
For this reason, for loops in Python are not suited for permanent changes to the loop variable and you should resort to a while loop instead, as has already been demonstrated in Volatility's answer.

This concept is not unusual in the C world, but should be avoided if possible.
Nonetheless, this is how I implemented it, in a way that I felt was clear what was happening. Then you can put your logic for skipping forward in the index anywhere inside the loop, and a reader will know to pay attention to the skip variable, whereas embedding an i=7 somewhere deep can easily be missed:
skip = 0
for i in range(1,10):
if skip:
skip -= 1
continue
if i=5:
skip = 2
<other stuff>

Simple idea is that i takes a value after every iteration irregardless of what it is assigned to inside the loop because the loop increments the iterating variable at the end of the iteration and since the value of i is declared inside the loop, it is simply overwritten. You'd probably wanna assign i to another variable and alter it. For e.g,
for i in range(1,10):
if i == 5:
u = 7
and then you can proceed to break the loop using 'break' inside the loop to prevent further iteration since it met the required condition.

Just as timgeb explained, the index you used was assigned a new value at the beginning of the for loop each time, the way that I found to work is to use another index.
For example, this is your original code:
for i in range(1,10):
if i is 5:
i = 7
you can use this one instead:
i = 1
j = i
for i in range(1,10):
i = j
j += 1
if i == 5:
j = 7
also, if you are modifying elements in a list in the for loop, you might also need to update the range to range(len(list)) at the end of each loop if you added or removed elements inside it. The way I do it is like, assigning another index to keep track of it.
list1 = [5,10,15,20,25,30,35,40,45,50]
i = 0
j = i
k = range(len(list1))
for i in k:
i = j
j += 1
if i == 5:
j = 7
if list1[i] == 20:
list1.append(int(100))
# suppose you remove or add some elements in the list at here,
# so now the length of the list has changed
k = range(len(list1))
# we use the range function to update the length of the list again at here
# and save it in the variable k
But well, it would still be more convenient to just use the while loop instead.
Anyway, I hope this helps.

Related

Loop counter behaving unexpectedly

I am having trouble understanding why the counter variable even though incremented within
the nested loop, is not having any effect on the number of times the parent loop runs!
The code below gives the output 0 1 2 when the increment inside the nested loop is supposed to stop it from executing the second and the third time.
for i in range(3):
print(i)
for j in range(2):
i = i + 2
Is the i inside the nested for pointing to a different i!
A for loop is a fancy kind of assignment statement; the loop index is assigned to at the start of each iteration.
The code
for i in range(3):
print(i)
for j in range(2):
i = i + 2
is effectively equivalent to
itr1 = iter(range(3))
while True:
try:
i = next(itr1)
except StopIteration:
break
itr2 = iter(range(2))
while True:
try:
j = next(itr2)
except StopIteration:
break
i = i + 2
The assignment i = next(itr1) ignores anything else done to i in the previous iteration: the value depends only on the state of itr1, not the current value of i.
if you display i after the loop incrementing it, you'll see the change
for i in range(3):
print(i)
for _ in range(2): # j is not used
i += 2
print(i) # I'm new!
each time the first line runs, it'll re-tag i with the next value from the range() sequence, discarding what it was before
You don't store the result of the increment anywhere. It goes like this: i is assigned a value from range(), you print it, then j gets the assigned value, i += 1, and then you loop starts again, and i is assigned the next value from the range().
This should work though
for i in range(3):
for j in range(2):
i = i + 2
print(i)
To achieve the effect you are looking for you would have to increment the iterator. You might think of it as incrementing the pointer, rather than the value at the pointer:
iterator = iter(range(123))
for i in iterator:
print(i)
next(iterator, None) #None here is a default,
#and stops you getting an error if there is no 'next'.
Imagine a for loop in python is doing something like the following, which makes it very obvious why a value assigned to i within the body of the loop is lost. You are incrementing i when you really want to increment n
iterator = list(range(10))
n = 0
while n < len(iterator):
n+=1
i = iterator[n]
print(i)
i = i+1

python decrement at special case in for-loop

I need to decrement in a python for-loop at a special case (or just don't increment).
In C-like languages, this can be easily accomplished by decrementing the index, or if you have an iterator-like structure you could just "decrement" the iterator. But I have no clue how to achieve this in python.
One solution would be to create a while loop and increment manually, but that would, in my case, bring in lots of extra cases, where just one case is needed when I could decrement.
C Example
for (int i = 0; i < N; ++i) {
if (some_condition) {
i--;
}
}
Python equivalent
for i in range(0, N):
if some_condition:
i -= 1 # need something like this
i = i.prev() # or like this
You can make an iteration loop by yourself. You could easily add a loop index independent from next calls, so that you could even use a skip condition that uses the current index.
skip_iteration = True
it = iter(range(10))
iterating = True
value = next(it)
while iterating:
try:
print(value, end=' ')
if skip_iteration:
# iteration skip code
skip_iteration = False
else:
# normal iteration code
value = next(it)
except StopIteration:
iterating = False
Here only the first iteration is skipped, which outputs :
0 0 1 2 3 4 5 6 7 8 9
This code relies on the fact that the next function raises StopIteration if it reaches the end of the iterator.
If the condition in the if statement is somehow related to the iterator i itself then the loop might not end but if the condition is not depended on i then there shall not be any problem
you can also try skipping that particular iteration by using continue.

How can I control the iterator value inside a for loop?

I would like to reproduce this JavaScript bucle in Python, but I'm not sure how to proceed.
for (i=0;i<toks.length && toks[i]!='\r'; i+=10)
you can write it using while
i = 0
while(i < len(toks) and toks[i] != '\r'):
i += 10
Personally, I don't like looping with indexes, so I suggest python for loop with break state (which stops the loop). Regarding every 10th loop logic, you can have this using toks[::10].
So the final code would be:
for v in toks[::10]:
if v == '\r':
break
print(v)
You can configure increment with 3rd argument of range and then check exit condition within the loop
toks = list(range(100))
toks[30] = '\r'
for i in range(0, len(toks), 10):
if toks[i] == '\r':
break
print(toks[i])
If you want to add 10 to the iterator variable for each loop, you can simply do so by passing it as a parameter to the range function:
for i in range(0, MAX_ITERATIONS, 10):
pass
The range function takes 3 parameters: the value to initialize the iterator variable, the maximum iterations and the value to add to the iterator variable for each loop (default is 1).
A code like that in python would be something like:
for i in range(0, len(toks), 10):
if toks[i] == '\r':
break

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

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.

python: restarting a loop

i have:
for i in range(2,n):
if(something):
do something
else:
do something else
i = 2 **restart the loop
But that doesn't seem to work. Is there a way to restart that loop?
Thanks
You may want to consider using a different type of loop where that logic is applicable, because it is the most obvious answer.
perhaps a:
i=2
while i < n:
if something:
do something
i += 1
else:
do something else
i = 2 #restart the loop
Changing the index variable i from within the loop is unlikely to do what you expect. You may need to use a while loop instead, and control the incrementing of the loop variable yourself. Each time around the for loop, i is reassigned with the next value from range(). So something like:
i = 2
while i < n:
if(something):
do something
else:
do something else
i = 2 # restart the loop
continue
i += 1
In my example, the continue statement jumps back up to the top of the loop, skipping the i += 1 statement for that iteration. Otherwise, i is incremented as you would expect (same as the for loop).
Here is an example using a generator's send() method:
def restartable(seq):
while True:
for item in seq:
restart = yield item
if restart:
break
else:
raise StopIteration
Example Usage:
x = [1, 2, 3, 4, 5]
total = 0
r = restartable(x)
for item in r:
if item == 5 and total < 100:
total += r.send(True)
else:
total += item
Just wanted to post an alternative which might be more genearally usable. Most of the existing solutions use a loop index to avoid this. But you don't have to use an index - the key here is that unlike a for loop, where the loop variable is hidden, the loop variable is exposed.
You can do very similar things with iterators/generators:
x = [1,2,3,4,5,6]
xi = iter(x)
ival = xi.next()
while not exit_condition(ival):
# Do some ival stuff
if ival == 4:
xi = iter(x)
ival = xi.next()
It's not as clean, but still retains the ability to write to the loop iterator itself.
Usually, when you think you want to do this, your algorithm is wrong, and you should rewrite it more cleanly. Probably what you really want to do is use a generator/coroutine instead. But it is at least possible.
a = ['1', '2', '3']
ls = []
count = False
while ls != a :
print(a[count])
if a[count] != a[-1] :
count = count + 1
else :
count = False
Restart while loop.

Categories

Resources