Python loops

Loops are used when you need to run the same expression of similar data. There are two kinds of loops in Python: for loops and while loops.

for loops

The syntax of for loops is as follows:

for <variable> in <collection>:
    # run an expression potentially using <variable>
    # run another expression ...

The idea is that <collection> is an iterable object like a tuple or a list and that <variable> takes sequentially each one of the values in that iterable object. Here are a few examples:

for x in (1, 5, 6, 10, 20):
    print('x = {0:d}'.format(x))
x = 1
x = 5
x = 6
x = 10
x = 20
for x in [1, 5, 6, 10, 20]:
    print('x = {0:d}'.format(x))
x = 1
x = 5
x = 6
x = 10
x = 20
for c in 'This is a string':
    print('c = \'{0:s}\''.format(c))
c = 'T'
c = 'h'
c = 'i'
c = 's'
c = ' '
c = 'i'
c = 's'
c = ' '
c = 'a'
c = ' '
c = 's'
c = 't'
c = 'r'
c = 'i'
c = 'n'
c = 'g'

Very often, we want to loop over all values from 0 to a given number. We can do this with range:

for i in range(10):
    print('i = {0:d}'.format(i))
i = 0
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9

You don’t have to start at 0:

for i in range(2, 10):
    print('i = {0:d}'.format(i))
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9

And you can skip numbers:

for i in range(2, 10, 2):
    print('i = {0:d}'.format(i))
i = 2
i = 4
i = 6
i = 8

And you can also go backwards:

for i in range(10, 0, -1):
    print('i = {0:d}'.format(i))
i = 10
i = 9
i = 8
i = 7
i = 6
i = 5
i = 4
i = 3
i = 2
i = 1

Let’s now create some random data and write code to calculate the average:

data = [0.16295757, 0.06804887, 0.76082389, 0.39089033, 0.63845432,
       0.12555193, 0.98251807, 0.38759292, 0.75819177, 0.63329008,
       0.94419572, 0.95363126, 0.60753037, 0.55727347, 0.55989237,
       0.2482676 , 0.13208943, 0.25090889, 0.7912593 , 0.30045856]

Recall that the average of \(N\) numbers \(x_1, x_2,\dots,x_N\) is:

\[ \bar{x} = \frac{x_1+\dots+x_N}{N}. \]

Here is how we can code this with a for loop:

# The number of observations
N = len(data)
# A variable that stores the sum of all the x's as we go through
# the data
s = 0.0
# The loop
for i in range(len(data)):
    x = data[i]
    # This is just a shortcut for s = s + x
    s += x
average = s / N
print('Average = {0:1.2f}'.format(average))
Average = 0.51

Let’s compare this to the other way we have learned:

print('Average the other way = {0:1.2f}'.format(sum(data) / N))
Average the other way = 0.51

Okay. It worked fine. Here is another way to do the same thing. Instead of using an index, we can directly go over each element in the list like this:

s = 0.0
for x in data:
    s += x
average = s / N
print('Average = {0:1.2f}'.format(average))
Average = 0.51

Questions

  • Write a code block that calculates the average of all the data elements that are greater than 0.5.

# Your code here
  • Write a for loop to calculate the second moment of the data, i.e., this expression:

\[ \bar{x}_2 = \frac{x_1^2 + \dots + x_N^2}{N} \]
# Your code here
  • In the code block provided below, write a for loop that finds the maximum of the data.

# Your code here

while loops

While loops are a bit more general than for loops as stopping depends on a boolean expression. Their syntax is like this:

while <boolean_expression>:
    # Some expressions to execute
    # Some other epxression to execute

Let’s see some examples of this:

i = 0
while i < 10:
    print('i = {0:d}'.format(i))
    i += 1
i = 0
i = 1
i = 2
i = 3
i = 4
i = 5
i = 6
i = 7
i = 8
i = 9

Let’s do a more mathematical example. We are going to write code that calculates the sum of a series to a given tolerance. That is, given a sequence of numbers: \(a_1, a_2, \dots,\), we will write code that approximates:

\[ a = \sum_{n=0}^\infty a_n. \]

To be specific, we are going to sum this series:

\[ 1 - \frac{1}{3} + \frac{1}{5} - \frac{1}{7} + \dots = \frac{\pi}{4}, \]

and use it to approximate \(\pi\). This is known as the Leibniz formula. In terms of the \(\alpha_n\) notation above, the formula is:

\[ \sum_{n=0}^\infty a_n = \sum_{n=0}^\infty\frac{(-1)^{n}}{2n + 1}. \]

Here is our first attempt:

# The maximum number of iterations you are willing to do:
max_iter = 100000
# The result
a = 0.0
# Start a counter
n = 0
# Start the loop
while n <= max_iter:
    # Compute the new term
    a_n = (-1) ** n / (2 * n + 1)
    # Add it to the sum
    a += a_n
    # and increase the counter
    n += 1
print('4 * a ~= {0:1.12f}'.format(4 * a))
4 * a ~= 3.141602653490

Let’s now make our code print something every few iterations (say every 10,000 iterations).

# The maximum number of iterations you are willing to do:
max_iter = 100000
# The result
a = 0.0
# Start a counter
n = 0
# Start the loop
while n <= max_iter:
    # Compute the new term
    a_n = (-1) ** n / (2 * n + 1)
    # Add it to the sum
    a += a_n
    # Print something about the current iteration
    if n % 10000 == 0:
        print('Current iteration n = {0:10d}, pi estimate so far: {1:1.12f}'.format(n, 4 * a))
    # and increase the counter
    n += 1
print('')
print('4 * a ~= {0:1.12f}'.format(4 * a))
Current iteration n =          0, pi estimate so far: 4.000000000000
Current iteration n =      10000, pi estimate so far: 3.141692643591
Current iteration n =      20000, pi estimate so far: 3.141642651090
Current iteration n =      30000, pi estimate so far: 3.141625985812
Current iteration n =      40000, pi estimate so far: 3.141617652965
Current iteration n =      50000, pi estimate so far: 3.141612653190
Current iteration n =      60000, pi estimate so far: 3.141609319979
Current iteration n =      70000, pi estimate so far: 3.141606939100
Current iteration n =      80000, pi estimate so far: 3.141605153434
Current iteration n =      90000, pi estimate so far: 3.141603764577
Current iteration n =     100000, pi estimate so far: 3.141602653490

4 * a ~= 3.141602653490

Now, let’s not run forever. We are going to modify the code so that it loops over \(n\) until \(|a_{n+1}| < \epsilon\), where \(\epsilon\) is a small positive number typically called a tolerance. We are going to use the keyword break to achieve this. break is a command to exit the loop (note that it also works in for loops). Here we go:

# The maximum number of iterations you are willing to do.
# You always add something like this to avoid looping forever
max_iter = 10000000
# The tolerance you wish to reach:
epsilon = 1e-6
# The result
a = 0.0
# Start a counter
n = 0
# Start the loop
while n <= max_iter:
    # Compute the new term
    a_n = (-1) ** n / (2 * n + 1)
    # Check if the absolute value of the new term is smaller than the tolerance
    if abs(a_n) < epsilon:
        # If it is indeed smaller, exit the loop
        # you do this with the command
        print('*** Converged in {0:d} iterations! ***'.format(n+1))
        break
    # Otherwise we just add the new term to our running sum
    a += a_n
    # Print something about the current iteration
    if n % 100000 == 0:
        print('Current iteration n = {0:10d}, sum so far: {1:1.12f}'.format(n, 4 * a))
    # and increase the counter
    n += 1
if n == max_iter + 1:
    print('*** Stopped when maximum number of iterations ({0:d}) were reached! ***'.format(max_iter))
print('4 * a ~= {0:1.12f}'.format(4 * a))
Current iteration n =          0, sum so far: 4.000000000000
Current iteration n =     100000, sum so far: 3.141602653490
Current iteration n =     200000, sum so far: 3.141597653565
Current iteration n =     300000, sum so far: 3.141595986912
Current iteration n =     400000, sum so far: 3.141595153583
*** Converged in 500001 iterations! ***
4 * a ~= 3.141590653590

Questions

  • Modify the code above so that you estimate the series:

\[ \sum_{n=0}^\infty\frac{1}{2^n}, \]

which converges to \(2\).