6. Python Code Blocks: Conditional and Loop Statements#

6.1. Lesson overview#

A Python code block is a section of code that runs from top to bottom, straight on through. So far all coding examples have run from top to bottom uninterrupted, so each example can be thought of as one code block:

a = 1 + 4                         ### Start code block
print('a is', a)                    #
equal_five = (a == 5)               #
print('a equals 5?', equal_five)  ### End code block
a is 5
a equals 5? True

Hey! Listen!

You can use the hashtag character # in Python to create a comment in your Python code. Everything after the # is ignored by the interpreter, so you can use it to put in helpful hints to understand your code, or to make notes for yourself and others.

Definition: code block

A section of code that runs from top to bottom, straight on through.

But what if we want to run one section of code if a condition is met, like when a == 5, and a different section of code if the condition is not met, like a != 5? That type of branching logic will mean not all the code will run, instead only certain blocks of code will run depending on the conditional. In this lesson we will go over how to create a code block in Python, and then how to use the conditional keywords (if, elif, and else), and looping keywords (for and while) to create code block statements.

6.2. Creating a code block#

So how do we create a code block in Python? We can use this code block as a working example:

print('Iterating over a list')
for i in [0,1,2,3]:
    times_five = i * 5
    print("i is ", i)                     
    print("5 times i:", times_five)     
                                        
print("End looping")                 
  
Iterating over a list
i is  0
5 times i: 0
i is  1
5 times i: 5
i is  2
5 times i: 10
i is  3
5 times i: 15
End looping

The command for i in [0,1,2,3]: starts the code block, which is a Python statement. The statement begins with a Python keyword, in this example we are using thefor keyword to create a for loop statement that iterates over a list. We will go over for and while loops in detail later in the lesson. We end the Python statement line with the colon character :.

Definition: statement

A command that the Python interpreter can read and execute.

Definition: keyword

A reserved word in Python that provides special functionality.

After the for statement, the following three lines are part of the new code block which is designated by the indented lines of code. This indent represents four SPACEBAR keystrokes, which is the standard length for each indentation level. When we want to complete the code block, we simply un-indent to match the previous code block and continue writing code. In this example, we un-indent and type print("End looping") back in the first code block. Below is the previous example now with code blocks labeled in the comments:

print("Start looping over a list")   #### Start code block 1
print("Here we go!")                    # 1
for i in [0,1,2,3]:                     #### Start code block 2
    times_five = i * 5                     # 2
    print("i is ", i)                      # 2
    print("5 times i:", times_five)     #### End code block 2
                                        # 1
print("End looping")                 #### End code block 1
Start looping over a list
Here we go!
i is  0
5 times i: 0
i is  1
5 times i: 5
i is  2
5 times i: 10
i is  3
5 times i: 15
End looping

Hey! Listen!

We (and the Python Software Foundation!) recommend you use four spaces using the SPACEBAR key to indent codeblocks. While you can use any number of spaces or the TAB key, four spaces is a solid style convention guideline. Your IDE, like Spyder, can set TAB to output four spaces which is a useful convenience.

It is crucial that you are consistent with the indentation in a code block, or else you will get errors. Below is an example of a code block without proper indentation:

print('Iterating over list')
for i in [0,4,2,9]:
print(i)  
  Input In [4]
    print(i)
    ^
IndentationError: expected an indented block after 'for' statement on line 2

The error message IndentationError: expected an indented block after 'for' statement on line 2 is reporting that we forgot to indent the code block after the initial statement. Too much indenting is also a problem, as highlighted in the code below:

print('Iterating over list')
for i in [0,4,2,9]:
    print(i)
        print(i*5)
  Input In [5]
    print(i*5)
    ^
IndentationError: unexpected indent

The error message IndentationError: unexpected indent points to the fact that we have not uniformly indented the code block. There are four extra spaces for      print(i*5). Indentation needs to be consistent throughout the code block.

6.3. if and else conditional code blocks#

The commands if and else are common and important keywords for code blocks starting with conditional statements. In this section we will learn when and how to use these important keywords.

6.3.1. The if keyword#

The if keyword is used to create a conditional statement where, if the statement is True, then it will proceed to the code block that follows. You can use any number of boolean operators to create your conditional statement.

Let us change our example code so that if a is greater than or equal to five, we multiply it by 10. Below is our code now modified with these changes:

a = 1 + 4                    #### Start code block 1
print('a is', a)                # 1
equal_five = (a == 5)           # 1
print('Equal 5?', equal_five)   # 1
if a >= 5:                      #### Start code block 2
    print('a >= 5')                # 2
    a = a * 10                  #### End code block 2
                                # 1
print('a is', a)             #### End code block 1
a is 5
Equal 5? True
a >= 5
a is 50

The if keyword starts a conditional statement to check if a is greater than or equal to five. If (a >= 5) is True, then we proceed to code block 2, which reassigns a to be itself multiplied by 10. However, if (a >= 5) is False, then we just bypass the if code block and print out the result. Finally, we return to code block 1 where we print out the value of a.

6.3.2. The else keyword#

What if we want to divide a by 10 when a is not greater than or equal to five? To capture these additional cases, we can use the else keyword to direct the code to an alternative code block. The code below incorporates the else keyword to include our desired change:

a = 1 + 2                    #### Start code block 1
print('a is', a)                # 1
equal_five = (a == 5)           # 1
print('Equal 5?', equal_five)   # 1
if a >= 5:                      #### Start code block 2
    print('a >= 5')                # 2
    a = a * 10                  #### End code block 2
else:                           #### Start code block 3
    print('a < 5')                 # 3    
    a = a / 10                  #### End code block 3                    
print('a is', a)             #### End code block 1
a is 3
Equal 5? False
a < 5
a is 0.3

Here, the else keyword catches all cases when the if clause is False. The flow of the program does not enter into code block 2, but is instead directed towards code block 3, which reassigns a to be itself divided by 10.

6.3.3. The elif (“else if”) keyword#

What if we want our code to catch several conditionals? For instance, imagine we want our code to do something if a is greater than 5, something else if a is between 3 and 5, and again something entirely different for the remaining cases?

Python can handle the need for multiple conditionals using the elif keyword (pronounced: “else if”). This means we can have another conditional statement to check if the initial conditional statements are False. You can chain as many elif conditional code blocks as you would like.

An important note when chaining multiple if and elif statements, the first condition that evaluates True will be the one that is executed. This is highlighted in the example below:

a = 3
print('If a is 3')
if a == 3:
    print('This code block will executed')
elif a > 1:
    print('This code block will not be executed if a == 3, even if a > 1, because it is evaluated second')
    print('But this will output if a == 2.')
If a is 3
This code block will executed

In the above example, technically both the if and the elif conditionals should return True, but since the if conditional is the first conditional evaluated True, its code block is executed.

Now knowing how elif works, we can now combine this conditional with our knowledge on if and else conditionals to make a multiple conditional program. This is demonstrated in the code below:

a = 1 + 2                    #### Start code block 1
print('a is', a)                # 1
equal_five = (a == 5)           # 1
print('Equal 5?', equal_five)   # 1
if a >= 5:                      #### Start code block 2
    print('a >= 5')                # 2
    a = a * 10                  #### End code block 2
elif a > 3 and a < 5:           #### Start code block 3
    print('3 < a < 5')             # 3
    a = a - 10                  #### End code block 3  
else:                           #### Start code block 4
    print('a <= 3')                # 4    
    a = a / 10                  #### End code block 4                    
print('a is', a)             #### End code block 1
a is 3
Equal 5? False
a <= 3
a is 0.3

Notice that in the above example the elif statement includes a conditional statement with two clauses: elif a > 3 and a < 5:. A conditional statement can be made up of several conditional clauses, in this case a > 3 and a < 5, and is a useful programming technique when implementing more complex conditional statements. This is often done using the and and or conditional keywords, which we will discuss in the next section.

Hey! Listen!

When comparing a variable to a range of numbers, you can also write the conditional statement as:

3 < a < 5

This is equivalent to the a > 3 and a < 5 format used in the above example.

6.3.4. The and and or keywords#

A conditional statement can be constructed with any number of conditional clauses chained together using the keywords and and or. For example, the and keyword will evaluate to True if and only if all clauses evaluate to True. The code below demonstrates how and is used to chain two conditionals together:

a = True
b = True
print("a: ", a, "b: ", b)
if a and b:
    print('a and b are both True')
else:
    print('Either a or b, or both, are False')

a = False
b = True
print("a: ", a, "b: ", b)
if a and b:
    print('a and b are both True')
else:
    print('Either a or b, or both, are False')
a:  True b:  True
a and b are both True
a:  False b:  True
Either a or b, or both, are False

The or keyword will evaluate to True if one or more clauses evaluate to True. It will evaluate False only if all clauses are False. The code below demonstrates how or is used to chain two conditionals together:

a = True
b = False
print("a:", a, "b:", b)
if a or b:
    print('One or both of a and b are True')
else:
    print('Both a and b are False')

a = False
b = False
print("a:", a, "b:", b)
if a or b:
    print('One or both of a and b are True')
else:
    print('Both a and b are False')
a: True b: False
One or both of a and b are True
a: False b: False
Both a and b are False

As noted, you can chain these keywords together to evaluate multiple conditional clauses like in the example below:

if True and False and True and False or True:
    print("True?")
    
True?

When chaining together conditional clauses it is useful to use the parentheses symbols ( and ) to break down the logic into manageable chunks. Parentheses will help the readability when trying to parse a long statement like:

if ( (True and False) and (True and False) ) or True:
    print("True because of 'or', much easier to understand")
    
True because of 'or', much easier to understand

6.3.5. Example: Let there be light#

The light that we see is part of the electromagnetic spectrum known as “visible light”. This visible part of the electromagnetic spectrum consists of light that has vacuum wavelengths, \(\lambda\), between 380 nm to 700 nm. This range can be further divided up into the primary spectral colors:

  • Violet: 380 nm \(\le\) \(\lambda\) \(\lt\) 450 nm

  • Blue: 450 nm \(\lt\) \(\lambda\) \(\le\) 500 nm

  • Green: 500 nm \(\lt\) \(\lambda\) \(\le\) 565 nm

  • Yellow: 565 nm \(\lt\) \(\lambda\) \(\le\) 590 nm

  • Orange: 590 nm \(\lt\) \(\lambda\) \(\le\) 625 nm

  • Red: 625 nm \(\lt\) \(\lambda\) \(\le\) 700 nm

Using what you know about conditional code blocks, create a small Python code that prints out the color of a user defined wavelength. For example, if we entered 600 nm as our wavelength, the Python code would output the string 600 nm is orange light!. If the wavelength lies outside the 380 nm to 700 nm range, have the code output the string X nm is outside the visible spectrum!, where X is the wavelength.


Solution:

We can create the necessary code by using an initial if keyword, a sequence of elif keywords, and an ending else statement. Since each color is bounded by a minimum and maximum wavelength, the and keyword allows us to check each color range. There are a few different ways this code can be created depending on how you use greater than or less than operators (see our previous discussion on the bool logic class for details). One example of implementing this program is shown below:

wavelength = 600 

if (wavelength >= 380) and (wavelength < 450):
    print(f"{wavelength} nm is purple light!")
elif (wavelength >= 450) and (wavelength < 500):
    print(f"{wavelength} nm is blue light!")
elif (wavelength >= 500) and (wavelength < 565):
    print(f"{wavelength} nm is green light!")
elif (wavelength >= 565) and (wavelength < 590):
    print(f"{wavelength} nm is yellow light!")
elif (wavelength >= 590) and (wavelength < 625):
    print(f"{wavelength} nm is orange light!")
elif (wavelength >= 625) and (wavelength < 700):
    print(f"{wavelength} nm is red light!")
else:
    print(f"{wavelength} nm is outside the visible spectrum!")
600 nm is orange light!

6.4. The for and while looping code blocks#

With conditional statements we were able to choose which code block to be executed. The Python keywordsfor and while, on the other hand, will allow us to create looping statements in which can iterate a block of code multiple times until a condition is met.

6.4.1. The for loop#

The for keyword iterates over a sequence, until it reaches the end of the sequence. The code below demonstrates this using a list object:

print('Iterating over list') #### Start code block 1
print('[0,1,2,3]')              # 1
for i in [0,1,2,3]:             #### Start code block 2
  print(i)                      #### End code 2
print('done looping')        #### End code block 1
Iterating over list
[0,1,2,3]
0
1
2
3
done looping

The looping code block starts with the for keyword, which is followed by i in [0,1,2,3]:. The statement for i in [0,1,2,3]: says two things. First, that for each element in the list [0,1,2,3], we will run the code block that follows this statement. Right away we can tell the looping code block will run four times because the list has four elements. Second, when running the looping code block, the variable i will be assigned the value of the current element of the list. So for the first run of the looping code block i = 0 and then next loop i = 1, etc.

It is possible to have loops nested in other loops. The example below shows how “nested” for loops can be used to loop over two ranges:

print('Iterating two ranges') #### Start code block 1
for i in range(4):               #### Start code block 2
                                    # 2
    print('i', i)                   # 2
                                    # 2
    for b in range(3):              #### Start code block 3
                                       # 3
        print('b', b)               #### End of code block 3      
    print('looping')                # 2
                                 #### End of code block 2
print('finished')             #### End of code block 1
Iterating two ranges
i 0
b 0
b 1
b 2
looping
i 1
b 0
b 1
b 2
looping
i 2
b 0
b 1
b 2
looping
i 3
b 0
b 1
b 2
looping
finished

From the output of the previous cell, you can see the sub loop for b in range(3): runs its code block entirely for each loop of the parent loop for i in range(4):. When creating multiple loops it is important to keep in mind the indentation. Additionally, it is important to use different variables for the inner loops. Using the same iterator variable can create bugs in your code, as the variable is being modified by two different loops.

A very useful function that can be used with for loops is the enumerate builtin function. The code below demonstrates how enumerate can be used over a list object:

print('Iterating over enumerated list')
for idx, i in enumerate([23,11,2,5]):
    print('index:', idx)
    print('value:', i)
Iterating over enumerated list
index: 0
value: 23
index: 1
value: 11
index: 2
value: 2
index: 3
value: 5

Our for loop statement has changed from the typical for i in [list] pattern to the enumeration pattern for idx, i in enumerate(list). The enumerate() function will take in a sequence and return two values at each step, the index of the current element and the element itself. The for idx, i in part of our statement means we are assigning the current element index to idx and the element value to i. So, when running the first loop of the code block we’ll have the variables idx = 0 and i = 23, and the second loop we’ll have idx = 1 and i = 11. With enumerate() we can use the index of the current element in many ways, for example we can use the idx on another list to get an element at the same index, and even reassign the element at the current list index:

list_a = [23,11,2,5]
list_b = [4,2,3,5]
print('Iterating over enumerated list:', list_a)
for idx, i in enumerate(list_a):
    # multiply current element with element in list_b at same index   
    # and assign value back to list_a at same index
    list_a[idx] = i * list_b[idx]
print('list_a:', list_a)
Iterating over enumerated list: [23, 11, 2, 5]
list_a: [92, 22, 6, 25]

If we were to iterate over a dictionary object, we would need to slightly change our approach:

list_a = ['a', 'b', 'c', 'd']
list_b = [4,2,3,5]

joined_dict = dict(zip(list_a, list_b))

print('Iterating over joined_dict:', joined_dict)
for i in joined_dict:
    print(i)
Iterating over joined_dict: {'a': 4, 'b': 2, 'c': 3, 'd': 5}
a
b
c
d

In this example we first create a dictionary by using the builtin zip() function to join list_a and list_b together as dict object. However, if try to iterate over the dict object, we see that only the keys are listed and not the values. If we want to iterate through the values, we can do so in a few ways. One way is to access our dict object by passing in the keys:

for i in joined_dict:
    print('key:', i, 'value:', joined_dict[i])
key: a value: 4
key: b value: 2
key: c value: 3
key: d value: 5

Or we can use the builtin .values() method that is available to dictionary objects:

for i in joined_dict.values():
    print('value:', i)
value: 4
value: 2
value: 3
value: 5

But perhaps more useful is the builtin method .items() that is available for dict objects. Again, do not worry about the details involved with using methods at this time as we will visit this concept in our object-oriented programming lesson. The code below demonstrates how to use a for loop with a dict object’s .items() method to display both keys and values:

for k, v in joined_dict.items():
    print('key:', k, 'value:', v)
key: a value: 4
key: b value: 2
key: c value: 3
key: d value: 5

The .items() method provides a set of tuples that can be iterated over in the form of (key, value). Therefore, we use two variables k and v to represent the key and value in the statement for k, v in joined_dict.items().

6.4.2. The while loop#

Using while loops are similar to using for loops, but instead of looping over elements in a sequence, we continue to loop as long as a conditional statement remains True:

print('Iterating over list')
counter = 0
while counter < 5:
  print(counter)
  counter = counter + 1
Iterating over list
0
1
2
3
4

The looping code block starts with the while keyword followed by the conditional statement counter < 5. This says that the following code block will continue looping until the conditional statement is False. If the conditional statement never becomes False, the while loop will run forever. When that happens, remember that you can stop or restart the IPython kernel in JupyterLab following steps detailed earlier.

6.4.3. Example: Bundling light#

Using the example from earlier as a starting point, create a program that reports the color of each wavelength in a list. The list should contain the following wavelengths:

300 nm, 390 nm, 400 nm, 535 nm, 600 nm, 732 nm, and 1000 nm

Create two versions of this program: one that uses a for loop and one that uses a while loop.


Solution:

We can adapt the previous example’s solution for our needs by inserting it as a code block that is embedded in a for loop or while loop. The for loop example is shown below with subsequent discussion:

wavelength = [300, 390, 400, 501, 535, 600, 732, 1000]


for i in wavelength:
    if (i >= 380) and (i < 450):
        print(f"{i} nm is purple light!")
    elif (i >= 450) and (i < 500):
        print(f"{i} nm is blue light!")
    elif (i >= 500) and (i < 565):
        print(f"{i} nm is green light!")
    elif (i >= 565) and (i < 590):
        print(f"{i} nm is yellow light!")
    elif (i >= 590) and (i < 625):
        print(f"{i} nm is orange light!")
    elif (i >= 625) and (i < 700):
        print(f"{i} nm is red light!")
    else:
        print(f"{i} nm is outside the visible spectrum!")
300 nm is outside the visible spectrum!
390 nm is purple light!
400 nm is purple light!
501 nm is green light!
535 nm is green light!
600 nm is orange light!
732 nm is outside the visible spectrum!
1000 nm is outside the visible spectrum!

Here, we first change wavelength to now be a list and then embed the previous example’s solution inside a for loop. Notice that we have to tab indent the if, elif, and else lines of code to have this work. In addition, we introduce the temporary variable i that represents each value in wavelength. This is why i is substituted into each conditional statement.

An equivalent program using the while keyword is shown below. In this version, we use i as the index number for wavelength and set up the loop to run when i is less than the “length” of wavelength (i.e., its size / number of entries). We use the built-in function len() it get the size of wavelength. We enter the line i = 0 before the start of the while statement to ensure that our loops begin at the first index position of wavelength (i.e., the zeroth position), and we include the i = i + 1 code at the end of the while block to increment along each entry.

wavelength = [300, 390, 400, 501, 535, 600, 732, 1000]

i = 0

while i < len(wavelength):
    if (wavelength[i] >= 380) and (wavelength[i] < 450):
        print(f"{wavelength[i]} nm is purple light!")
    elif (wavelength[i] >= 450) and (wavelength[i] < 500):
        print(f"{wavelength[i]} nm is blue light!")
    elif (wavelength[i] >= 500) and (wavelength[i] < 565):
        print(f"{wavelength[i]} nm is green light!")
    elif (wavelength[i] >= 565) and (wavelength[i] < 590):
        print(f"{wavelength[i]} nm is yellow light!")
    elif (wavelength[i] >= 590) and (wavelength[i] < 625):
        print(f"{wavelength[i]} nm is orange light!")
    elif (wavelength[i] >= 625) and (wavelength[i] < 700):
        print(f"{wavelength[i]} nm is red light!")
    else:
        print(f"{wavelength[i]} nm is outside the visible spectrum!")
    i += 1
300 nm is outside the visible spectrum!
390 nm is purple light!
400 nm is purple light!
501 nm is green light!
535 nm is green light!
600 nm is orange light!
732 nm is outside the visible spectrum!
1000 nm is outside the visible spectrum!

6.4.4. The break and continue keywords#

When working with looping code blocks, it can be useful to break out of an iteration cycle or an entire loop. The break keyword will stop running the current looping code block and return to the parent code block. The code below demonstrates how the break keyword works:

print('Breaking out at 3')
counter = 0
while counter < 5:
  if counter == 3:
      break
  print(counter)
  counter += 1
Breaking out at 3
0
1
2

Instead of breaking out of the entire loop, the continue keyword will skip the current iteration of the looping code block and start on the next one:

print('Skipping 3')
counter = 0
while counter < 5:
  counter += 1
  if counter == 3:
      continue  
  print(counter)
Skipping 3
1
2
4
5

Hey! Listen!

What do you think would happen if the counter += 1 line in the while loop above was placed after the continue keyword? Try it out, but do not forget about the stop button to interrupt the kernel.

All in all, both continue and break are useful keywords to use in for or while looping code blocks.

6.5. List comprehension#

List comprehension is a powerful coding pattern when working with list objects. List comprehension allows you to “comprehend” a list by iterating over it, doing some operation on each element, and returning a new list as an output, all in one line. To demonstrate this, the example below first uses a for loop code block to create a list called list_b whose values are four times of the start list list_a. We then repeat this process to create list_c, but now use list comprehension to achieve this in one line of code.

list_a = [0, 1, 2, 3, 4]

# Multiply each element in list_a by 4 and put into a new list

# for loop implementation:
list_b = []
for i in list_a:
    list_b.append(i*4)

# List comprehension implementation:
list_c = [i*4 for i in list_a]

print('list_b:', list_b)
print('list_c:', list_c)
list_b: [0, 4, 8, 12, 16]
list_c: [0, 4, 8, 12, 16]

Our list comprehension statement, list_c = [i*4 for i in list_a], does all the work of the for loop in one line. In this statement, we are saying to multiply each element by 4 (i*4) for each element i in list_a and return the results as a new list. We can also pair conditional statements at the end of a list comprehension statement to run an if conditional on each element:

list_a = [0, 1, 2, 3, 4]

# Create new list of even numbers in list_a
evens = [i for i in list_a if i % 2 == 0]

print('Evens in list_a:', evens)
Evens in list_a: [0, 2, 4]

As seen above, we have added a short conditional clause if i % 2 ==0 at the end of our list comprehension statement. This conditional clause checks if the modulus of the element i and 2 is equal to 0, and if the clause evaluates to True, then that element is included in the new list.

As you might guess, list comprehension and enumeration both work with dictionaries as well:

dict_a = {'a' : 0, 'b' : 1, 'c' : 2, 'd' : 3, 'e' : 4}

# Create new list of even numbers in dict_a
evens = {key:value for key, value in dict_a.items() if value % 2 == 0}

print('Evens in dict_a:', evens)
Evens in dict_a: {'a': 0, 'c': 2, 'e': 4}

In this example we are using the .items() method to iterate through dict_a in a set of the (key,value). We can filter the value with some criteria using an if statement (here we are looking for values that are even), and then we can return the key:value for those values.

6.6. Matrix operations using conditional and looping statements#

A common application of conditional and looping statements in scientific Python is to iteratively access and operate on objects inside of lists and tuples. Rather than manually writing out each indexed value in a block of code, we can automate the process using these newly learned statements.

Let’s revisit an earlier topic about using lists to represent matrices. While we demonstrated a way to represent matrices using nested lists, we soon found out that matrix operations like matrix addition do not directly translate over to lists. However, now with the knowledge of conditional and looping statements, we can write a code block to perform this mathematical operation!

As a review, recall that matrix addition is a cell-by-cell operation across two matrices that have the same dimensions. For a set of 3 x 2 matrices, this can be symbolically represented as:

\[\begin{split} \pmatrix{a_{11} & a_{12} \\ a_{21} & a_{22} \\ a_{31} & a_{32}} + \pmatrix{b_{11} & b_{12} \\ b_{21} & b_{22} \\ b_{31} & b_{32}} = \pmatrix{a_{11} + b_{11} & a_{12} + b_{12} \\ a_{21} + b_{21} & a_{22} + b_{22} \\ a_{31} + b_{31} & a_{32} + b_{32}} \end{split}\]

and simple numerical example is shown below for completeness:

\[\begin{split} \pmatrix{1 & 2 \\ 3 & 4 \\ 5 & 6} + \pmatrix{0 & 3 \\ 5 & 7 \\ 2 & 1} = \pmatrix{1 & 5 \\ 8 & 11 \\ 7 & 7} \end{split}\]

The cell-by-cell iterative operation lends itself to the use of for loops, specifically a nested for loop structure in which we iterate over each row and column. The code below demonstrates how matrix addition can be performed using conditional and looping statements on nested lists:

Even though there is 15 effective lines of code, there is a lot to take in here. Comments have been added for clarification, but let’s go over some important code blocks.

The first few lines of code,

# initialize variables
a = [[1, 2], [3, 4], [5, 6]]
b = [[0, 3], [5, 7], [2, 1]]
c = [] # summed matrix

initialize the two matrices (represented by lists a and b) to be added and a final matrix c to store added values. Following the numerical matrix addition example from earlier, we should expect c to be equal to [[1, 5], [8, 11], [7, 7]].

The if-else statement is technically not needed in the addition process, but we have included it to verify that the dimensions of the two matrices are equal. There are two conditional checks in this if statement. The first check verifies that the number of rows between a and b match. This is verified with the len(a) == len(b) command. The second check verifies that the number of columns between a and b also match. This is verified with the len(a[0]) == len(b[0]) command.

It is important to note that Python allows you to create nested list structures with varying sub-list length. So our conditional statements here are not the most robust checks available, but for this example they will suffice.

These two conditional checks are linked with an and boolean operator to ensure that both checks must be true in order to allow for the addition operation. If not, the code block shifts to the else section and prints out an error message.

Now the actual looping process begins. The primary for loop iterates over all rows with the following block of code:

for row in range(len(a)):

Using the range() function based on the output of the len() function is VERY common in scientific Python code, and you will probably use this structure many times throughout your career. This structure ensures that we iterate over all rows in a (and subsequently b since they have the same dimensions). The variable row will be incremented in each for loop cycle.

We then initialize a temporary variable called summed_row that will store the cell-by-cell additions for each row before heading into the next for loop structure,

for col in range(len(a[0])): 

which iterates over all column positions in the matrices. The setup here is similar to the row iteration above but now col will be our incremented variable over each cycle, and we use len() on the first sub-list entry in a to get the number of columns.

The actual cell-by-cell addition occurs with the line,

value = a[row][col] + b[row][col]

where value is another temporary variable. Here you can directly see how the incrementing variables row and col are used to cycle through each cell. From there, we use the list method .append() to insert this summed value into the temporary list object summed_row. For our 3 x 2 matrices, the inner for loop on the column position occurs twice, so the length of summed_row will be 2.

Once the column-based for loop completes, we again used .append() but now on c to store our two summed values for each row. Therefore, we add the list object summed_row to c in each cycle of the main for loop (i.e., in a row-by-row basis). This allows us to build our nested list structure for c. For our current example, this process occurs over three iterations of the main for loop.

When the nested for loop structure is complete, we finally print out our results with the command,

print("Summed matrix:", c)  

and notice that we do get the correct answer!

As seen in this example, we can implement matrix operations using conditional and looping statements on nested lists. Reading nested code blocks can be difficult at times though, so while this code is serviceable, we prefer something more intuitive to implement when performing matrix operations. Thankfully, external libraries, like NumPy, exist that are specifically designed for numerical method analysis. As we will see in a later lesson about NumPy, many matrix operations are already coded into the library, so we can simply call these commands rather than writing out our own bespoke code blocks.

6.6.1. Example: Matrix multiplication#

Matrix multiplication is an important and common operation involving two matrices. This operation requires that the number of columns of the first matrix equals the number of rows of the second matrix. The resultant matrix’s (often referred to as the “matrix product”) size is based on the number of rows of the first matrix by the number of columns of the second matrix. In general, an m x n matrix multiplied by an n x p matrix results in a matrix product of size m x p.

The actual “multiplication” step consists of taking the dot product between rows and columns of the two starting matrices. While writing out a general formula for very large matrices is cumbersome, for smaller matrices it is manageable. For example, the matrix multiplication of a 2 x 3 matrix by a 3 x 2 matrix results in a 2 x 2 matrix with the following cells:

\[\begin{split} \pmatrix{d_{11} & d_{12} & d_{13} \\ d_{21} & d_{22} & d_{23}} \cdot \pmatrix{e_{11} & e_{12} \\ e_{21} & e_{22} \\ e_{31} & e_{32}} = \pmatrix{d_{11}e_{11} + d_{12}e_{21} + d_{13}e_{31} & d_{11}e_{12} + d_{12}e_{22} + d_{13}e_{32} \\ d_{21}e_{11} + d_{22}e_{21} + d_{23}e_{31} & d_{21}e_{12} + d_{22}e_{22} + d_{23}e_{32}} \end{split}\]

Using your knowledge on list representation of matrices, conditional statements, and looping statements, write a short Python code to perform matrix multiplication on the following two matrices:

\[\begin{split} \pmatrix{1 & 2 & 3 \\ 4 & 5 &6} \cdot \pmatrix{0 & 3 \\ 5 & 7 \\ 2 & 1} = \pmatrix{16 & 20 \\ 37 & 53} \end{split}\]

The matrix product as been provided to allow you to check your code.


Solution:

One way to solve this problem is to build matrix multiplication code block using the earlier matrix addition example as a starting point since nested for loops are needed to iterate overall all positions. An example solution is provided below:

# matrix multiplication example

# initialize variables
d = [[1, 2, 3], [4, 5, 6]]
e = [[0, 3], [5, 7], [2, 1]]
f = []  # multiplied matrix

if (len(d[0]) == len(e)):
    for row in range(len(d)):                         # loops over all rows in d
        matrix_product_row = []                       # temp variable to hold summed values for current row
        for col in range(len(e[0])):                  # loops over all columns in e
            dot_product = int()                       # dot product value for particular cell
            for pos in range(len(d[0])):
                dot_product += d[row][pos] * e[pos][col]
            matrix_product_row.append(dot_product)    # inserts value to end of matrix_product_row
        f.append(matrix_product_row)                  # inserts summed row to c
    print("Multiplied matrix:", f)
else:
    print("Inner dimensions do not match!")
Multiplied matrix: [[16, 20], [37, 53]]

The start of the code is similar to the matrix addition example in which variables are initialized and an if statement is used to check that the “inner dimensions” of the two matrices (i.e., the column size of the first matrix and the row size of the second matrix) match. This solution assumes that one is using list objects as matrices so each list’s sub-list dimension is consistent with all other sub-lists.

The need to perform a dot product of the first matrix’s row with the second matrix’s column requires an additional nested for loop. This is seen with the creation of the temporary variable dot_product and loop statement section:

for pos in range(len(d[0])):
    dot_product += d[row][pos] * e[pos][col]
matrix_product_row.append(dot_product) 

The dot product is then stored temporarily in the list matrix_product_row until all operations are done on the current row and then inserted into the matrix product f. In both cases the .append() method is used to insert these values at the end of each list.


6.7. Conclusion#

In this lesson, we covered many of the fundamental concepts that are needed to create more complex Python programs. First, we discussed how code blocks create the programmatic flow of a Python program, and how the use indentation allow us to identify different and nested code blocks. We then covered many of the core Python statements that are used with code blocks, such as the conditional statements if, elif, and else, and the looping statements for and while. The lesson ended with a discussion on how list comprehension can distill the logic of a multi-line for loop in one easy to read line of code. It is the through the use of these code block-centric keywords that creates the basic structure of complex, multi-branching, and iterative programs.

6.8. Want to learn more?#

Python Software Foundation - Compound Statements