When Does a While Loop Break - python

I have a while loop:
def setWorkDays(dayNameList):
workDays = []
while self.count > 0: #continue loop until all 5 work days have been filled or the loop breaks at the end
for day in dayNameList: #iterate over days, adding days to work days if they are not free days, AL days, preferred days, or free Saturdays
if day in self.freeDays or day in self.alDays or (day == 'Saturday' and self.satOff is True):
continue
elif day in self.programDays:
workDays.append(day)
self.count -= 1
elif self.preferredDay is not None and day in self.preferredDay:
continue
else:
workDays.append(day)
self.count -= 1
if self.preferredDay not in self.workDays: #if iteration completes, 5 work days have not been filled, and the preferred day has not been added, add the preferred day
workDays.append(self.preferredDay)
self.count -=1
return workDays
the idea behind the loop is that the second that self.count hits 0, the loop is terminated. This is the only function in which self.count is modified. Yet I'm getting strange results, where the loop appears to go on for at least 1 count too long, as the program is outputting -1 in some cases for self.count. Should this be happening? Shouldn't the while loop terminate the second self.count hits zero, or does it have to first finish the for loop? Should I be adding conditional logic after the self.count decrements that checks if self.count is zero and breaks if it is? That seems like the purpose of the while loop ...

A while loop doesn't automatically exit in mid-loop as soon as its condition is no longer true; it just checks the condition at the start of each loop. If you want to get out early, you need to break explicitly (or do something else non-local, like return from the function or raise to an except handler outside the loop).
It seems like what you're trying to do is get out of the for loop early, if self.count ever hits 0. There's really no way to do that directly. You have to check each time you decrement it.
However, you really don't need self.count at all. You decrement it exactly in the same places you append to workDays. So, just check whether you've got 5 of them yet. In other words, each self.count -= 1 becomes:
if len(workDays) >= 5: break
There is actually a way to do what (I think) you want in Python: use a generator instead of a list. If you yield each value instead of appending it to a list and then returning that list at the end, then you just stop iterating over the generator once you get 5 entries.
For example:
def setWorkDays(dayNameList):
while self.count > 0: #continue loop until all 5 work days have been filled or the loop breaks at the end
for day in dayNameList: #iterate over days, adding days to work days if they are not free days, AL days, preferred days, or free Saturdays
if day in self.freeDays or day in self.alDays or (day == 'Saturday' and self.satOff is True):
continue
elif day in self.programDays:
yield day
elif self.preferredDay is not None and day in self.preferredDay:
continue
else:
yield day
if self.preferredDay not in self.workDays: #if iteration completes, 5 work days have not been filled, and the preferred day has not been added, add the preferred day
yield day
workDays = [setWorkDays(dayNameList) for _ in range(5)]
Often, you don't even really need the list, all you need to do is iterate over it. For that, you could do:
for workDay in (setWorkDays(dayNameList) for _ in range(5)):
Or:
for workDay in itertools.islice(setWorkDays(dayNameList), 5):
A lot of things that generators can do feel like magic until you understand them—which often means you shouldn't do them until you learn about generators. So, if this makes no sense to you, don't just pick it up and use it. But if it prompts you to learn how to write and use generator functions, great!

Your decrement several times self.count in your while loop.
The loop will only break at the beginning of the block. So what can happen is that self.count is positive at the beginning of the block and then decrements to a negative value in a single block.

A while loop in any language (that I know) doesn't work the way you were expecting.
In general a while loop has the format:
while <condition>:
<code block>
The while loop checks the condition once per loop. It does not check the condition constantly. This means that the entirety of the code block would execute between each condition check, unless some control flow statement (such as continue, break, or return) executes during the code block.
In practice, this condition check once per loop has performance benefits (the computer doesn't have to constantly evaluate the condition) as well as limiting the cyclomatic complexity of a program.

This seems to do what you need:
def setWorkDays(dayNameList):
workDays = []
for day in dayNameList:
if day in self.freeDays or day in self.alDays or (
day == 'Saturday' and self.satOff):
continue
elif day in self.programDays:
workDays.append(day)
self.count -= 1
elif self.preferredDay is not None and day in self.preferredDay:
continue
else:
workDays.append(day)
self.count -= 1
if self.count <= 0:
break
else:
if self.preferredDay not in self.workDays:
workDays.append(self.preferredDay)
self.count -=1
return workDays

Related

Breaking out of a while loop

I am new to python and programming and having difficulty breaking out of a while loop that calls a few functions. I have tried a variety of different options but they all end the same, well they don't end it just keeps running. I am only on here because I have really researched and tried for a long time to fix this. Below is some of the code where you can see there might be confusion with a function. I am not posting the full program just the last part. Thank you for any assistance while I learn. This is also my first time posting, I've been using stackoverflow for the past year dabbling.
def main():
choice = input('''Hello,
Would you like to do basic math or return an average?
Please select 1 for basic math and 2 for average and 3 to quit:
''')
if choice == '1':
print(performCalculation())
elif choice == '2':
print(calculateAverage())
elif choice == '3':
print(main())
j = 0
k = 0
while j < 3:
print(main())
while k == 3:
break
print('All Done!')
Simply change
j = 0
k = 0
while j < 3:
print(main())
while k == 3:
break
print('All Done!')
to
j = 0
while j < 3:
print(main())
j += 1
print('All Done!')
The reason your while loop never breaks is because you have while j < 3:, but you never change the value of j, so if it was smaller to begin with, it will forever be smaller.
Also, k will never equal to 3, and even if it will, the break statement within that while loop will only make that while loop terminate, not the main one.
You have several basic mistakes here. I'll go in order of your program execution.
You aren't incrementing your loop variables.
You've started your loop variables with j and k (why not i and j?) set to zero, and are looped based on the value of these variables. Since they are never incremented, your loop never hits an exit condition.
You can increment the variables with j += 1, and k += 1 at the end of the respective loops.
Your loops are "unpythonic"
These would typically be written as my example below. You don't need to declare i separately, or increment it here. Python handles that for you.
for i in range(0, 3):
...
You're printing a function that doesn't return anything.
Your main function doesn't have a return value, so calling print(main()) is nonsense. You can replace this with simply main() unless you change main() to have some kind of return "foo" statement.

It should have been an infinite loop

I wrote a code in python. While changing the code, I asked myself what should be the output.
I also answered myself it should be an infinite loop. Then I ran it. But surprisingly it wasn't an infinite loop. My question is why ?
i=0
for i in range(10):
if i == 5:
i -=1
else:
print(i)
i+=1
It's very basic in python. For your information, the range() function generates a list. Here range(5) means [0,1,2,3,4].
So i iterates through the list [0,1,2,3,4] one by one. i doesn't hold the same value initialized from the beginning like a while loop condition.
for i in [0,1,2,3,4]:
if i==5:
i-=1
else:
print(i)
i+=1
Your code and this code perform similarly. The next value of i doesn't depend on the previous value of i but on the objects of the list.
Further study might be helpful for you.
range(10) produces the sequence 0,1,...,9 from which your i variable takes its values in the loop. The fact that you do i -= 1 when i == 5 won't make i to switch back and forth from 5 to 4 on and on because i is taking its values from range(10). What happens when i == 5 is that it becomes i == 4 when you do i -= 1 but at the next iteration i will take the next value from the range which would be 6, and so on until the loop ends.
Here's an infinite loop:
i=0
while i < 10:
if i == 5:
i -=1
else:
print(i)
i+=1

Can this code be made more Pythonic? Loops

So I am writing some code to roll three fudge dice (six sided dice with sides of -1,-1,0,0,+1,+1). If the three dice together roll a total of -3, I have a function that then rolls a single fudge dice over and over again, subtacting 1 from the total for each -1 that get's rolled and quiting if something other than a -1 is rolled - in this way I get an "explosion down" making totals less than -3 possible, though increasingly less likely.
My explode down function is this:
def explodedown():
curval = -3
while 1:
newroll = rolldie()
if newroll != -1:
break
else:
curval = curval-1
return curval;
That seems to work well enough, but I almost feel that if I wanted to write this even more simply, there should be some way to write the loop more like:
while newroll == -1
newroll = rolldie()
curval = curval-1
And then the loop would naturally break without needing an if statement. Problem is newroll does not exist until we get inside the loop, so I don't think that will work. Maybe if I added another statement before the loop starts like:
newroll = rolldie()
while newroll == -1
newroll = rolldie()
curval = curval-1
But it seems un-pythonic to have the newroll line there twice.
Thoughts? is there a way to simplify and make more readable my explode down function?
You could do it like this:
while rolldie() == -1:
curval -= 1

while loop and interpreting values as True or False

The following program is from a book of python. In this code, count is first set to 0 and then while True is used. In the book I read that zeros and empty strings are evaluated as False while all the other values are evaluated as True. If that is the case then how the program executes the while loop? wouldn't the count evaluated as False as the count is set to 0?
Could someone please explain this?
# Finicky Counter
# Demonstrates the break and continue statements
count = 0
while True: # while count is True
count += 1
# end loop if count greater than 10
if count > 10:
break
# skip 5
if count == 5:
continue
print(count)
input("\n\nPress the enter key to exit.")
If you evaluate
if count: # and count is zero
break
then sure - the loop will break immediately.
But you are evaluating this expression:
if count > 10: # 0 > 10
which is False, so you won't break on the first iteration.
in code portion while True, the condition will always evaluate to true. Now lets go inside the while loop.
when count > 10 is evaluated, for count = 0, it is false, so while count < 10, it will not break out of while loop.
If it was while count: Yes it would have come out of the loop in the first iteration itself as while count: is equivalent to - while count != 0
for condition count == 5 it continues on to next iteration, and does not print inside the loop.
If you changed while True: to while count:, your assumption would indeed be correct
You didn't say while count: You said while True: Since True is always True, your loop will run forever unless something inside tells it not to. That could be a line that says break, or it could be an exception raised. Your loop will break if count is greater than 10. count starts out at zero, but at the first iteration, count += 1 happens and count is now one. Since count is not greater than ten, it does not break. Since count is not equal to 5, it doesn't continue either. All it does is print 1. At the next iteration, count increments again and the same thing happens with the if statements. It is the same until eventually, the iteration where count is equal to 4 ends. Since True is still True, the iteration happens again. count is incremented and now equals five. The if count > 10: statement is False, but if count == 5: is True, so the loop just skips the print call and goes back to the beginning of the loop. The first thing it gets to is count += 1, so count is now equal to six. The next few iterations are quite similar to what happened before count was five. Once the iteration where count is nine happens, it gets to the beginning of the loop and increments count. Now count is ten and the loop breaks.
A while loop is a loop that will run a portion of code until the condition is False.while True is called infinite loop because True is not a condition and therefore can not change. So the loop will run until it found the break instruction.
Here you have two special elements related to loops :
continue : go directly to next iteration
break : go out of the loop

Else clause on Python while statement

I've noticed the following code is legal in Python. My question is why? Is there a specific reason?
n = 5
while n != 0:
print n
n -= 1
else:
print "what the..."
Many beginners accidentally stumble on this syntax when they try to put an if/else block inside of a while or for loop, and don't indent the else properly. The solution is to make sure the else block lines up with the if, assuming that it was your intent to pair them. This question explains why it didn't cause a syntax error, and what the resulting code means. See also I'm getting an IndentationError. How do I fix it?, for the cases where there is a syntax error reported.
The else clause is only executed when your while condition becomes false. If you break out of the loop, or if an exception is raised, it won't be executed.
One way to think about it is as an if/else construct with respect to the condition:
if condition:
handle_true()
else:
handle_false()
is analogous to the looping construct:
while condition:
handle_true()
else:
# condition is false now, handle and go on with the rest of the program
handle_false()
An example might be along the lines of:
while value < threshold:
if not process_acceptable_value(value):
# something went wrong, exit the loop; don't pass go, don't collect 200
break
value = update(value)
else:
# value >= threshold; pass go, collect 200
handle_threshold_reached()
The else clause is executed if you exit a block normally, by hitting the loop condition or falling off the bottom of a try block. It is not executed if you break or return out of a block, or raise an exception. It works for not only while and for loops, but also try blocks.
You typically find it in places where normally you would exit a loop early, and running off the end of the loop is an unexpected/unusual occasion. For example, if you're looping through a list looking for a value:
for value in values:
if value == 5:
print "Found it!"
break
else:
print "Nowhere to be found. :-("
Allow me to give an example on why to use this else-clause. But:
my point is now better explained in Leo’s answer
I use a for- instead of a while-loop, but else works similar (executes unless break was encountered)
there are better ways to do this (e.g. wrapping it into a function or raising an exception)
Breaking out of multiple levels of looping
Here is how it works: the outer loop has a break at the end, so it would only be executed once. However, if the inner loop completes (finds no divisor), then it reaches the else statement and the outer break is never reached. This way, a break in the inner loop will break out of both loops, rather than just one.
for k in [2, 3, 5, 7, 11, 13, 17, 25]:
for m in range(2, 10):
if k == m:
continue
print 'trying %s %% %s' % (k, m)
if k % m == 0:
print 'found a divisor: %d %% %d; breaking out of loop' % (k, m)
break
else:
continue
print 'breaking another level of loop'
break
else:
print 'no divisor could be found!'
The else-clause is executed when the while-condition evaluates to false.
From the documentation:
The while statement is used for repeated execution as long as an expression is true:
while_stmt ::= "while" expression ":" suite
["else" ":" suite]
This repeatedly tests the expression and, if it is true, executes the first suite; if the expression is false (which may be the first time it is tested) the suite of the else clause, if present, is executed and the loop terminates.
A break statement executed in the first suite terminates the loop without executing the else clause’s suite. A continue statement executed in the first suite skips the rest of the suite and goes back to testing the expression.
The else clause is only executed when the while-condition becomes false.
Here are some examples:
Example 1: Initially the condition is false, so else-clause is executed.
i = 99999999
while i < 5:
print(i)
i += 1
else:
print('this')
OUTPUT:
this
Example 2: The while-condition i < 5 never became false because i == 3 breaks the loop, so else-clause was not executed.
i = 0
while i < 5:
print(i)
if i == 3:
break
i += 1
else:
print('this')
OUTPUT:
0
1
2
3
Example 3: The while-condition i < 5 became false when i was 5, so else-clause was executed.
i = 0
while i < 5:
print(i)
i += 1
else:
print('this')
OUTPUT:
0
1
2
3
4
this
My answer will focus on WHEN we can use while/for-else.
At the first glance, it seems there is no different when using
while CONDITION:
EXPRESSIONS
print 'ELSE'
print 'The next statement'
and
while CONDITION:
EXPRESSIONS
else:
print 'ELSE'
print 'The next statement'
Because the print 'ELSE' statement seems always executed in both cases (both when the while loop finished or not run).
Then, it's only different when the statement print 'ELSE' will not be executed.
It's when there is a breakinside the code block under while
In [17]: i = 0
In [18]: while i < 5:
print i
if i == 2:
break
i = i +1
else:
print 'ELSE'
print 'The next statement'
....:
0
1
2
The next statement
If differ to:
In [19]: i = 0
In [20]: while i < 5:
print i
if i == 2:
break
i = i +1
print 'ELSE'
print 'The next statement'
....:
0
1
2
ELSE
The next statement
return is not in this category, because it does the same effect for two above cases.
exception raise also does not cause difference, because when it raises, where the next code will be executed is in exception handler (except block), the code in else clause or right after the while clause will not be executed.
I know this is old question but...
As Raymond Hettinger said, it should be called while/no_break instead of while/else.
I find it easy to understeand if you look at this snippet.
n = 5
while n > 0:
print n
n -= 1
if n == 2:
break
if n == 0:
print n
Now instead of checking condition after while loop we can swap it with else and get rid of that check.
n = 5
while n > 0:
print n
n -= 1
if n == 2:
break
else: # read it as "no_break"
print n
I always read it as while/no_break to understand the code and that syntax makes much more sense to me.
thing = 'hay'
while thing:
if thing == 'needle':
print('I found it!!') # wrap up for break
break
thing = haystack.next()
else:
print('I did not find it.') # wrap up for no-break
The possibly unfortunately named else-clause is your place to wrap up from loop-exhaustion without break.
You can get by without it if
you break with return or raise → the entire code after the call or try is your no-break place
you set a default before while (e.g. found = False)
but it might hide bugs the else-clause knows to avoid
If you use a multi-break with non-trivial wrap-up, you should use a simple assignment before break, an else-clause assignment for no-break, and an if-elif-else or match-case to avoid repeating non-trival break handling code.
Note: the same applies to for thing in haystack:
Else is executed if while loop did not break.
I kinda like to think of it with a 'runner' metaphor.
The "else" is like crossing the finish line, irrelevant of whether you started at the beginning or end of the track. "else" is only not executed if you break somewhere in between.
runner_at = 0 # or 10 makes no difference, if unlucky_sector is not 0-10
unlucky_sector = 6
while runner_at < 10:
print("Runner at: ", runner_at)
if runner_at == unlucky_sector:
print("Runner fell and broke his foot. Will not reach finish.")
break
runner_at += 1
else:
print("Runner has finished the race!") # Not executed if runner broke his foot.
Main use cases is using this breaking out of nested loops or if you want to run some statements only if loop didn't break somewhere (think of breaking being an unusual situation).
For example, the following is a mechanism on how to break out of an inner loop without using variables or try/catch:
for i in [1,2,3]:
for j in ['a', 'unlucky', 'c']:
print(i, j)
if j == 'unlucky':
break
else:
continue # Only executed if inner loop didn't break.
break # This is only reached if inner loop 'breaked' out since continue didn't run.
print("Finished")
# 1 a
# 1 b
# Finished
The else: statement is executed when and only when the while loop no longer meets its condition (in your example, when n != 0 is false).
So the output would be this:
5
4
3
2
1
what the...
Suppose you've to search an element x in a single linked list
def search(self, x):
position = 1
p =self.start
while p is not None:
if p.info == x:
print(x, " is at position ", position)
return True
position += 1
p = p.link
else:
print(x, "not found in list")
return False
So if while conditions fails else will execute, hope it helps!
The better use of 'while: else:' construction in Python should be if no loop is executed in 'while' then the 'else' statement is executed. The way it works today doesn't make sense because you can use the code below with the same results...
n = 5
while n != 0:
print n
n -= 1
print "what the..."
As far as I know the main reason for adding else to loops in any language is in cases when the iterator is not on in your control. Imagine the iterator is on a server and you just give it a signal to fetch the next 100 records of data. You want the loop to go on as long as the length of the data received is 100. If it is less, you need it to go one more times and then end it. There are many other situations where you have no control over the last iteration. Having the option to add an else in these cases makes everything much easier.

Categories