Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

6 Code Blocks: Conditional and Looping Statements

6.1Lesson goals

6.2Overview

Everything we have written so far follows a linear process: the Python interpreter run each line of code sequentially until it reaches the end. There are times in which we want our code to branch out into various paths depending on a set of inputs and requirements. This lesson will cover how we can use conditional checks to allow code to run under certain requirements and how we can iterate / loop a section of code multiple times.

6.3Code blocks

A code block is a section of code that runs from top to bottom in sequential order until it either ends or is redirected to another code block. Below is a simple example of code block:

a = 5                                   # Start code block
print(f"a is {a}")
equal_five = (a == 5)
print(f"a equals 5?: {equal_five}")     # End code block
a is 5
a equals 5?: True

Seems straightforward! 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. Conditional and looping statements allow us to this!

6.4Conditional statements

A conditional statement is a command that redirects program flow if a certain testing condition is met. This is often done using boolean logic with bool objects.

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. The code block below uses the if keyword to output and additional line of text if a >= 5:

a = 5                                   # Start code block 1
print(f"a is {a} (Code Block 1)")       # 1
                                        # 1
if a >= 5:                                  # Start code block 2
    print("a >= 5 (Code Block 2)")          # 2
    a = a * 10                              # End code block 2
                                         # 1
print(f"a is {a} (Code Block 1)")       # End code block 1
a is 5 (Code Block 1)
a >= 5 (Code Block 2)
a is 50 (Code Block 1)

Code block 2 only runs if a >= 5 is True. Notice how this is much easier to read than the previous one line check from earlier. Let’s change the value of a to be less than or equal to 5 and see what happens!

Code Block 2 is skipped! We have redirected program flow!

6.4.2Structure of the if statement

There are a few key formatting aspects the above if statement to be aware of. The command if a >= 5: that starts the second code block is called in Python a compound statement. This statement begins with the if keyword and ends with the colon character :.

6.4.3Indentations matter!

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:

a = 5                                   # Start code block 1
print(f"a is {a} (Code Block 1)")       # 1
                                        # 1
if a >= 5:                                  # Start code block 2
print("a >= 5 (Code Block 2)")          # 2
a = a * 10                              # End code block 2
                                         # 1
print(f"a is {a} (Code Block 1)")       # End code block 1
  Cell In[4], line 5
    print("a >= 5 (Code Block 2)")          # 2
    ^
IndentationError: expected an indented block after 'if' statement on line 4

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

a = 5                                   # Start code block 1
print(f"a is {a} (Code Block 1)")       # 1
                                        # 1
if a >= 5:                                  # Start code block 2
    print("a >= 5 (Code Block 2)")          # 2
        a = a * 10                              # End code block 2
                                         # 1
print(f"a is {a} (Code Block 1)")       # End code block 1
  Cell In[5], line 6
    a = a * 10                              # End code block 2
    ^
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 in the a = a * 10 line. Indentation needs to be consistent throughout the code block. Improper spacing and indenting will generate errors. Indentation overall significantly improves overall readability, but it requires you to be mindful of your formatting!

6.4.4The else clause

The else clause allows us to do something with the False outcome of the if statement. Using our initial if statement code block as an example, let’s add an else clause that prints out that a is less than 5:

a = 3                                   # Start code block 1
print(f"a is {a} (Code Block 1)")       # 1
                                        # 1
if a >= 5:                                  # Start code block 2
    print("a >= 5 (Code Block 2)")          # 2
    a = a * 10                              # End code block 2
else:                                           # Start of code block 3
    print("Hey! a < 5! (Code Block 3)")         # End of code block 3
                                        # 1
print(f"a is {a} (Code Block 1)")       # End code block 1
a is 3 (Code Block 1)
Hey! a < 5! (Code Block 3)
a is 3 (Code Block 1)

Here, the else clause catches all cases when the if condition 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.4.5The elif keyword

The elif keyword (pronounced: “else if”) allows us to chain multiple if statements together. So if the first if statement flags a False condition, we can then check another conditional. The code block below demonstrates this:

a = 3                                   # Start code block 1
print(f"a is {a} (Code Block 1)")       # 1
                                        # 1
if a >= 5:                                  # Start code block 2
    print("a >= 5 (Code Block 2)")          # 2
    a = a * 10                              # End code block 2
elif a >= 3:                                    # Start code block 3
    print("3 =< a < 5 (Code Block 3)")          # End code block 3
else:                                               # Start of code block 4
    print("Hey! a < 3! (Code Block 4)")             # End of code block 4
                                        # 1
print(f"a is {a} (Code Block 1)")       # End code block 1
a is 3 (Code Block 1)
3 =< a < 5 (Code Block 3)
a is 3 (Code Block 1)

In this example the second code block with the if statement is False and skipped so the interpreter moves to the elif keyword that evaluates True.

It is important to 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 = 6                                   # Start code block 1
print(f"a is {a} (Code Block 1)")       # 1
                                        # 1
if a >= 5:                                  # Start code block 2
    print("a >= 5 (Code Block 2)")          # 2
    a = a * 10                              # End code block 2
elif a >= 3:                                    # Start code block 3
    print("3 =< a < 5 (Code Block 3)")          # End code block 3
else:                                               # Start of code block 4
    print("Hey! a < 3! (Code Block 4)")             # End of code block 4
                                        # 1
print(f"a is {a} (Code Block 1)")       # End code block 1
a is 6 (Code Block 1)
a >= 5 (Code Block 2)
a is 60 (Code Block 1)

In this example a is set to 6 so both the if and the elif conditionals should return True. However, since the if conditional is the first conditional evaluated True, its code block is executed and all other code blocks associated with the starting if statement (the elif and else conditional statements) are ignored.

6.4.6The and and or Boolean operators

A conditional statement can be constructed with any number of conditional clauses chained together using the and and or Boolean operators. The and operator 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:

# `and` Boolean operator demonstration

number = 10

# Individual Boolean checks checks
print("Individual Boolean checks")
print("~~~~~~~~~~~~~~~~")
print("number > 8:", number > 8)
print("number < 11:", number < 11)
print("(number > 8) and (number < 11):", (number > 8) and (number < 11))
print("\n")

# Using `and` in an `if` statement
print("Using `and`")
print("~~~~~~~~~~~~~~~~")
if (number > 8) and (number < 11):
    print(f"The value {number} is between 8 and 11")
else:
    print(f"The value {number} is outside of 8 and 11")
Individual Boolean checks
~~~~~~~~~~~~~~~~
number > 8: True
number < 11: True
(number > 8) and (number < 11): True


Using `and`
~~~~~~~~~~~~~~~~
The value 10 is between 8 and 11

The or operator 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:

# `or` Boolean operator demonstration

number = 5

# Individual Boolean checks checks
print("Individual Boolean checks")
print("~~~~~~~~~~~~~~~~")
print("number > 8:", number > 8)
print("number < 11:", number < 11)
print("(number > 8) or (number < 11):", (number > 8) or (number < 11))
print("\n")

# Using `or` in an `if` statement
print("Using `or`")
print("~~~~~~~~~~~~~~~~")
if (number > 8) or (number < 11):
    print(f"The value {number} is outside of 8 and 11")
else:
    print(f"The value {number} is between of 8 and 11")
Individual Boolean checks
~~~~~~~~~~~~~~~~
number > 8: False
number < 11: True
(number > 8) or (number < 11): True


Using `or`
~~~~~~~~~~~~~~~~
The value 5 is outside of 8 and 11

6.4.7Chaining conditionals

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? I guess so!")
True? I guess so!

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.4.8The not Boolean operator

The not Boolean operator can be used to negate a Boolean (i.e., take the opposite) in a conditional statement. This is useful if you need to do a quick check for a negated condition (i.e., a False condition):

a = False

if not a:
    print("a is False -> the `if` statement is True!")
a is False -> the `if` statement is True!

This is equivalent to:

a = False

if a == False:
    print("a is False")
a is False

Both methods work! The not keyword gives you another way to test for negated (i.e., False) conditions.

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 spectral colors:

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!

You can also chain this another way since each conditional check goes sequentially:

wavelength = 600

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

Both ways work!


Looping statements allow us to iterate a block of code multiple times until a condition is met. There are two common built-in looping statements used in Python: the for statement and the while statment.

6.5.1The for statement

The for statement iterates over a sequence-based object 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 and is followed by the command i in [0, 1, 2, 3]:, which states two things. First, that for each element in the list [0, 1, 2, 3], we will run the subsequent code block using the objects in the sequence. 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.

You can also place variables in the for loop statement:

print("Iterating over list")      # Start code block 1
a = [0, 1, 2, 3]                  # 1
print("a:", a)                    # 1
                                  # 1
for i in a:                         # Start code block 2
  print(i)                          # End code 2
print("Done looping")             # End code block 1
Iterating over list
a: [0, 1, 2, 3]
0
1
2
3
Done looping

The iterated object does not need to be a perfect periodic sequence either. The for statement simply iterates over all the objects in the sequence.

print("Iterating over list")      # Start code block 1
a = [0, -1, 21, 3.3, "hello"]     # 1
print("a:", a)                     # 1
                                  # 1
for i in a:                         # Start code block 2
  print(i)                          # End code 2
print("Done looping")             # End code block 1
Iterating over list
a: [0, -1, 21, 3.3, 'hello']
0
-1
21
3.3
hello
Done looping

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
                                # 1
for i in range(4):                  # Start code block 2
    print("i:", i)                  # 2
    for b in range(3):                  # Start code block 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 main loop for i in range(4):. It is important to keep in mind the indentation when creating nested loops and 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.

6.5.2The enumerate( ) function

A very useful function that can be used with for loops is the built-in enumerate function. This function attaches an index number to each object in an iterable list. The code below demonstrates how enumerate can be used over a list object:

print("Iterating using an enumerated list")
for idx, i in enumerate([23, 11, 2 ,5]):
    print("Index:", idx)
    print("Value:", i)
Iterating using an 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: (1) the index of the current element and (2) 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]

6.5.3Looping through dict objects

If we iterate over a dictionary object, we 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 built-in 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 built-in .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.5.4The while loop

The while loop is similar to the for loop but it iterates using a conditional statement rather than sequenced-base object. This looping structure operates until the conditional statement is no longer true (i.e., False):

counter = 0
print("Loop until condition is false")

while counter < 5:
  print(counter)
  counter = counter + 1
Loop until condition is false
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 and you will need to halt / stop the kernel’s process.

6.5.5Example: Bundling light

Using the example from earlier as a starting point, create a program that reports the color of each wavelength in a list object that contains the following wavelengths:

300 nm300 \text{ nm}, 390 nm390 \text{ nm}, 400 nm400 \text{ nm}, 535 nm535 \text{ nm}, 600 nm600 \text{ nm}, 732 nm732 \text{ nm}, and 1000 nm1000 \text{ 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.5.6The 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, for example, 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:
  if counter == 3:
    counter += 1
    continue
  print(counter)
  counter += 1
Skipping 3
0
1
2
4

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

6.6List comprehension

List comprehension is a powerful coding pattern when working with list objects. List comprehension allows the Python shell 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.

List comprehension-based coding can be a bit hard to read at first, so let’s go over two examples. Below is an example in which a new list is created whose values are 4x greater than a starting list. Both for loop and list comprehension versions are provided:

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

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

# Simple `for` loop implementation:
list_b = []
for i in list_a:
    list_b.append(4*i)   # List-based method that appends value to list

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

# Display results
print("list_a:", list_a)
print("list_b:", list_b)
print("list_c:", list_c)
list_a: [0, 1, 2, 3, 4]
list_b: [0, 4, 8, 12, 16]
list_c: [0, 4, 8, 12, 16]

The example above 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. This process is repeated to create list_c, but now use list comprehension to achieve this in one line of code. The list comprehension statement, list_c = [4*i for i in list_a], does all the work of the for loop in one line. This statement multiplies each element by 4 (4*i) for each element i in list_a and returns the results as a new list.

The list comprehension route is a more compact way to create the new list object but it is harder to read. 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 checks if the modulus of the element i and 2 is equal to 0. If the conditional 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.

The choice on using list comprehension is up to you. It is more compact to write, but harder to read. Code readability is critical. Some programmers will simply use a set of for and if statements instead of list comprehension. Others will include additional comments to clarify the command.

6.7Matrix 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. Now with the knowledge of conditional and looping statements, let’s see if we can write a code block to perform this mathematical operation!

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:

(a11a12a21a22a31a32)+(b11b12b21b22b31b32)=(a11+b11a12+b12a21+b21a22+b22a31+b31a32+b32)\begin{pmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \\ a_{31} & a_{32} \end{pmatrix} + \begin{pmatrix} b_{11} & b_{12} \\ b_{21} & b_{22} \\ b_{31} & b_{32} \end{pmatrix} = \begin{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{pmatrix}

and simple numerical example is shown below for completeness:

(123456)+(035721)=(1581177)\begin{pmatrix} 1 & 2 \\ 3 & 4 \\ 5 & 6 \end{pmatrix} + \begin{pmatrix} 0 & 3 \\ 5 & 7 \\ 2 & 1 \end{pmatrix} = \begin{pmatrix} 1 & 5 \\ 8 & 11 \\ 7 & 7 \end{pmatrix}

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 are 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.7.1Example: 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×nm \times n matrix multiplied by an n×pn \times p matrix results in a matrix product of size m×pm \times 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×32 \times 3 matrix by a 3×23 \times 2 matrix results in a 2×22 \times 2 matrix with the following cells:

(d11d12d13d21d22d23)(e11e12e21e22e31e32)=(d11e11+d12e21+d13e31d11e12+d12e22+d13e32d21e11+d22e21+d23e31d21e12+d22e22+d23e32)\begin{pmatrix} d_{11} & d_{12} & d_{13} \\ d_{21} & d_{22} & d_{23} \end{pmatrix} \cdot \begin{pmatrix} e_{11} & e_{12} \\ e_{21} & e_{22} \\ e_{31} & e_{32} \end{pmatrix} = \begin{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{pmatrix}

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:

(123456)(035721)=(16203753)\begin{pmatrix} 1 & 2 & 3 \\ 4 & 5 &6 \end{pmatrix} \cdot \begin{pmatrix} 0 & 3 \\ 5 & 7 \\ 2 & 1 \end{pmatrix} = \begin{pmatrix} 16 & 20 \\ 37 & 53 \end{pmatrix}

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.8Final thoughts

We covered many of the fundamental concepts that are needed to create more complex Python programs in this lesson. 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 discussions on how list comprehension can distill the logic of a multi-line for loop in one easy to read line of code and how to use this branching code blocks to do simple matrix math. It is the through the use of these code block-centric keywords that creates the basic structure of complex, multi-branching, and iterative programs.