Back to Article
PE100-04: Repetition
Download Notebook

PE100-04: Repetition

We started off learning Python with just simple lists of statements…

In [3]:
temperature = 100
print("It's", temperature, "celsius")
faren_t = temperature * (9/5) + 32
print("or", faren_t, "in pagan units.")
It's 100 celsius
or 212.0 in pagan units.

Then we added the if statement so we could control whether or not certain statements would execute or not:

In [4]:
if temperature >= 100:
    print("Good chance it's boiling.")
elif temperature > 3000:
    print("Odds are it's plasma by now.")
else:
    print("You could probably run the experiment and it might even work.")
Good chance it's boiling.

In both of these cases, the code blocks only run once.

The problem is, sometimes we need things to run repeatedly. We want to look up all of the readings from an experiment or we need to compute the properties of something over dozens of temperatures each at dozens of pressures.

Python gives us two different ways to make our programs repeat things in a loop.

  1. while loops run a block of code, repeatedly, until some conditional statement evaluates to false.

  2. for loops run a code block a specific number of times.

Let’s start with the while loop.

While Loops

The syntax of a while loop looks a bit like an if statement. Take a look:

In [1]:
instrument = 1
while instrument <= 2:
   print("Looking at instrument number", instrument)
   print("and then maybe we'll look at the next one.")
   instrument = instrument + 1
print("Done with all that looping.")
print("...and ready to do something else now.")
Looking at instrument number 1
and then maybe we'll look at the next one.
Looking at instrument number 2
and then maybe we'll look at the next one.
Done with all that looping.
...and ready to do something else now.

Here’s what the above code does. First, it creates a variable named “instrument” and sets it to 1. Then it goes into the while loop. The first time through, it checks to see if instrument is less than or equal to 2. It is (because we set it to 1 just a moment ago) so the while loop will execute the code block. This block prints out two lines and then it adds 1 to instrument. That means instrument now equals 2.

The second time through the loop, instrument equals 2. That satisfies the conditional statement of the while loop (2 is less than or equal to 2) so the code block runs again. Two more lines are printed out and then instrument is incremented one more time.

The while loop runs for a third time now. This time, 3 is not less than or equal to 2, so the conditional statement is false. This means the while loop is done - it won’t run its code block again, and the flow of control will go on to the next line after the while loop. It will run the two print statements explaining that the looping is over and it can go on to other tasks.

Let’s look at another example. Let’s print out all the powers of two that are less than 928.

In [5]:
power=0
two_to_the_power = 2**power

while two_to_the_power < 928:  # Totally not Porsche related.
    print("2 to the", power,"equals",two_to_the_power)
    power = power+1
    two_to_the_power = 2**power
print("2 to the", power,"is too big.")
2 to the 0 equals 1
2 to the 1 equals 2
2 to the 2 equals 4
2 to the 3 equals 8
2 to the 4 equals 16
2 to the 5 equals 32
2 to the 6 equals 64
2 to the 7 equals 128
2 to the 8 equals 256
2 to the 9 equals 512
2 to the 10 is too big.

Did you notice I sneaked something in there we haven’t talked about yet? See the “#” character on the line with the while statement? That indicates the rest of the line is a comment. Python will totally ignore it. It’s handy for leaving little notes to yourself, like “why did I choose 928 there when I could have put 944?” This is very, very important when writing full-fledged, standalone programs. If you don’t leave some notes for yourself, you’ll never remember what you were thinking when you go back to that code six months from now. Also, the next person who comes along and has to change something in your code will greatly appreciate the hints.

Leaving comments in the code isn’t as big a deal in Jupyter notebooks… you can write rather substantial notes in a Markdown cell complete with boldface, italics, and whatever other fanciness you desire. On the other hand, it’s also nice to be able to leave your comments in the just the right place in the code so it flows effortlessly through your comprehension as you read it. Let experience and personal opinion be your guide here.

Reading information from the outside world

Notice that in both of those cases, we actually did know how many times the loop would run. We know that 2 to the 9th is 512 and so we know the while loop will only run that far. In fact, in every example we’ve had so far we’ve know what the output will be because we always have the same inputs. Computer software wouldn’t be terribly interesting if it could only run specific, known, canned inputs. Fortunately, Python gives us several ways to bring data into our programs.

The simplest way to bring data into a Python program is to edit the program and change the values we assign to variables. This is sort of the reducto ad absurdum method, but honestly it isn’t a bad way to handle very small amounts of input. It’s even easier in Jupyter notebooks since the code is just sitting there looking at us, waiting to be edited. For values that aren’t going to change very often (your name, perhaps, or the chargeback account number for using some instrument, for instance) then just assigning a value to a variable and editing it every once and a while is a fine way to go.

Another way to get data into a Python program is to read it in from where the user is running the program. For doing this, Python provides a function called “input” which takes an optional argument, specifically a string that is printed as a prompt. Python then waits for the user to type something as a response. When they do, that string is returned to the calling program. Here’s a simple example:

In [3]:
your_name = input("Please enter your name")
print("Hello,", your_name)
Please enter your name Erik
Hello, Erik

When the above code runs, the prompt “Please enter your name” is displayed right below the code cell and a text entry box is placed beside it. When you enter your name, it greets you.

If we were running this tiny little snippet of code as a regular program, the interaction would be in the terminal emulator window that we ran the program in. Because this is running in Jupyter, though, the interaction is directly in the notebook. The prompt and the entry blank occur just below the running code cell.

What will happen when we run the following?

In [4]:
response = input("Enter a number between 4 and 8")
new_value = response + 6.5
print(new_value)
Enter a number between 4 and 8 5.25
TypeError: can only concatenate str (not "float") to str

Wow! Python couldn’t run that and it “threw an error”. We’ll examine Python’s error handling facilities later, but for now we’ll just assume that means it came to a screeching halt. Looking at the error message, it seems there is some problem with trying to add a real number (a floting point number) to a string.

Type Casting

input() prompts the user and returns the string they entered, but what if we want the user to enter a number? What do we do then? The answer is we’ll use a process known as type casting. The act of type casting is no more than converting information from one type to another.

There are three very useful functions for type casting: int(), float(), and str(). Let’s see them in action…

In [6]:
# three conversions:

# first, string to float:
the_sum = 6.554 + float("9.9")
print(the_sum)

# next, string to integer:
the_integer_number = int("34543456")
print(the_integer_number)

#finally, integer to string:
handy_string = str(2+2)
print(handy_string)
16.454
34543456
4

What did the above do? First, it converted the string “9.9” (literally, three characters… it’s a string) to a “float” (a floating point number, some languages will call that a real number). The second example takes a string of 8 characters and interprets them as an integer. That value is what gets returned and stored in our variable. Finally, we copmute the number 4 by adding 2+2, and then we let the str() function convert that to a single character long string having just the character “4”.

By now we know enough to be able to ask the user for a number and get something back that we can actually do math with.

In [10]:
user_response = input("Enter a number between 17 and 34")
selected_number = float(user_response)
selected_number

There’s an even easier way, though. Just like function composition worked when you took precalculus, the results of a Python function can be used as the argument to another. Hence:

In [8]:
selected_number = float(input("Enter a number between 17 and 34"))
selected_number
Enter a number between 17 and 34 26
26.0

Sometimes, function calls can be nested really deeply. Personally, when it comes time to debug code like that I find myself printing it out and coloring each level with a different highlighter pen.

Putting it together: while loops to get user input

The great thing about a while statement is that it can loop zero times, one, two, or twelve trillion. Best of all, we don’t have to know how many ahead of time. We could do the following:

print(“Computing an average.”)

sum=0.0 counter=0 data_point = float(input(“Enter a number, or enter negative num to stop”)) while data_point >= 0.0: sum = sum+data_point counter = counter+1 data_point = float(input(“Enter a number, or enter negative num to stop”))

print(“Average value is”, sum/counter)

When we run the code above, we’re prompted to keep entering numbers until we finally enter -999. Each time it goes through the loop it keeps track of the running total of the numbers and the count of how many numbers have been entered. Once it’s done, it divides the total by the count and displays that as the average.

Let’s step through what happens when the user enters 1, 2, 3, and -999: 1. The sum and counter variables are initialized to zero. 1. The user is prompted to enter a number, possibly a negative number to indicate no more data, and that input is type cast to a floating point number. 1. The while loop’s condition will be met any time a positive number was input (greater than or equal to zero). 1 is a positive number, so run the loop body. 1. This first time through, we’ll add the 1 that was input to our running total, which is now 1. 1. And increment the count, now equal to 1. 1. AND PROMPT THE USER FOR ANOTHER NUMBER!!! 1. Back at the while statement again, we check the condition and, yes, 2 is a positive number, so we run the loop’s code block. 1. Update the sum and count, and then… 1. PROMPT THE USER FOR ANOTHER VALUE!!! 1. Running the while statement again, the user entered 3, and 3 is positive, so the clode block will be executed. 1. Update the sum (now 6) and count (now 3). 1. Prompt for another number 1. Back at the while statement, we check and see that -999 is not a positive number, so we skip the code block and resume by running whatever follows it. 1. Having exited the while loop entirely, print out the average value by dividing sum/count.

All the boldface and all-capitals lines above are there to emphasize how important it is to make sure your while loop isn’t just checking the same thing over and over. If we didn’t get a new number from the user each time through, the value of data_point would never change. That would result in an infinite loop, causing Python to never be able to complete the code in that cell. If it ever happens to you, and it probably will, the “Interrupt Kernel” command on JupyterLab’s Kernel menu will stop the looping and let you get back to work.

The while loop is certainly versatile… it can be used any time you need to do something repeatedly. If you know how many times you need to have the code block execute, either when you write the code or when it’s running, then keep a variable that is incremented in the block every time and exit the while loop when the counter hits the right number.

Where while loops really shine is when it’s impossible to know ahead of time how many times the code block should run. The example above, where we keep accepting numbers until the user signals there aren’t any more, there’s no way to know how many times to execute that loop until we see a negative number. In a case like that, the while loop is the only practical solution.

So if while loops are so great and solve every problem, why do we need anything else? The big reason is expressiveness: they can be a little awkward to understand, especially when you’re looking at someone else’s code. Having the conditional test separated from the action that establishes when to stop makes it a little awkward to understand (or debug!) someone else’s code. This is especially true when we need to step through something by unusual increments.

So what are we to do in these cases?

For Loops

The for loop is quite similar to the while loop. The difference is that for loops are controlled by a count whereas while loops are controlled by a condition.

Let’s start with an example.

In [5]:
for the_value in range(1,4):
    print(the_value)
1
2
3

That is the simplest for loop you’ll see. Let’s look at the pieces. 1. The for statement itself 2. The name of the target variable whose value will be changing as the loop runs (“the_value” in this case”) 3. “in” - and if this reminds you of set membership then you’re on to something 4. “range()” - this is an example of an iterable, which means “something that can be stepped through”. 5. The colon… the one I forget 50% of the time. 6. The code block, in this case just a print statement.

Most of the time, fairly close to “always”, the code block will take advantage of the target variable changing each time through. In our example, “the_value” is our target variable, as it loops through it will take on the values 1 through 3, and the code block has a print statement that uses it.

Before we examine the range() function, let’s take a look at another iterable. We’ll talk about lists in a later lesson, but for now we can just wave our hands around and understand enough for the moment.

In [3]:
for sample_weight in [123.6, 121.9, 119.4, 124.23219]:
    print("The sample weighed", sample_weight, "grams.")
    if sample_weight < 120:
        print("Be careful! This sample might not be all you hoped for.")
        
The sample weighed 143.6 grams.
The sample weighed 141.9 grams.
The sample weighed 139.4 grams.
The sample weighed 144.23219 grams.

You can use the target variable as many times as you want to in the code block.

Now let’s take a more detailed look at the range() function. In its most basic form it takes one argument - the stop value.

In [6]:
for i in range(4):
    print(i)
0
1
2
3

This single-argument form starts at zero, counts up by one each time, and doesn’t include the stop value. This is different from every other programming language you’ll ever encounter. It’s just one of those things.

We’ve already seen the two-argument form. It takes a starting value and a stopping value, and iterates by one from the start until the last value that is less than the stop.

In [9]:
for i in range(7,10):
    print(i)
7
8
9

And there’s even a three-argument form. The third argument is the amount to step by.

In [10]:
for i in range (12,20,3):
    print(i)
12
15
18

The step size doesn’t have to be a positive number…

In [13]:
for i in range(6, -3, -2):
    print(i)
6
4
2
0
-2

In case you’re curious, the step size cannot be zero. If you really want an infinite loop, and there are cases where it makes sense, you have to use a while loop instead.

As a general rule, any place where you can use an explicit value (a literal) you can use a variable. Arguments to a for loop are no exception:

In [15]:
start_value = int(input("where should we start? "))
end_value   = int(input("where should we run right up to and stop just short of it? "))
step_size   = int(input("what should we step by? "))

for i in range(start_value, end_value, step_size):
    print(i)
    
where should we start?  13
where should we run right up to and stop just short of it?  15
what should we step by?  2
13

If we need to do something a specific number of times, we need to pay attention to our starting and stopping conditions. I’ve messed this up so many times I know now to be careful. You’ve been warned.

In [18]:
how_many = int(input("How many numbers would you like to total up? "))
sum = 0
for i in range(1, how_many):
    sum = sum + int(input("Enter a number "))
print("They add up to", sum)
How many numbers would you like to total up?  3
Enter a number  4
Enter a number  5
They add up to 9

Notice something wrong? If you ask it to total 3 numbers, it only prompts for two of them. There are a couple of ways to solve this. The easiest is to just use the one-argument form of range().

In [19]:
how_many = int(input("How many numbers would you like to total up? "))
sum = 0
for i in range(how_many):
    sum = sum + int(input("Enter a number "))
print("They add up to", sum)
How many numbers would you like to total up?  3
Enter a number  4
Enter a number  5
Enter a number  6
They add up to 15

That offers a little insight into why Python has it’s funny “up to but not including” semantics: zero is a perfectly legitimate number and a very natural starting point.

The only problem with the single-argument method is that the values that the target variable goes through include zero. This may or may not be a problem if that value is used inside the code block. If you really need to count from one instead of zero, you can increment the stopping value:

In [20]:
how_many = int(input("How many numbers would you like to total up? "))
sum = 0
for i in range(1, how_many + 1):
    sum = sum + int(input("Enter a number "))
print("They add up to", sum)
How many numbers would you like to total up?  3
Enter a number  4
Enter a number  5
Enter a number  6
They add up to 15

And that behaved just like we expected.

You may have noticed a pattern already. We frequently need to compute a new value for an existing variable. What we’ve done so far has been along the lines of grand_total = grand_total + new_reading. Python gives us a shorthand way to write that. We could instead express that as grand_total += new_reading. There is no space between the plus and equals signs. The only reason this exists is to save you some typing. As you might expect, there are a few more of these Augmented Assignment Operators

Operator Example Equivalent
+= count += 1 count = count + 1
-= x -= offset x = x - offset
*= product *= val product = product * val
/= y /= 3 y = y / 3
%= val %= 2 val = val % 2

Out of all of them, += is far and away the most commonly used one.

Nested Loops

You know what’s fun to put in a loop’s code block? Another loop! Best of all, it comes in pretty handy when dealing with high-dimensional data. Plenty of algorithms rely on nested loops, too. Take a look at this:

In [1]:
for x in range(5):
    for y in range(4):
        print("x=",x," y=", y)
x= 0  y= 0
x= 0  y= 1
x= 0  y= 2
x= 0  y= 3
x= 1  y= 0
x= 1  y= 1
x= 1  y= 2
x= 1  y= 3
x= 2  y= 0
x= 2  y= 1
x= 2  y= 2
x= 2  y= 3
x= 3  y= 0
x= 3  y= 1
x= 3  y= 2
x= 3  y= 3
x= 4  y= 0
x= 4  y= 1
x= 4  y= 2
x= 4  y= 3

What’s going on here? Initially, the outer loop, the one that iterates zero through four and assigns it’s value to x, runs. When it starts running its code block for the x=0 pass, the for loop for the y variable starts. ‘y’ assumes the values 0 through 3, so the first four lines printed out are for x=0, y=0, then x=0, y=1, and so on through x=0, y=3. Once that inner for loop completes, the outer for loop gets to iterate again. Now the inside for loop runs again, only this time we have x=1. That’s why the next four lines are “x=1, y=0” through “x=1, y=3”. Every time the outer loop runs another iteration, the inner loop gets to run all the way from start to finish.

In later lessons, we’ll have a few opportunities to play with nested loops. In fact, we’ll get to do that in the very next lesson: Functions!