Now that we have learned a bit about while and for loops, now we can consider nested loops. A nested loop structure is constructed by placing one loop inside another loop. This is often called repeating a repetition. Nested loops can be while loops or for loops, or a combination, that is, a for loop nested inside a while loop or visa versa.
General Form
The general form of the code of the nested for loop is as follows:
pre_loop_statement(s)
for outer_loop_variable in sequence:
for inner_loop_variable in sequence:
inner_loop_statement_1
inner_loop_statement_1
inner_loop_statement_1
.
inner_loop_statement_n
outer_loop_statement_1
outer_loop_statement_2
.
outer_loop_statement_n
post_loop_statement(s)
General Form Details:
Nested loops have an outer loop and an inner loop.
Notice the indentation in this general form. The inner for loop is indented inside the outer for loop and each of the two loops has its indented statements as well.
Each time the outer loop runs (iterates) the inner loop runs the number of times specified in the inner loop signature.
The outer loop of the functional code example above iterates five times (0 thru 4) and each time the outer loop runs the inner loop iterates ten times (0 thru 9).
Output:
Functional Code Details:
As specified in the outer loop signature, the outer loop repeats five times, with x set to 0 initially, then 1, then 2, then 3, and then 4.
Each time the outer loop runs, it prints the value of x (0) and a space.
Then the inner loop runs, as specified in the inner loop signature, it repeats 10 times with y set to 0 initially, then 1, then 2, ... thru to 9.
After the inner loop runs, the execution leaves the inner loop and returns to the next statement of the outer loop which is the print() statement. That empty print statement moves the (invisible) cursor to the next line (like pressing the Enter key on the keyboard).
All of those steps create one row of output, the first time it looks like 0 0 1 2 3 4 5 6 7 8 9,
The first 0 is the 0 from the outer loop, then the 0 thru 9 are the values of y as the inner loop runs.
Next, execution returns to the outer loop signature.
The second time the outer loop runs, x is set to 1 and prints the value of x (1) and a space.
Then the inner loop runs again, just as it did the first time, it repeats 10 times with y set to 0 initially, then 1, then 2, ... thru to 9.
Again, after the inner loop runs, the execution leaves the inner loop and returns to the next statement of the outer loop which is the print() statement. That empty print statement moves the (invisible) cursor to the next line (like pressing the Enter key on the keyboard).
These steps create the second row of output, the first time it looks like 1 0 1 2 3 4 5 6 7 8 9,
The first digit (1) is the 1 from the outer loop, then the 0 thru 9 are the values of y as the inner loop runs.
Next, execution returns to the outer loop signature.
These steps repeat again for x = 2, x = 3 and x = 4.
There are many uses of nested loops. One very common use is working with rows and columns of data, which we will explore when we study data structures a little later in the book.
More Examples
Examples 1 & 2 are intended to be studied and compared.
Example 1
for i in range(5):
for j in range(5):
print("*", end="")
print()
Example 1 Output
*****
*****
*****
*****
*****
Example 2
for i in range(5):
for j in range(i + 1):
print("*", end="")
print()
Example 2 Output
*
**
***
****
*****
Question
Compare the loop signatures between Examples 1 & 2 above and answer the following questions:
Note that in Example 2, we changed the range parameter to i + 1. Why did making this change alter the output to appear as a triangle of * characters, instead of the rectangle of * characters we see in Example 1's output?
What difference would there have been to the output of Example 2 if we had not included + 1 in the Example 2 range parameter?
Answers:
Changing the range parameter to i causes the inner loop to iterate a different number of times each time the outer loop runs. Linking the inner loop to the variable of the outer loop causes the inner loop to run 0 times the first iteration, 1 time the second iteration, 2 times, etc.
Remember that ranges start at zero, so we added the +1 so that count of * characters starts with 1, instead of zero, the first time the inner loop runs and then every subsequent iteration increments that by one based on the value of i in the outer loop.
Examples 3 & 4 add decision statements to demonstrate how to combine decisions with loops to achieve intended results.
Example 3
rows = 5
cols = 5
for i in range(rows):
for j in range(cols):
if j < i:
print(" ", end=" ")
else:
print("*", end=" ")
print()
Example 3 Output
* * * * *
* * * *
* * *
* *
*
Example 4
rows = 5
cols = 10
for i in range(1, rows + 1):
for j in range(1, cols + 1):
if i == 1 or i == rows or j == 1 or j == cols:
print("*", end=" ")
else:
print(" ", end=" ")
print()
Questions: Answer the following questions about Examples 3 ad 4:
Example 3: What is the purpose of the if j < i if statement in the Example 3 code?
Example 3: What would be the effect of changing Line 5 to if i < j? And why?
Example 4: What is the purpose of the compound if decision on Line 5?
Example 4: How could you change this code to print the box with double the spaces between the * characters and also a blank line between each row in the rectangle shape? Like this (compare this to the Example 4 Output above):
Example 3: The if j < i statement in Example 3 controls when a * character is printed on any given row. So, for example, during the first iteration of the nested for loop, i starts out at zero (0). Inside the first iteration of the i loop, j starts at zero (0) so when the if j < i statement checks the values, i is never less than j so the else part of the decision runs for each iteration of the j loop and an * character is printed each time the j loop runs. When the i loop runs the second time, the value of i is one (1). Since the j loop starts at zero (0), when the first iteration of the inner loop runs the if j < 1 will be true so a blank space will print, not an *. Then the second through the fifth iterations of the inner loop will print * each time because j < 1 will be False each of the last four iterations. This pattern will continue for the remaining iterations of the i loop. Each iteration i will increment and less and less of the values of rows and cols will result in a False result in the if statement.
Example 3: If we changed the condition in the if statement to if i < j the output will be reversed, the triangle of * characters would be printed like this:
*
* *
* * *
* * * *
* * * * *
The reason is that if we trace execution of this nested loop, the comparison of j < i and i < j reverses the True and False nature of the condition so the printing of the * characters occur opposite of the original.
Example 4: The purpose of the compound condition is to control when the * character is printed. The key to understanding this compound condition is to remember that in a nested loop the outer loop controls the rows and the inner loop controls the inner loop. Also, note that we are forcing our range count to start at 1, rather than its default of 0. This causes us to add 1 to our rows and columns in the loop signatures so that our row and columns counts result in the 5 and 10 respectively as we defined on Lines 1 and 2 of the code.
Example 4: There are several approaches to this proposed change. For example, in the example solution code below, to increase the space between the * characters on any given row, we simply change the end parameter value to contain two spaces rather than one. To add blank rows to increase the vertical spacing we can add the \n escape sequence to the existing print() statement at the bottom of the outer loop.
rows = 5
cols = 10
for i in range(1, rows + 1):
for j in range(1, cols + 1):
if i == 1 or i == rows or j == 1 or j == cols:
print("*", end=" ")
else:
print(" ", end=" ")
print("\n")
Tracing Nested Loop Execution
When you are learning programming structures, especially those that repeat steps, it is often very useful to manually trace the execution of that structure. For loops, this is called loop tracing. When we trace code execution it is important to, first, understand how the structure works and, second, to write out each step the way the interpreter will execute each step in the computer.
For example, consider the following nested for loop:
for i in range(4):
print("Row", i + 1, sep=" ", end=": ")
for j in range(3):
print(j + 1, end=" ")
print()
Write a Python program that prints a triangle of digits using nested for loops. Prompt the user several rows, then for that number of rows print the row number repeated each row's number of times. Here is an example of the prompt and output:
Please enter the number of rows: 5
1
22
333
4444
55555
Python Code
rows = int(input("Enter the number of rows:"))
for i in range(1, rows + 1):
for j in range(i):
print(i, end="")
print()
Output
Enter the number of rows: 8
1
22
333
4444
55555
666666
7777777
88888888
Solution Notes:
First, we prompt the user for the number rows and store it in a variable.
In the outer loop signature on Line 2, we establish a range() from 1 to rows + 1 which is based on the value entered by the user.
This outer loop range will give us the number of rows starting with 1 and it increments until it reaches the user-entered value.
In this solution, the inner loop signature is set to range(i) which ties the inner loop to the outer loop value.
As each iteration of the outer loop occurs (1, 2, 3, ... rows) the inner loop iterates that number of times and prints the value of i each time.
The result of this approach provides the desired output.
Problem 2
Write a Python program that prompts the user for an integer value and then uses nested for loops to create a multiplication (times) table based on that number. For example, if the user enters 5, your program should produce the times table displayed below. Notice the spacing between numbers on each row and the blank lines between rows, your program should produce that spacing as well. Hint: Take a look at the Escape Sequences page for help with the spacing of the rows and columns in your output.
user_num = int(input("Please enter an integer for your times table: "))
for i in range(1, user_num + 1):
for j in range(1, user_num + 1):
print(i * j, end="\t")
print("\n")
Solution Notes:
Notice that we use the user-entered value for both the outer and inner loop range value. This gives us the equal number of rows and columns we want for our times table.
Also notice that we explicitly start our ranges at 1, rather than the default of 0. This allows us to use the loop variables for our multiplication at each iteration.
Note that we use the \t and \n escape sequences to help with the spacing of our multiplication table output.