7  Loops

Jupyter Notebook

Loops allow programs to rerun the same block of code multiple times. This might seem like a funny thing to want to do but it turns out that there are many important tasks that are repetitive in nature (perhaps with small changes for each successive repetition). A loop provides a succinct and efficient way to perform tasks of this nature.

7.1 for loops

The for loop is probably the most common loop you will encounter and is a good choice when you know beforehand exactly what things you want to loop over. Here is an example of a for loop that is used to add up the elements of a list.

thesum = 0
for i in [3,2,1,9.9]: 
    thesum += i

This would be equivalent to the following code:

thesum = 0

thesum = thesum + 3
thesum = thesum + 2
thesum = thesum + 1
thesum = thesum + 9.9

which isn’t that much longer than using a loop. However, as the list gets longer and/or the mathematical operations being performed get more complex the second method would get unreasonably long.

The correct language is to say that we are iterating over the list [3,2,1,9.9]. This means that the loop variable (i in this case but you can choose it to be whatever you want) gets assigned the values of the list elements, one by one, until it reaches the end of the list. You can use for loops to iterate over any multi-element object like lists or tuples. Python uses indentation to indicate where the loop ends. In this case there was only one statement inside to loop, but if you wanted more than one each line should be indented.

To Do:

  1. Add a print statement inside of the loop above to display the value of the variable thesum.
  2. Predict what the output will be and then run the code and verify that you were correct.
  3. Now change the loop variable to be named physics and verify that the loop still works as expected.

You can iterate over range objects and strings using for loops.

for i in ['Physics', 'is','so','fun']: # Iterate over a list of strings
    print(i)
Physics
is
so
fun
for i in range(5,50,3):  #Generates a list from 5 -> 50 with a step size of 3
    print(i)
5
8
11
14
17
20
23
26
29
32
35
38
41
44
47

These examples are so simple that you might wonder when a loop might actually be useful to you. Let’s see if we can build a loop to calculate the following sum

\[ \sum_{n=1}^{1000} {1\over n^2} \tag{7.1}\]

theSum = 0
for n in range(1,1000):
    theSum = theSum + 1/n**2
print(theSum)
1.6439335666815615

Here, n is being assigned the values 1,2,3,4....1000, one by one, until it gets all the way to 1000. Each time through the loop, n is different and the expression 1/n**2 evaluates to a new value. The variable theSum is updated each time through to be the running total of all calculations performed thus far. Here’s another example of a loop used to calculate the value of \(20!\):

theProduct = 1
for n in range(1,21):
    theProduct = theProduct * n #Multiply theProduct by n
print(theProduct)
2432902008176640000

Remember that the range function creates a list starting at \(1\), going up to \(21\) but not including it. The math library has a function called factorial that does the same thing. Let’s use it to check our answer:

from math import factorial
factorial(20)
2432902008176640000

7.1.1 Boolean Logic Inside Loops

Often when using loops, we only want a block of code to execute when some condition is satisfied. We can use boolean logic inside of the loop to accomplish this. For example, let’s write a loop to compute the following sum:

\[ \sum_{{n\over 5} \in \text{ Int and } {n\over 3} \in \text{ Int}} {1\over n^2} \]

which is similar to the one we did above, but this time we only want to include terms where \(n\) is a perfect multiple of both 5 and 3. To check to see if n is a perfect multiple of a number we can calculate the modulo (remainder after division) using the % operator and check that it is equal to zero.

theSum = 0
for n in range(1,1000):
    if n % 5 == 0 and n % 3 == 0:
        theSum = theSum + 1/n**2
print(theSum)
0.007243985583159138

To Do:

Perform the following modifications to the loop above.

  1. Increase the upper bound of the sum to go up to and include \(5000\).
  2. Only include the terms where n is a multiple of 5 and 3 or is a multiple of 7.
  3. Replace the statement that updates theSum to its shorthand version.

7.1.2 Zipping and Enumerating

There are times when it is necessary to iterate over two lists simultaneously. For example, let us say that we have a list of atomic numbers (AN) and a list of approximate atomic masses (mass) of the most abundant isotopes for the first six elements on the periodic table.

AN = [1,2,3,4,5,6]
mass = [1,4,7,9,11,12]

If we want to calculate the number of neutrons in each isotope, we need to subtract each atomic number from the atomic mass. To accomplish this, it would be nice to iterate over both lists simultaneously

7.1.2.1 Zipping

The simplest way to iterate over two lists simultaneously is to combine both lists into a single, iterable object and iterate over it once. The zip function does just that by merging two lists or tuples into a nested list

AN = [1,2,3,4,5,6]
mass = [1,4,7,9,11,12]

zipped = zip(AN,mass)

for pair in zipped:
    print(pair[1] - pair[0])
0
2
4
5
6
6

The zip objects are “single use” so you can’t reuse zipped in a later loop. If the two lists being zipped are not the same length, zip stops zipping when it reaches the end of the shorter list.

To Do:

  1. Print the variable zipped and inspect closely. Was the output what you expected?
  2. Now do print(tuple(zipped)) and inspect closely. Draw a conclusion.
  3. Add a few more entries to the list named AN, but don’t add the corresponding entries to the other list. Now the lists being zipped aren’t the same length. Inspect the output of the print statement to determine what zip does in this scenario.

7.1.2.2 Enumeration

A close relative to zip is enumerate which zips a list to the index value for that list (read that last statement again). It also returns a “single use” object that can be iterated over.

AN = [1,2,3,4,5,6]
mass = [1,4,7,9,11,12]

enum = enumerate(mass)

for idx,val in enum:
    print(val - AN[idx])
0
2
4
5
6
6

To Do:

  1. Repeat the previous To-Dos for the cell above.

7.1.3 List Comprehension

It is fairly common to use a for loop to populate a list with a sequence of numbers.

myList = []

for i in range(10):
    myList.append(i**2)

print(myList)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

This entire process can be compressed down into a single line by expressing the for loop in square brackets. This is known as list comprehension. The code below will generate the list as above.

myList = [i**2 for i in range(10)]
print(myList)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

List comprehension can take a little time to get used to but it is well worth it. It saves both time and space and makes code less cluttered. You can even add boolean expressions to your conditionals for further control of the final result.

myList = [i**2 for i in range(10) if i %2 == 0]  # Include only the evens.
print(myList)
[0, 4, 16, 36, 64]

To Do:

Think of your favorite mathematical function and modify the code above to generate a few samples from it.

7.2 while Loops

Logic can be combined with loops using something called a while loop. A while loop is a good choice when you don’t know beforehand exactly how many iterations of the loop will be executed but rather want the loop to continue to execute until some condition is met. As an example, notice that in Equation 7.1, the terms in the sum get progressively smaller as \(n\) gets bigger. It doesn’t make sense to continue adding to the sum once the terms get very small. Let’s compute this sum by looping until the fraction \({1 \over n^2}\) become smaller than \(1 \times 10^{-2}\).

term = 1  # Load the first term in the sum
s = term  # Initialize the sum
n = 1     # Set a counter
while term > 1e-2:  # Loop while term is bigger than 1e-2
    n = n +  1        #Add 1 to n so that it will count: 2,3,4,5
    term = 1./n**2    # Calculate the next term to add
    s = s +  term     # Add 1/n^2 to the running total

This loop will continue to execute until term<1e-2. Note that unlike the for loop, here you have to do your own counting if you need to know how many iterations have been performed. Be careful about what value n starts at and when it is incremented (n = n + 1). Also notice that term must be assigned prior to the start of the loop. If it wasn’t the loop’s first logical test would fail and the loop wouldn’t execute at all.

To Do:

  1. Decrease the threshold on the termination condition and observe any changes in the final result and how many more iterations are performed.
  2. After toying around with it for a while pick a termination condition that you feel will produce a result that is accurate.

while loops should be used with caution because you can easily write a faulty termination condition and inadvertently write a loop that runs forever. This happens because your termination condition was never met. An example of this is given below.

Warning: Do not execute the code block below!!

x = 0

while x != 10:
    x = x + 3
print("Done")

The loop above is intended to end after a few iterations when the value of x is equal to 10. However, closer inspection reveals that the value of x will never be equal to 10. After the first iteration x is equal to 3, then 6,9,12,15 and so on… but never 10. This loop will run forever because the termination condition is never met (x != 10 never produces a False)!! If you choose to use a while loop, triple check your termination condition to make sure you haven’t made a mental error. Avoiding the use of != or == in your termination condition will help too. Use <= or >= instead.

To Do:

  1. Modify the termination condition in the loop above so that it terminates when x gets larger than 15.
  2. Run the code and verify that you did it correct.

7.3 continue, break, and pass Commands

The continue, break, and pass commands are used to control the flow of code execution in loops. Here is a description of their usage:

Operator Description
break Exits a for/while loop.
continue Skips the remaining loop block and begins the next iteration.
pass No action; code contiues on

The break statement is useful when you want to completely stop a loop early. Here is our sum loop rewritten with a break statement added to stop the loop after 1000 iterations.

term = 1  # Load the first term in the sum
s = term  # Initialize the sum
n = 1     # Set a counter
while term > 1e-10:  # Loop while term is bigger than 1e-10
    n +=  1        #Add 1 to n so that it will count: 2,3,4,5
    term = 1./n**2    # Calculate the next term to add
    s += term     # Add 1/n^2 to the running total
    if n > 1000:
        print('This is taking too long. I''m outta here...')
        break
This is taking too long. Im outta here...

The continue statement is similar to break except that instead of stopping the loop, it only stops the current iteration of the loop. All code below the continue statement will be skipped and the next iteration will begin. For example, if you wanted to do the sum from equation ?? but only include those terms for which n is a multiple of 3, it could be done like this:

term = 1  # Load the first term in the sum
s = term  # Initialize the sum
n = 1     # Set a counter
while term > 1e-10:  # Loop while term is bigger than 1e-10
    n +=  1        #Add 1 to n so that it will count: 2,3,4,5
    if n % 3 != 0:
        continue
    term = 1./n**2    # Calculate the next term to add
    s += term     # Add 1/n^2 to the running total

Now, when the value of n is not a multiple of 3, the sum will not be updated and the associated terms are effectively skipped.

Finally, the pass statement does nothing. Seriously!! It is merely a place holder for code that has not bee written yet. Usually, you’ll use the pass statement to run and test code without errors due to missing code.