# For loops

## Contents

# For loops#

Without us telling it otherwise, Python will execute code within a cell from top to bottom, with each line being run once. However, most of the important tasks that computers are used for involve repeating steps a large number of times. A computer’s ability to repeat steps quickly is one of the main things that make them so useful. We can have Python repeat lines by simply copy and pasting them:

```
from mobilechelonian import Turtle
tina = Turtle()
# make tina go a bit faster for convenience
tina.speed(5)
# draw a square by repeatedly moving forward then turning left 90 degrees
tina.forward(60)
tina.left(90)
tina.forward(60)
tina.left(90)
tina.forward(60)
tina.left(90)
tina.forward(60)
tina.left(90)
```

```
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
Input In [1], in <cell line: 1>()
----> 1 from mobilechelonian import Turtle
3 tina = Turtle()
5 # make tina go a bit faster for convenience
ModuleNotFoundError: No module named 'mobilechelonian'
```

Doing this has many problems; for example, it is prone to mistakes, difficult to read, and difficult to modify (imagine we realised we wanted a hexagon instead). A `for`

loop makes the code much tidier:

```
raph = Turtle()
# make tina go a bit faster for convenience
raph.speed(5)
# To repeat something 4 times, we use the following syntax
for i in range(4):
# these indented lines will be repeated 4 times
raph.forward(60)
raph.left(90)
# To create any other regular polygon, you would only need to change two numbers
```

Let’s break down the code above. When we want Python to repeat a process a certain *number* of times, or once for each object in some sequence, we typically use a `for`

loop. The alternative is to loop until some *condition* is satisfied, which we will see in a later notebook on `while`

loops. The `for`

loop has the basic syntax

```
for x in some_sequence:
indented body statements to be repeated, possibly making use of x
next thing to do, not repeated
```

In the example above, the `some_sequence`

is `range(4)`

, which represents the sequence `0, 1, 2, 3`

.
In that example, we didn’t really care about what the sequence was, only that it had four elements in it so that the code would be repeated four times (note that the variable `i`

was never used).

Here’s an example where we actually use the sequence in the `for`

loop:

```
for i in range(4):
print(f"i is {i}")
print(f"i squared is {i**2}")
# any following unindented lines will not be repeated
print(f"now the loop is done, and the final value of i is {i}")
```

```
i is 0
i squared is 0
i is 1
i squared is 1
i is 2
i squared is 4
i is 3
i squared is 9
now the loop is done, and the final value of i is 3
```

You can use any variables in your `for`

loop; the only requirement is that `some_sequence`

makes sense to “loop over”. The technical name for this is that `some_sequence`

is * iterable*, and it includes things like

`lists`

, `sets`

, `tuples`

, and `ranges`

, and most objects which “contain” other objects.The * body* statements are repeated once for each value in the sequence. Like regular code, they are executed from top to bottom within a

`for`

loop. It is important to remember that the body statements are all executed in sequence for each value of `x`

, rather than executing the first statement for all `x`

, and then the second statement for all `x`

, and so on. You can see this behaviour in the examples above.## Computing sums and pseudocode#

A common example of where you might use a `for`

-loop is to sum up some values. For example, let’s try to compute the sum

\[ S(n) = \sum_{i = 1}^{n} i^{10}, \]

where \(n\) is fixed. When we are trying to write code to solve a problem, it is often the best strategy to first understand how we would systematically do the computation by hand. To compute \(S(n)\), the simplest method is to keep a running total, starting with zero and adding \(i^{10}\) to the total for \(i = 1, \ldots, n\). We might write this down in the following way:

We have some fixed \(n \in \mathbb{N}\)

Start with the total being 0

For \(i = 1, 2, \ldots, n\):

Add \(i^{10}\) to the total

Report the total as the answer

We call this sort of description of how to systematically solve a problem * pseudocode*. Pseudocode is not code and there is no specific standard which you must follow when writing it, but it should be possible to turn pseudocode directly into code. For example, the pseudocode above translates directly into Python like so:

```
# fix a value of n
n = 10
# start with the total being 0
total = 0
# loop through i = 0, 1, ..., n
for i in range(n + 1):
# add i**10 to the total
total = total + i**10
# report the total as the answer
total
```

```
14914341925
```

# Ranges#

In the example above, we used the command `range(4)`

to create the sequence `0, 1, 2, 3`

. Ranges are a very common way of making sequences of integers. You can see the numbers in a `range`

by using the `list`

command:

```
list(range(4))
```

```
[0, 1, 2, 3]
```

By default, a `range`

starts at `0`

and stops * before* reaching the stop value. You can change where the range starts by providing a

`start`

value as well:```
list(range(4, 10))
```

```
[4, 5, 6, 7, 8, 9]
```

Sometimes we don’t want the integers to increase by `1`

each time. We can change this by giving a `step`

argument. For example, to produce the odd numbers between 1 and 21 (inclusive), we could start at 1 and step by 2 each time:

```
list(range(1, 22, 2))
```

```
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21]
```

The `stop`

value doesn’t have to be precisely 1 more than the final value; it just has to be *less than or equal to* than the next value that the step would produce. In the previous example, using `23`

as the stop value would produce the same result.

```
list(range(1, 23, 2))
```

```
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21]
```

We can also go backwards by giving a negative step value. Note that in this case, the final element of the `range`

will be *greater* than the `stop`

value.

```
list(range(23, 11, -2))
```

```
[23, 21, 19, 17, 15, 13]
```

# Looping over lists#

It is very common to loop over a `range`

, but you can also loop over other things. We’ll see more on `lists`

later, but for now here is an example where we iterate over a `list`

of strings:

```
fruits = ["apple", "durian", "mangosteen", "pear"]
for fruit in fruits:
print(f"'{fruit}' has {len(fruit)} letters")
```

```
'apple' has 5 letters
'durian' has 6 letters
'mangosteen' has 10 letters
'pear' has 4 letters
```

# Nested `for`

loops#

It is often useful to “nest” a loop inside another loop. When this occurs, the “inner” loop (i.e. the one inside the body of the other loop) will be fully executed, then the “outer” loop will iterate; the inner loop will execute again, and so on until the outer loop is finished.

This idea is easiest seen by example. Note that the body of the inner loop is indented two levels.

```
for i in range(2):
print("Start of the outer loop body")
for j in range(3, 5):
print(f" Inner loop: the outer loop variable is {i} and inner loop variable is {j}")
print("End of the outer loop body")
```

```
Start of the outer loop body
Inner loop: the outer loop variable is 0 and inner loop variable is 3
Inner loop: the outer loop variable is 0 and inner loop variable is 4
End of the outer loop body
Start of the outer loop body
Inner loop: the outer loop variable is 1 and inner loop variable is 3
Inner loop: the outer loop variable is 1 and inner loop variable is 4
End of the outer loop body
```

This might seem like a strange thing to want to do, but many problems involve repeating a repetitive task. For example, suppose you have an inbox full of emails you need to respond to. You could represent the basic process of responding to all your emails like this:

For each email in your inbox:

For each word in the email:

Read the word

Think of a response

For each word of the response:

Write the word

Add any necessary punctuation

We could also break down writing words into repeatedly typing letters - another level of nesting in our repetition! You can nest loops as much as you want in Python, but if you find yourself with many levels of nesting it is usually a sign that you need to reconsider what you are doing and try to simplify it.

Here’s another example, where we output some short multiplication tables.

```
for i in range(7, 10):
print(f"{i} times table:")
for j in range(1, 5):
print(f"{i} x {j} is {i*j}")
print() # print a blank line; note that this is only in the "outer" loop
```

```
7 times table:
7 x 1 is 7
7 x 2 is 14
7 x 3 is 21
7 x 4 is 28
8 times table:
8 x 1 is 8
8 x 2 is 16
8 x 3 is 24
8 x 4 is 32
9 times table:
9 x 1 is 9
9 x 2 is 18
9 x 3 is 27
9 x 4 is 36
```

Here’s an example with a turtle; we draw a triangle with a small hexagon attached to each corner.

```
from mobilechelonian import Turtle
leo = Turtle()
leo.speed(8)
# loop 3 times for the triangle
for i in range(3):
leo.forward(100)
# get the hexagon in the right orientation.
leo.right(90)
# loop 6 times for the hexagon
for i in range(6):
leo.forward(30)
leo.left(60)
# point the turtle back in the right direction for the next triangle edge
leo.left(210)
```

Finally, let’s use nested loops to compute the double sum

\[ \sum_{i = 1}^{10} \sum_{j = 2 \text{, j even}}^{20} \frac{1}{i ^2 j}. \]

```
# start with the total being 0
total = 0
# Use nested for-loops for the double sum.
# The loop variables correspond to the sum indices.
for i in range(1, 11):
for j in range(2, 21, 2):
term = 1 / (i**2 * j)
# the next line means "total = total + term"
total += term
total
```

```
2.2696102428056033
```