COMP1701-004
fall 2023
lec-14
Lo, Your Month




Any A3 Questions?
I will only mark the 2 files that were included in the starter code.
RECALL

Let's do a brain dump: write down everything you can remember - words, phrases, pictures, whatever - about things we covered in lecture and lab last week.
You've got 1 minute.
let's talk about these things today:
⦾ counted loops
⦾ sentinel loops
⦾ loopsies
categorizing loops
The Python syntax allows for 2 types of loops.
- while loops
- for loops (covered later)
Some textbooks, sites, and instructors suggest categorizing while loops...but agreement on the definition of these further categorizations isn't always great.
This can be pretty confusing - especially when you're first learning this stuff!
Nevertheless, since our department uses certain terms in certain ways, it's best if I bring them up, since you'll be encountering them over your stay here.
counted loops
counted loops
A counted/counting/counter-controlled loop is a loop that runs a known number of times.
This kind of loop may also be called a definite loop.
counted loops
2 common flavours
Flavour 1
Do something a specific number of times.
counter = 0
DESIRED_COUNT = 4
while counter != DESIRED_COUNT:
# do something
print('something')
counter += 1
Flavour 2
Do something until some upper/lower bound is reached (or exceeded).
counter = int(input("times to loop? "))
while counter != 0:
# do something
print('something')
counter -= 1
counting UP version
counting DOWN version
curr_val = int(input("Yer number? "))
UPPER_BOUND = 14
while curr_val < UPPER_BOUND:
# do something
print('something')
curr_val += 2
counting UP by 2's version
curr_val = int(input("Yer number? "))
LOWER_BOUND = 2
while curr_val > LOWER_BOUND:
# do something
print('something')
curr_val -= 3
counting DOWN by 3's
Flavour 1 is just a specific case of flavour 2!
We could also use <0 here.
In fact, it might even be a better idea?....
🙋🏻♂️❓🙋🏻♀️While we're here...can you identify the 6 required parts of any while loop here?
counted loops
def sing_that_song():
number_of_bottles = 99
while number_of_bottles > 2:
print(f"{number_of_bottles} bottles of age-appropriate beverages on the wall,")
print(f"{number_of_bottles} bottles of age-appropriate beverages,")
print("take one down, pass it around,")
number_of_bottles = number_of_bottles - 1
print(f"{number_of_bottles} bottles of age-appropriate beverages on the wall,")
print()
# other stuffs here
sing_that_song()
Questions
- What's our LCV?
- What is the purpose of that 2 in the condition?
- ***How many times will this loop loop? (consider temporarily using a smaller number than 99 to help you here)
- What happens if we use number_of_bottles >= 3?
- How can we rewrite the line that modifies the LCV - and what's that op called?
Remember those bevs? Counted loop for sure!
HONOURS_CUTOFF = 3.6
PASS_CUTOFF = 2.0
def display_grade_stats(num_students: int) -> None:
student_count = 0
num_honours = 0
num_passed = 0
num_failed = 0
while student_count < num_students:
gpa = float(input(f"Student {student_count + 1} GPA: "))
if gpa >= HONOURS_CUTOFF:
num_honours += 1
if gpa >= PASS_CUTOFF:
num_passed += 1
else:
num_failed += 1
student_count += 1
print(num_honours, num_passed, num_failed)
counted loops
The bounds on your counted loop do NOT have to be hard-coded in the code itself!
Questions
- What's the LCV here? Then what's the other variable in the condition?
- How many times does this loop loop?
To be called a counted loop, you only need to be able to figure out how many times the loop will loop when you RUN the code, not when you DESIGN it.
counted loops
from random import randint
def dice_sum(num_dice: int) -> int:
sum = 0
dice_rolled = 0
while dice_rolled < num_dice:
sum += randint(1, 6)
return sum
Another one....
Question
- What's the LCV here?
- How many times does this loop loop?
- What's a quick way of figuring out the number of times a counted loop loops?
- If we change the second argument to randint to a 1, what is returned if we call dice_sum(0)? TRACE IT
- Same as 4, but this time calling dice_sum(2)? TRACE IT
counted loops
from random import randint
def dice_sum(num_dice: int) -> int:
sum = 0
dice_rolled = 0
while dice_rolled < num_dice:
sum += randint(1, 6)
dice_rolled += 1
return sum
BTW - did you see the bug?
#$%!@
from random import randint
def dice_sum(num_dice: int) -> int:
sum = 0
dice_rolled = 0
while dice_rolled < num_dice:
sum += randint(1, 6)
return sum
This error creates an infinite loop.
🙋🏻♂️❓🙋🏻♀️Which of the 3 categories of bug is this?
counted loops
These counted loops are all well and good...
We need a different solution!
...but it's more common to bump into situations where we can't know how many times we need to loop.
sentinel loops
sentinel loops
For Your Consideration
You've just attached an altimeter to a little birdie. (Go science!)
You plan on releasing said bird into the wild, letting it fly around for a few days, and then capturing it - and the altimeter.
You then want to grab the data off the altimeter so you can get a feel for the different altitudes the feathered fellow flew.
Say that the altimeter records integers representing the altitude (rounded to the nearest whole meter) at fixed intervals of one minute and marks the end of recording with an altitude of -1.
TASK
Write a program that echoes the data in the altimeter's memory to the console in minute : altitude format .
Assume you have a function called next_altitude() which gets the next altitude out of the altimeter's memory.
sentinel loops
As an example, if this is what's in the altimeter's memory...
0
5
4
0
10
0
-1
...then this is what we'd want our code to print to the console:
1:0
2:5
3:4
4:0
5:10
6:0
minute #s
altitudes
altitudes
We don't know in advance how many readings there are going to be, so a definite loop won't cut it.
We're going to need an indefinite loop!
sentinel loops
altitude = next_altitude()
curr_minute = 1
while altitude != -1:
print(f"{curr_minute}:{altitude}")
curr_minute += 1
altitude = next_altitude()one possible solution
A condition determines what keeps the loop going.
The condition uses a variable - the LCV (loop control variable).
The LCV is given a useful initial value.
The LCV is changed inside the loop.
One or more statements are run while the loop is going. (the loop body)
This has everything we come to expect from a good loop:
What's this step called again?
sentinel loops
This kind of loop is so common that it gets its own name!
altitude = next_altitude()
curr_minute = 0
while altitude != -1:
print(f"{curr_minute}:{altitude}")
curr_minute += 1
altitude = next_altitude()A sentinel loop is a loop that keeps going until it reaches a special value - the sentinel value.
The textbook calls this a listener loop.
sentinel loops
In this case, the sentinel value is -1
altitude = next_altitude()
curr_minute = 0
while altitude != -1:
print(f"{curr_minute}:{altitude}")
curr_minute += 1
altitude = next_altitude()As a programmer, you want to choose a value that is guaranteed NOT to be one you'd expect in the data you're looping through!
Here, we'd never expect to have a negative value for altitude;
we could choose any negative value (like -999), but it's a good idea to keep things simple!
sentinel loops
What would you choose as a sentinel value for...
- test scores?
- first names?
- decibel readings?
- temperatures?
- precipitation levels?
- incomes?
Choosing a reasonable sentinel value isn't always as easy as it seems at first glance - it requires some knowledge about the problem domain!
sentinel loops
There are a 3 things I'd like to call out here:
altitude = next_altitude()
curr_minute = 0
while altitude != -1:
print(f"{curr_minute}:{altitude}")
curr_minute += 1
altitude = next_altitude()- Is the sentinel ever used inside the loop itself?
- How well does this handle the case when there are NO altitudes? Trace it!
- This pattern - priming read, test for sentinel, re-prime - is something you will see repeatedly throughout your studies and your career - it's very useful to memorize it!


loopsies
loopsies
Loops can be a bit slippery at times!
Let's go over examples of some loops with some common bugs.
I sometimes call these bugs "loopsies", because my brain's an idiot that thinks its clever.
loopsies
Solid tracing skills - whether visually, on paper, or via a debugger - are essential for debugging loops.
You should look at every opportunity to trace something as a workout and not a chore.
loopsies
LETTER_LIMIT = 3
print("Enter a 3-letter word, one letter per line.")
num_letters_read = 0
num_lowercase_read = 0
while num_letters_read != LETTER_LIMIT:
letter = input()
if letter.islower():
num_lowercase_read += 1
print("There were", num_lowercase_read, "letters in that word.")
This Code SHOULD:
Read in 3 letters, then tell you how many of them were lowercase.
There is ONE loopsie in this code.
What is it? Trace to find it.
How would you fix this bug?
Loopsie: forgot to update the LCV every time through the loop. Super-common bug!
oops!
loopsies
dial_reading = int(input("dial reading? "))
while dial_reading < 0 and dial_reading > 10:
dial_reading = int(input("dial reading? "))
print("The dial is set to", dial_reading)
This Code SHOULD:
Validate input so the dial reading is in [0, 10].
There is ONE loopsie in this code.
What is it? Trace to find it.
How would you fix this bug?
Loopsie: that loop condition isn't what you think it is...
Pro Tip:
When dealing with compound loop conditions, it is often easier to think of a stopping condition and then "flip" it logically to come up with the loop condition!
loopsies
def damage_from_enemy():
return 3
def fight_unbeatable_foe(hit_points):
while hit_points != 0:
print(f"Ow! {hit_points} hp left!")
hit_points -= damage_from_enemy()
fight_unbeatable_foe(4)
This Code SHOULD:
Stop after the player is dead (i.e. reaches 0 hit points)
There are TWO loopsies in this code.
What are they? Trace to find it.
How would you fix these bugs?
Loopsie: that loop condition is too picky!
Loopsie: that loop condition is too picky!
Loopsie: that print happens too soon!
loopsies
low_bound = int(input("lower bound? "))
high_bound = int(input("upper bound? "))
curr_num = low_bound
num_odds = 0
while curr_num < high_bound:
curr_num += 1
if curr_num % 2 != 0:
num_odds += 1
print(f"# odds in [{low_bound},{high_bound}]: {num_odds}")
This Code SHOULD:
Find all odd numbers between 2 user-defined numbers, inclusive.
Loopsie #1: counted loop condition is off-by-one
Loopsie #2: LCV is changing too soon
There is TWO loopsies in this code.
What is it? Trace to find it.
How would you fix this bug?
lec-14
By Jordan Pratt
lec-14
counted loops | sentinel loops | common loopsies
- 270