Lesson 08 - For Loops

The following topics are discussed in this notebook:

  • Looping over ranges
  • Using loops with lists
  • Looping over lists

Loops

A loop is a tool that allows us to instruct Python to repeat a task several times, usually with slight variations each time. We will consider two types of loops in this course: for loops and while loops.

We will begin with for loops, but before we can do so, we need to introduce the concept of a range.

Ranges

The range(a, b) function generates a list of integers beginning with a and continuing up to, but NOT INCLUDING b.

A range is not a list, but can be converted into a list.

In [1]:
my_range = range(3,8)
print(type(my_range))
print(my_range)
print(list(my_range))
<class 'range'>
range(3, 8)
[3, 4, 5, 6, 7]

For Loops

We can use for loops to ask Python to repeat a task a certain number of times. The syntax for constructing a for loop is as follows:

for i in range(a,b): 
    # Body of for loop

This code asks Python to execute the body of the for loop 5 times - once for each integer in the specified range. The variable i is referred to as a loop counter. The counter will have a value of a during the first iteration of the loop, and will increment at the start of each new iteration.

In [2]:
for i in range(0,5):
    print('The value of i is', i)
The value of i is 0
The value of i is 1
The value of i is 2
The value of i is 3
The value of i is 4

The code in the cell above is equivalent to that in cell below. Notice that using a for loop for this task is much more concise.

In [3]:
i = 0
print('The value of i is', i)
i = 1
print('The value of i is', i)
i = 2
print('The value of i is', i)
i = 3
print('The value of i is', i)
i = 4
print('The value of i is', i)
The value of i is 0
The value of i is 1
The value of i is 2
The value of i is 3
The value of i is 4

Note the following five points regarding for loops:

  1. We can use any variable name that we wish for the loop counter.
  2. We can use the counter in arithmetic operations, or as a list index.
  3. The range over which we are looping does not have to begin with 0.
  4. We can include multiple lines of code within a for loop.
  5. Each line in the body of a loop must be indented one level beyond the for statement.

These ideas are all illustrated in the following example, which prints the squares of the first 10 positive integers.

In [4]:
for n in range(1,11):
    message = "The square of " + str(n) + " is " + str(n**2) + "."
    print(message)
The square of 1 is 1.
The square of 2 is 4.
The square of 3 is 9.
The square of 4 is 16.
The square of 5 is 25.
The square of 6 is 36.
The square of 7 is 49.
The square of 8 is 64.
The square of 9 is 81.
The square of 10 is 100.

We can also use loops to alter variables that are defined outside of the loop. This is useful for calculating running totals. The code in the cell below calculates the sum of the squares of the first 100 integers. That is, it calculates $1^2 + 2^2 + 3^2 + ... + 99^2 + 100^2$.

In [5]:
# Sum the first 100 positive integers
total = 0
for i in range(1,101):
    total += i**2

print(total)
338350

Using Loops to Work with Lists

One common use of a loop is to perform an action on every element of a list. In this case, we typically use the loop counter as an index for elements in the list.

The list below contains 30 integer values, several of which are negative.

In [6]:
big_list = [ 17, -72,  97, -18, 32, -15, -63, -57,  40,  83,
            -48,  26,  12, -62, 16,  49,  55, -77, -30,  92,
             34, -29, -75,  13, 40, -85,  62, -74, -69, -31]

Assume that we wish to sum the absolute values of the numbers in big_list. We can do so using a for loop and the abs() value function.

In [7]:
total = 0
for i in range(0, len(big_list)):
    total += abs(big_list[i])
    
print(total)
1473

There are occasions when we might want to create a new list based on the values in one or more already existing lists. Depending on the task, we might be able to accomplish it by starting with an empty list, and then appending elements to it as we loop over another list.

As an example, assume that we have been provided with two equally sized lists, listA and listB, as follows:

In [8]:
listA = [13, 18, 23, 42, 27, 22, 36, 17, 44, 34, 35, 33, 41, 43, 12, 45, 29, 37, 14, 15]

listB = [42, 14, 31, 39, 40, 48, 47, 36, 28, 32, 11, 27, 16, 17, 34, 33, 46, 22, 26, 15]

Assume that we need to create a new list, listC, that has the same number of elements as listA and listB, such that an particular element in listC is the sum of the numbers with the same index in listA and listB. The cell below includes code for accomplishing such a task.

In [9]:
listC = []

M = min(len(listA), len(listB))

for i in range(0, M):
    temp = listA[i] + listB[i]
    listC.append(temp)
    
print(listC)
[55, 32, 54, 81, 67, 70, 83, 53, 72, 66, 46, 60, 57, 60, 46, 78, 75, 59, 40, 30]

Alternate Method for Looping over Lists

In the for loops that we have seen so far, we have specifed a range, and then a counter that iterated through all of the values in that range. Python provides us with another technique for looping over a list. If we replace the range in our loop statement with a list, then the loop counter will iterate over items in the list, rather than elements in a range.

For an example, consider the following three cells.

In [10]:
SW = ['the phantom menace', 'attack of the clones', 'revenge of the sith',
      'a new hope', 'the empire strikes back','return of the jedi',
      'the force awakens']

The cell below loops over the list SW, printing each element. During the first iteration of the loop, the counter movie will be equal to the first element of SW, during the second iteration, movie will be equal to the second element, and so on.

In [11]:
for movie in SW:
    print(movie)
the phantom menace
attack of the clones
revenge of the sith
a new hope
the empire strikes back
return of the jedi
the force awakens
In [12]:
for movie in SW:
    print(movie.title())
The Phantom Menace
Attack Of The Clones
Revenge Of The Sith
A New Hope
The Empire Strikes Back
Return Of The Jedi
The Force Awakens

It should be noted that this method of looping does not provide any new functionality. In fact, losing access to the index actually limits the applications that we can use a loop for. However, looping over a list directly can be a convenient shortcut for reading the elements of a list when we don't need to make any changes to the list and don't need care about the indices of the elements in the list.

Nested Loops

For some complex tasks, it is necessary to include loops inside of other loops. This is called a nested loop. The simplest case of a nested loops is when we have one loop that itself contains another loop. In this case, we call the first loop that Python encounters the outer loop, and the loop inside of that is called the inner loop. Each time the outer loop runs, the entire inner loop will process, running through all of it's possible iterations.

To get an introduction to how nested loops work, consider the following example.

In [13]:
for i in range(0, 6):
    
    for j in range(0, 3):
        
        print("i is equal to " + str(i) + "; j is equal to " + str(j))
        
    print()
i is equal to 0; j is equal to 0
i is equal to 0; j is equal to 1
i is equal to 0; j is equal to 2

i is equal to 1; j is equal to 0
i is equal to 1; j is equal to 1
i is equal to 1; j is equal to 2

i is equal to 2; j is equal to 0
i is equal to 2; j is equal to 1
i is equal to 2; j is equal to 2

i is equal to 3; j is equal to 0
i is equal to 3; j is equal to 1
i is equal to 3; j is equal to 2

i is equal to 4; j is equal to 0
i is equal to 4; j is equal to 1
i is equal to 4; j is equal to 2

i is equal to 5; j is equal to 0
i is equal to 5; j is equal to 1
i is equal to 5; j is equal to 2

Example: Matrix Addition

The following cell contains a list of lists, named A. This list is intended to represent a 3x5 matrix. Each of the three lists inside A represents a single row with 5 elements.

In [14]:
A = [ [11,12,13,14,15], [16,17,18,19,20], [21,22,23,24,25]  ]

The cell below prints the rows of A one at a time. This output will more closely resemble a matrix.

In [15]:
for row in A:
    print(row)
[11, 12, 13, 14, 15]
[16, 17, 18, 19, 20]
[21, 22, 23, 24, 25]

Assume that we wish to create a new list called ASquare. This list should also represent a 3x5 matrix, and each element of ASquare should be the square of the corresponding element of A. This will require two loops. The first loop will loop over the rows of A. The second loop will loop over the elements of a given row.

In [16]:
ASquare = []

for i in range(0,len(A)):
    
    tempRow = []    
    for j in range(0,len(A[i])):
        tempRow.append(A[i][j] ** 2)
        
    ASquare.append(tempRow)

Notice that we build up each row in ASquare one element at a time, and when we are finished with a row, we add that to ASquare. We will now print the rows of ASquare.

In [17]:
for row in ASquare:
    print(row)
[121, 144, 169, 196, 225]
[256, 289, 324, 361, 400]
[441, 484, 529, 576, 625]

Example: Sales Information

Assume that a company called WidgCo manufactures and sells many different varieties of widgets. In the cell below, four lists are created to contain information about WidgCo's annual sales.

  • prodID is a list that contains the product ID for each variety of widget produced.
  • units is a list containing the number of widgets sold of each type during the previous year.
  • unit_price is a list containing the price per widget for which WidgCo sells each widget type.
  • unit_cost is a list containing the cost to WidgCo for producint a single widget of each type.
In [18]:
import random
random.seed(37)
n = random.choice(range(200,400))

prodID = list(range(101, 101+n))
units = random.choices(range(100,300), k=n)
unit_price = random.choices(range(20,50), k=n)
unit_cost = random.choices(range(5,10), k=n)

The lists in the cell above are generated at random, and have an unknown size. However, each list is the same size, and for any given index i, the elements of the four lists at that particular index will all refer to the same type of widget.

In the cell below, we print the information for the first five product types. We will print the information in a tabular format.

In [19]:
print('prodID\t units\t price\t cost')
print('-----------------------------')
for i in range(0,5):
    print(prodID[i], '\t', units[i], '\t', 
          unit_price[i], '\t', unit_cost[i])
prodID	 units	 price	 cost
-----------------------------
101 	 221 	 42 	 6
102 	 287 	 33 	 9
103 	 233 	 48 	 5
104 	 249 	 23 	 9
105 	 226 	 44 	 6

We now create three new lists called revenue, cost, and profit.

  • revenue will contain the total annual revenue generated by each type of widget.
  • cost will contain the total annual costs incurred by producing each type of widget.
  • profit will contain the total annual profit generated by each type of widget.
In [20]:
revenue = []
cost = []
profit = []

for i in range(0, len(prodID)):
    revenue.append( units[i] * unit_price[i])
    cost.append( units[i] * unit_cost[i] )
    profit.append( revenue[i] - cost[i] )

In the cell below, we calculate the total annual revenue, total annual cost, and total annual profit for WidCo.

In [21]:
total_revenue = sum(revenue)
total_cost = sum(cost)
total_profit = sum(profit)

print('Revenue:', total_revenue)
print('Cost:   ', total_cost)
print('Profit: ', total_profit)
Revenue: 2523165
Cost:    524894
Profit:  1998271

Assume that we are asked to determine which product generated the greatest profit during the previous year. We can accomplish that using the max() function and the index() method.

In [22]:
max_profit = max(profit)
idx = profit.index(max_profit)

print('prodID:    ', prodID[idx])
print('units:     ', units[idx])
print('unit_price:', unit_price[idx])
print('unit_cost: ', unit_cost[idx])
print('profit:    ', profit[idx])
prodID:     299
units:      279
unit_price: 48
unit_cost:  5
profit:     11997

List Comprehensions

Python creates a shortcut for iteratively creating lists. This shortcut is called a list comprehension. We know that we can create a list iteratively using a for loop as follows:

my_list = []
for i in range(a,b):
    value = whatever
    my_list.append(value)

The same result can be obtained in a more compact fashion using a list comprehension as follows:

my_list = [value for i in range(a,b)]

Lets see a few examples. In the first example, we will create a list containing the square of all elements in a range.

In [23]:
sq_list = [n**2 for n in range(1,10)]
print(sq_list)
[1, 4, 9, 16, 25, 36, 49, 64, 81]

In a previous example, we were given lists listA and listB, and were asked to create a list listC that contained an elementwise sum of the listA and listB. Let's see how this could be accomplished with a list comprehension.

In [24]:
listA = [13, 18, 23, 42, 27, 22, 36, 17, 44, 34, 35, 33, 41, 43, 12, 45, 29, 37, 14, 15]

listB = [42, 14, 31, 39, 40, 48, 47, 36, 28, 32, 11, 27, 16, 17, 34, 33, 46, 22, 26, 15]
In [25]:
listC = [listA[i] + listB[i] for i in range(0, len(listA))]

print(listC)
[55, 32, 54, 81, 67, 70, 83, 53, 72, 66, 46, 60, 57, 60, 46, 78, 75, 59, 40, 30]