# Loops and Lists¶

`for`

Loops and Lifetime Savings¶

Because Python is quick and easy (once you have a handle of it), it serves as a useful tool for playing out ideas. For example, suppose that we want to visualize a savings decision:

Once you begin contributing to a savings account, assume that you put away \(x\) dollars per year and expect an annual return of \(r\). How much money will you have saved in \(55\) years?

Begin by defining the *variables* you need

```
x = 5000
r = 0.06
```

Now, *compound* the returns you get over the next \(55\) years. Imagine that today is year 0. At the end of year 0, you’ll have saved \(x \times (1+r)\). Then, after another, you’re year zero savings grow by another \((1+r)\), and your year one savings (since you put away \(x\) more each year), also grows by \((1+r)\). The following table helps visualize the idea:

Time |
Value of Money Saved at Year 0 |
Value of Money Saved at Year 1 |
Value of Money Saved at Year 2 |
Value of Money Saved at Year 3 |
---|---|---|---|---|

0 |
\(x\) |
. |
. |
. |

1 |
\(x(1+r)\) |
\(x\) |
. |
. |

2 |
\(x(1+r)^2\) |
\(x(1+r)\) |
\(x\) |
. |

3 |
\(x(1+r)^3\) |
\(x(1+r)^2\) |
\(x(1+r)\) |
\(x\) |

Add up the values across each column to get the total savings at the *beginning* of a given year. This sum is reported in the following table:

Time | Total Savings (beginning of year) | Total Savings (algebraically re-arranged) |
---|---|---|

$0$ | $x$ | $x$ |

$1$ | $x(1+r)+x$ | $x(1+r)+x$ |

$2$ | $x(1+r)^2+x(1+r)+x$ | $\Big[$$x(1+r)+x$$\Big](1+r)+x$ |

$3$ | $x(1+r)^3+x(1+r)^2+x(1+r)+x$ | $\Big[$$x(1+r)^2+x(1+r)+x$$\Big](1+r)+x$ |

Comparing values across columns two and three of the above table, a convenient pattern emerges. For instance, the value in year 2, column three equals \(x\) plus \((1+r)\) times the value in year 1, column two.

If we want to calculate the value of your savings acount at the beginning of year \(55\), we need to tell Python to compound returns for \(55\) years. The really awful way to do this is to tell Python to calculate \(x(1+r)^{55}+x(1+r)^{54}+x(1+r)^{53}+...+x(1+r)^2+x(1+r)+x\). The programmer’s way to do this is to use the pattern we pointed out in the above table, and make use of a *for loop* that operates over a range of years.

If we wanted to loop over years \(0\) through \(3\), we could tell Python to use a `for`

loop to iterate over a list of the numbers \(0\) through \(3\). A `for`

loop has four components to it:

the word

`for`

: tells Python to execute a`for`

loopa variable name : this serves as a

*loop index*. You’ll see in a moment what this index does. A common name used here is the letter`i`

the word

`in`

: tells Python that the loop index will be contained within ‘’something’’.a list : this is the ‘’something’’ that we loop over. Every item in the list gets used once.

```
for i in [0, 1, 2, 3]:
print(i)
```

```
0
1
2
3
```

What a `for`

loop does is tells Python to loop over the list specified (in this case, the list of numbers \(0\) through \(4\)). For each item in that list, Python sets the loop index variable (in this case, we defined the index variable to be named `i`

) equal to that value of the list and then executes every step within the for loop (all of the indented lines of code underneath the line `for`

. Here, our for loop only specifies one action: print out the value of the loop index.

In our motivating example, we want Python to work over a long range of years. In this case, years \(0\) through \(55\).
This would be tedious to type out explicitly into `[0, 1, 2, ..., 54, 55]`

. Python has a trick for this, but there’s one caveat to keep in mind. As children, we learn to start counting with the number \(1\). Python, like most programming languages, starts counting with the number \(0\). Thus, the built-in function `range()`

uses this starts-at-zero convention.

```
for t in range(5):
print(t)
```

```
0
1
2
3
4
```

As demonstrated above, `range(5)`

tells Python that the loop should iterate over numbers \(0\) through \(4\). Generally, we use `range(n)`

to obtain numbers \(0\) through \(n-1\) (*not* \(0\) through \(n\)). Thus, we get \(n\) numbers out of `range(n)`

. If we want to iterate up through year \(55\), we would therefore use the statement `range(56)`

.

To calculate the value of your savings after \(55\) years, we simply have to type out:

```
savings = 0 # initially, 0 dollars in savings account
for i in range(56):
savings = savings*(1+r) + x # each year, pre-existings savings grows by (1+r) and an additional x is added
print(savings)
```

```
2094111.7408149564
```

**Concept check**: Edit the following code so that the value for `my_savings`

is printed out at years `10`

, `20`

, and `30`

. Hint: check for whether `i`

is `in [10, 20, 30]`

, or use the `%`

operator.

```
my_savings = 0
for i in range(35):
my_savings = my_savings*(1+r) + x
# edit the code here!
print(savings) # this will print the savings after 35 years, we want to see savings at year 10, 20, 30 as well
```

```
2094111.7408149564
```

For loops allow us to quickly visualize an idea. For example, let’s return to the valuation problem from earlier in the chapter.

```
def better_wacc(E, D, rE, rD, tC):
if rE <= 0:
raise Exception('rE is not positive')
if rD <= 0:
raise Exception('rD is not positive')
if tC <= 0:
raise Exception('tC is not positive')
V = E + D
cost_of_capital = E / V * rE + D / V * rD * (1-tC)
return cost_of_capital
def cost_of_equity(E, D, rA, rD, tC):
return rA + (rA-rD)*(1-tC)*(D/E)
def firm_value(E, D, rA, rD, tC, FCF, g):
rE = cost_of_equity(E=E, D=D, rA=rA, rD=rD, tC=tC)
rW = better_wacc(E, D, rE, rD, tC)
if rW < g:
raise Exception('rW must be greater than g, but g=', g, 'and rW=', rW)
return FCF / (rW - g)
```

Earlier, we saw that the value of the firm changed as its leverage ratio changed. A more complete, easier to see representation of this idea is to loop over many different debt-equity mixes and check what happens to firm value.

Before jumping on the firm value example, let’s clarify the usages of `range()`

. There are three possible ways to call the `range()`

function:

`range(n)`

: gives numbers \(0\) to \(n\), i.e. [0, 1, …, n]`range(k,n)`

: gives numbers \(k\) to \(n\), i.e. [k, k+1, …, n]`range(k,n,b)`

: gives numbers \(k\) to \(n\), incrementing by \(b\)

It’s perhaps easiest to understand the use cases with a few examples.

```
for i in range(7):
print(i)
```

```
0
1
2
3
4
5
6
```

```
for i in range(2,7):
print(i)
```

```
2
3
4
5
6
```

```
for i in range(2,7,3):
print(i)
```

```
2
5
```

In the earlier example estimating firm value, the level of debt and equity of the firm added up to be 100. Let’s stick to the example numbers used earlier, and consider different debt-equity mixes that add to 100. We could iterate over possible debt levels of 0 to 90 with either a call to `range(91)`

or `range(0,91)`

, but that would call the `firm_value()`

function 91 times! That many calculated firm values would be hard to read. To make things easier on ourselves, let’s try debt values of 0, 10, 20, …, 90. We can do this with the three-input version of the `range()`

function call.

```
for D in range(0, 100, 10):
v = firm_value(E=100-D, D=D, rA=.05, rD=.04, tC=.35, FCF=2, g=.01)
print(D, v)
```

```
0 50.0
10 52.287581699346404
20 54.7945205479452
30 57.55395683453237
40 60.6060606060606
50 64.0
60 67.79661016949152
70 72.07207207207207
80 76.92307692307692
90 82.4742268041237
```

Think carefully about why `range(0,100,10)`

gives values [0, 10, 20, …, 80, 90] and not values [0, 10, 20, …, 90, 100].