So I want to do something like this:
for i in range(5):
print(i);
if(condition==true):
i=i-1;
However, for whatever reason, even though I'm decrementing i, the loop doesn't seem to notice. Is there any way to repeat an iteration?
for loops in Python always go forward. If you want to be able to move backwards, you must use a different mechanism, such as while:
i = 0
while i < 5:
print(i)
if condition:
i=i-1
i += 1
Or even better:
i = 0
while i < 5:
print(i)
if condition:
do_something()
# don't increment here, so we stay on the same value for i
else:
# only increment in the case where we're not "moving backwards"
i += 1
Python loop using range are by-design to be different from C/C++/Java for-loops. For every iteration, the i is set the the next value of range(5), no matter what you do to i in between.
You could use a while-loop instead:
i = 0
while i<5:
print i
if condition:
continue
i+=1
But honestly: I'd step back and think again about your original problem. Probably you'll find a better solution as such loops are always error-prone. There's a reason why Python for-loops where designed to be different.
You have a misunderstanding about loops in Python. The for loop doesn't care what you do with i at each iteration, because it is not related to the logic of the loop at all. Modifying i just rebinds a local variable.
You would need to use a while loop to achieve the behaviour you're expecting, where the state of i does affect the control flow of the loop:
import random
i = 0
while i < 5:
print(i)
i += 1
if random.choice([True, False]):
i -= 1
range(5) creates a list with numbers 0 thru 4 in it - [0, 1, 2, 3, 4].
When you run a for loop over it, you are iterating over the list. Doing i-= 1 will only decrement the value of that particular element of the list, and the iteration will continue.
Like the other answers here have suggested, you should use a while loop.
i= 0
while i<5:
# do stuff
if #condition:
i-= 1 # or +
i+= 1
Repeating many other answers, and just for completeness, you will need to use a while loop.
i = 0
while i < 5:
print(i)
if (not condition):
i+=1
If you want to move back an iteration in the loop (instead of repeating an iteration), then use this:
i = 0
while i < 5:
print(i)
if (condition):
i -= 1
else:
i += 1
Essentially, while i < 5 evaluates upon each iteration, and checks if i < 5. Thus by decrementing/not changing i, we get something like this: (values of i)
Not changing: 1->2->3-(condition satisfied)> 3 -> 4 -> 5
Decrementing: 1->2->3-(condition satisfied)>2 -> 3 -> 4 -> 5
The reason why i=i-1 in your for loop doesn't make it repeat the iteration is simple. In the for loop, i is assigned the value of the next item in the for loop. Python could care less about what you do with i, as long as it is able to assign the next item to it. Thus, the for loop for i in <your_iterable>:<do whatever> is closer to this:
_i = 0
_length = len(<your_iterable>)
while _i < _length:
i = _i
_i += 1
<do whatever>
However, in this analogy, you wouldn't be able to access the _ predicated variables (_i,_length). This is how I simplify the logic of the for loop. Note that regardless of what i is assigned to, it will be assigned to _i upon the next iteration, and the loop really doesn't care about what i is.
Utilize a while loop:
i = 0
while i < 5:
print(i)
if condition:
i -= 1
i += 1
As has been mentioned, this is rather unidiomatic Python. Perhaps if you post what you are trying to achieve we can give some better advice.
In Python it's possible to set up a two-way exchange between an iterator (what comes after in in a for..in loop) and its consumer (code inside the loop). To achieve this, you can use send in the consumer code to "inject" a value in a generator. In your case, you can simply send back the current value once the condition is met and wrap the range call in a generator that repeats whatever is sent back to it. Here's some code for you to play, intentionally verbose for clarity:
def repeateble(it):
buf, it = None, iter(it)
while True:
if buf is None:
# the buffer is empty, send them the next elem
val = next(it)
else:
# there's something in the buffer
# let's send that back
val = buf
# send the value and wait what they say
back = yield val
if back:
# they've sent us something!
# give them some dummy value as a result of send()
yield None
# and save what they've sent in a buffer
# for the next iteration
buf = back
else:
# they haven't sent anything
# empty the buffer
buf = None
from random import randint
# create a repeateble generator
rng = repeateble(range(100))
for x in rng:
print(x)
# check "some condition"...
if randint(1, 100) > 80:
print('repeat:')
# send the current value back to the generator
# it will be returned on the next iteration
rng.send(x)
You can use readlines if you're iterating through a file and pull out the previous lines based on a condition.
with open("myfile.txt", "r") as f:
text = f.readlines()
for row in range(0, len(text)):
if re.search("Error", text[row]):
print(text[row-1].strip())
Related
I recently ran into an issue where I was using a for loop somewhat similar to this:
for i in range(lineCount(fileToBeProcessed)):
print(i)
j = doSomeStuff() #returns number of lines in the file to skip
i = i+j
print(i)
print('next_loop')
For a value of j={2,3,1} the output was:
1
3
next_loop
2
5
next_loop
.
.
My desired output:
1
3
next_loop
4
7
next_loop
.
.
Every time the next iteration started, the for loop counter i reset to the original cycle. My question is, is there a way to force the for loop to skip the iterations based on the return value j. I understand and was able to implement something similar with a while loop. However, I was curious as to how or why would Python not allow such manipulation?
It allows manipulations. But a for loop in Python works with a:
for <var> in <iterable>:
# ...
So Python does not attaches a special meaning to range(n) as a for loop: a range(n) is an iterable that iterates from 0 to n (exclusive). At the end of each iteration the next element of the iterable. It furthermore means that once you constructed a range(n), if you alter n, it has no impact on the for loop. This in contrast with for instance Java, where n is evaluated each iteration again.
Therefore you can manipulate the variable, but after the end of the loop, it will be assigned the next value of the loop.
In order to manipulate the variable, you can use a while loop:
i = 0 # initialization
while i < lineCount(fileToBeProcessed): # while loop
print(i)
j = doSomeStuff() #returns number of lines in the file to skip
i = i+j
print(i)
print('next_loop')
i += 1 # increment of the for loop is explicit here
Usually a while loop is considered to be "less safe" since you have to do the increment yourself (for all code paths in the loop). Since it is something one tends to forget, it is easier to write an endless loop.
Assuming that fileToBeProcessed is actually a file-like object, you can iterate directly over the file (i.e. over the lines in that file), or use enumerate(fileToBeProcessed) if you need the line numbers, and call next on that iterator to skip lines.
Like this (not tested):
iterator = enumerate(fileToBeProcessed) # or just iter = fileToBeProcessed
for i, line in iterator:
print(i)
j = doSomeStuff() #returns number of lines in the file to skip
for _ in range(j):
i, line = next(iterator) # advance iterator -> skip lines
print(i)
print('next_loop')
I've edited the code hope it may help
z =0
for i in range(lineCount(fileToBeProcessed)):
if i <= z: #if i is a value that you don't want to be output, then skip loop to next one
continue
print(i)
j = doSomeStuff()
cnt += 1
z = i+j #use a different variable from i since i the iterator value will not be updated
print(z)
print('next_loop')
with open("...txt") as fp:
for i, line in enumerate(fp):
if some condition :
i=0
fp.seek(0)
Text is huge, GBs of data so I use enumerate. I need to process this huge file several thousands of time so I decided to open it just at first time for efficiency. However although this code works, i does not become 0 and it just goes on incrementing. I need that to be zero because I need position of lines i. And it is just inefficient to multiply billions*several thousands everytime and make some modular arithmetic.
So my question is how can I set i to be zero when I go back to the beginning of file? Thanks in advance (I use python 3.6)
You could always make your own resettable enumerator, but there are probably better ways to do what you really want to do.
Still, here's what a resettable enumerator looks like:
def reset_enumerate(thing, start=0):
x = start
for t in thing:
val = yield t, x
if val is not None:
x = val
else:
x += 1
Then you would use it like this:
r = reset_enumerate(range(10))
for i, num in r:
print('i:', i, 'num:', num)
if i == 5:
i, num = r.send(0)
print('i:', i, 'num:', num)
As stated in the comment, enumerate is a generator function. It's "exhausted" by the time it completes. This is also why you can't just "reset" it. Here is the PEP on enumerate to further explain how it works.
Furthermore, as also indicated in the comments, this post provides the typical way to handle large files.
Here is an example of how you can emulate a scenario like yours:
Assuming i have a file called input.txt with this kind of data:
1
2
3
Code:
j = 0
with open('input.txt', 'r') as f:
for k in f:
# A break condition
# If not we'll face an infinite loop
if j > 4:
break
if k.strip() == '2':
f.seek(0)
print("Return to position 0")
# Don't forget to increment j
# Otherwise, we'll end up with an infinite loop
j += 1
print(k.strip())
Will output:
1
Return to position 0
2
1
Return to position 0
2
1
Return to position 0
2
1
Return to position 0
2
1
Return to position 0
2
rmNegative(L) removes the negative numbers from list L, assumed to contain only numeric elements. (Modifies L; does not create a new list.)
How would I go about this when using a while loop? I've tried coding it over and over and all I got was a never ending loop..
def rmNegatives(L):
pos=len(L)-1
while pos>-1:
pos=pos
if pos<len(L)-1:
pos=pos
if L[pos]>0:
L[:]=L[:]
pos=len(L)-2
elif L[pos]<0:
L[:]=[L[pos]]+L[0:]
L[:]=L[1:]
pos=len(L)-1
elif pos==len(L)-1:
pos=pos
if L[pos]<0:
L[0:]=L[0:pos]
pos=len(L)-1
elif L[pos]>0:
L[:]=L[:]
pos=len(L)-2
rmNegatives([-25,31,-10,23,45,-2])
Run the code here
edit** i thank you for your responses. the reason why my code does not contain any form of remove or index is because i was restricted from using them(would have been nice if they weren't but..)
Here is an implementation with a while loop. I start at the end because the list is getting shorter as the loop iterates and the earlier indexes are shifting as a result.
def rmNegative(L):
index = len(L) - 1
while index >= 0:
if L[index] < 0:
del L[index]
index = index - 1
If you absolutely cannot afford to create a new list and you have to use a while loop:
l = [1,2,3,-1,-2,-3,4,5]
x = 0
while x < len(l):
if l[x] < 0:
l.remove(l[x])
continue
x += 1
Alternatively, as suggested by abarnert (lower runtime):
l = [1,2,3,-1,-2,-3,4,5]
x = 0
while x < len(l):
if l[x] < 0:
del l[x]
continue
x += 1
If you absolutely cannot afford to create a new list, but can use a for loop:
l = [1,2,3,-1,-2,-3,4,5]
for x in xrange(len(l)):
if x < len(l) and l[x] < 0:
l.remove(l[x])
If you can afford to create a new list:
l = [1,2,3,-1,-2,-3,4,5]
l = [num for num in l if num >= 0]
Code:
def rmNegatives(L):
i = 0
while i < len(L):
if L[i] < 0:
L.pop(i)
else:
i += 1
return L
The first think you need to know is that del L[i] removes the ith element. (Don't use L.remove(L[i]); that looks up the ith element, then searches the whole list until it finds an equal value, then deletes that.)
But notice that if you delete L[3], and then move on to L[4], you've skipped one value—the original L[4] is now L[3], and you still need to check it. So you have to make sure not to increment i until you find a value that you're keeping.
But if you just loop over all of the indices, and you've deleted some along the way, you're going to run off the end. So, you need to decrement the the length every time you delete. Or you could also just keep calling len(L) again and checking against the new length each time.
One clever way to solve both of those problems at once is to count backward, as in Brad Budlong's answer. But that can make it easy to make fencepost errors. So, I'll do it the other way.
def rmNegative(L):
length = len(L)
i = 0
while i < length:
if L[i] < 0:
del L[i]
length -= 1
else:
i += 1
And that's it.
If you want to know why your existing code is an infinite loop, let's step through a bit of what it does.
We start with pos=len(L)-1, so we go into the big elif. pos=pos does nothing. If it's a negative number, you remove it and go to the new len(L)-1; if it's a positive number, you leave it, and go to len(L)-2. If it's 0, we do neither, which means pos=len(L)-1 still, and we're just going to repeatedly look at that 0 forever.
So, that's one way to get an infinite loop. But let's assume it didn't end in 0.
If we've just removed a negative number, we go back to the elif, and we know that's OK unless 0.
But if we've left a positive number, then we now have pos=len(L)-2, so we go to the if. Again, pos=pos does nothing. If it's a positive number, we copy the list to itself, which does nothing, then set pos=len(L)-2. Which is the same thing it already is. So, if the next-to-last number is positive, we're just going to keep looking at that number forever. That's another way to get an infinite loop.
What if it was negative? Then L[:]=[L[pos]]+L[0:] prepends the value you wanted to delete to the whole list (which still includes the original copy of the value), L[:]=L[1:] removes the value you just prepended, so you end up with the same values in L you started with. Then you set pos=len(L)-1, which goes back to the end of the list. We know that's going to work successfully, and get back to the next-to-last slot again, which will still be the same value, so we'll shuttle back and forth forever. So, that's enough way to get an infinite loop.
What if it was 0? Then we do nothing, so pos and L never change, so that's another way to get an infinite loop.
So, once we get a positive element on the end of the list, all three possibilities for the next-to-last element are infinite loops.
Stepping back a bit, the only things your code ever sets pos to are len(L)-1 and len(L)-2. So, even if you did all of the others stuff right, how could this possibly ever complete on a list with more than 2 non-negative numbers?
Here is my implementation of rmNegative(L).
def rmNegative(L):
m = min(L)
while m < 0:
L.remove(m)
m = min(L)
This modifies the list and does not create a new list.
Well, not allocating new list now,
>>> list_l = [1, -1, 2, -8]
>>> length = len(list_l)
>>> while i < length:
... if list_l[i] < 0:
... del list_l[i]
... length -= 1
... else:
... i = i + 1
...
>>> list_l
[1, 2]
I want to know if is it possible to change the value of the iterator in its for-loop?
For example I want to write a program to calculate prime factor of a number in the below way :
def primeFactors(number):
for i in range(2,number+1):
if (number%i==0)
print(i,end=',')
number=number/i
i=i-1 #to check that factor again!
My question : Is it possible to change the last two line in a way that when I change i and number in the if block, their value change in the for loop!
Update: Defining the iterator as a global variable, could help me? Why?
Short answer (like Daniel Roseman's): No
Long answer: No, but this does what you want:
def redo_range(start, end):
while start < end:
start += 1
redo = (yield start)
if redo:
start -= 2
redone_5 = False
r = redo_range(2, 10)
for i in r:
print(i)
if i == 5 and not redone_5:
r.send(True)
redone_5 = True
Output:
3
4
5
5
6
7
8
9
10
As you can see, 5 gets repeated. It used a generator function which allows the last value of the index variable to be repeated. There are simpler methods (while loops, list of values to check, etc.) but this one matches you code the closest.
No.
Python's for loop is like other languages' foreach loops. Your i variable is not a counter, it is the value of each element in a list, in this case the list of numbers between 2 and number+1. Even if you changed the value, that would not change what was the next element in that list.
The standard way of dealing with this is to completely exhaust the divisions by i in the body of the for loop itself:
def primeFactors(number):
for i in range(2,number+1):
while number % i == 0:
print(i, end=',')
number /= i
It's slightly more efficient to do the division and remainder in one step:
def primeFactors(number):
for i in range(2, number+1):
while True:
q, r = divmod(number, i)
if r != 0:
break
print(i, end=',')
number = q
The only way to change the next value yielded is to somehow tell the iterable what the next value to yield should be. With a lot of standard iterables, this isn't possible. however, you can do it with a specially coded generator:
def crazy_iter(iterable):
iterable = iter(iterable)
for item in iterable:
sent = yield item
if sent is not None:
yield None # Return value of `iterable.send(...)`
yield sent
num = 10
iterable = crazy_iter(range(2, 11))
for i in iterable:
if not num%i:
print i
num /= i
if i > 2:
iterable.send(i-1)
I would definitely not argue that this is easier to read than the equivalent while loop, but it does demonstrate sending stuff to a generator which may gain your team points at your next local programming trivia night.
It is not possible the way you are doing it. The for loop variable can be changed inside each loop iteration, like this:
for a in range (1, 6):
print a
a = a + 1
print a
print
The resulting output is:
1
2
2
3
3
4
4
5
5
6
It does get modified inside each for loop iteration.
The reason for the behavior displayed by Python's for loop is that, at the beginning of each iteration, the for loop variable is assinged the next unused value from the specified iterator. Therefore, whatever changes you make to the for loop variable get effectively destroyed at the beginning of each iteration.
To achieve what I think you may be needing, you should probably use a while loop, providing your own counter variable, your own increment code and any special case modifications for it you may need inside your loop. Example:
a = 1
while a <= 5:
print a
if a == 3:
a = a + 1
a = a + 1
print a
print
The resulting output is:
1
2
2
3
3
5
5
6
Yes, we can only if we dont change the reference of the object that we are using. If we can edit the number by accessing the reference of number variable, then what you asked is possible.
A simple example:
a=[1,2,3]
a=a+[4]==>here, a new object is created which plots to different address.
a+=[4]==>here , the same object is getting updated which give us the desired result.
number=10
list1=list(range(2,number+1))
# list1
for i in list1:
print(list1,i)
if (number%i==0):
print(i,end=',')
number=number//i #we can simply replace it with number//=i to edit the number without changing the reference or without creating a new object.
try:
[list1.pop() for i in range(10,0,-1) if(i>number)]
#here pop() method is working on the same object which list created by number refers. so, we can able to change the iterable in the forloop.
except:
continue
i=i-1 #to check that factor again!
I'm trying to take an input of a list of numbers and return the list of indices in the original list that contain negative values. I also want to use a while loop. Here is my code so far.
def scrollList2(myList):
negativeIndices = []
i = 0
while i < len(myList):
if myList[i] < 0:
i = i + 1
negativeIndices.append(i)
return negativeIndices
How to I stop the loop and how do i get the indices to return? Right now when I run it, it runs forever (infinite loop) how do I tell it to stop once it hits the last indices of myList?
When you hit your first non-negative number, the if is never entered again and i never gets incremented again. Put the part where you increment i outside the if block.
while i < len(myList):
if myList[i] < 0:
i = i + 1
negativeIndices.append(i)
Assume, the conditional myList[i] < 0 is not true. In that case, i won’t be incremented and nothing else happens either. So you will end up in the next iteration, with the same value of i and the same conditional. Forever, in an endless loop.
You will want to increment i regardless of whether you matched something or not. So you will have to put the increment outside of the if conditional. Furthermore, you want to increment i after appending the index to the list, so you actually append the index you tested, and not the one afterwards:
while i < len(myList):
if myList[i] < 0:
negativeIndices.append(i)
i = i + 1
Also, you would usually use a for loop here. It will automatically take care of giving you all the values of i which you need to index every element in myList. It works like this:
negativeIndices = []
for i in range(len(myList)):
if myList[i] < 0:
negativeIndices.append(i)
range(len(myList)) will give you a sequence of values for every number from zero to the length of the list (not including the length itself). So if your list holds 4 values, you will get the values 0, 1, 2 and 3 for i. So you won’t need to take care of incrementing it on your own.
Another possibility would be enumerate as Foo Bar User mentioned. That function will take a sequence, or list in your case, and will give you both an index and the value at the same time:
negativeIndices = []
for i, value in enumerate(myList):
if value < 0:
negativeIndices.append(i)
As you can see, this completely removes the need to even access the list by its index.
OP wants to use a while loop, so this answer is not exactly on point - but I feel I should point out that many pythonistas will expect something like:
neg_indices = [k for k,v in enumerate(myList) if v < 0]
This is implicit in the other answers, however it might be useful for lurkers and for OP to consider down the road... While certainly does the job as the other answers show, but its 'free' in a list comprehension; plus, there's no chance of an infinite loop here....