Nested Loop
Learning Objectives
At the end of this sub-unit, students should
- understand how nested iteration works.
- know how to write nested iteration statement.
- know how to apply nested iteration statement in problem solving.
ASCII Art
As with selection, we can combine iteration with another iteration. If we have a loop inside a loop, we have a nested loop! The easiest way to illustrate nested loop is via ASCII art. What we will do is to draw some patterns on screen.
As we are trying to illustrate nested loop, all of these patterns can be solved with a single loop by using the string repetition operator *
.
So let us assume that we do not have such operator.
Otherwise, we will have to look for much more complicated problems and that may only cause confusion.
Square
The first pattern we want to draw is the simplest one.
Let us draw a square.
The square should depend on some variable n
.
Given n
, we draw a square with n
rows and n
columns.
How should we draw this?
Let us first understand how we can draw on a screen.
Unlike pen/pencil, the way we draw on a screen is to print using the print
function.
But recap that the print
function print one line at a time.
So we need to figure out what need to do at each line.
By comparing the sample run above, we can figure out that at each line, we need to print exactly n
number of *
.
Of course we can use n * '*'
, but we assume we have no such operation.
So what can we do?
Well, we need to use another loop to repeatedly concatenate *
into a string and print this string.
So let us try to solve this problem of repeated concatenation first.
Constructing a Row
To construct a row, we will need to start with a variable holding the most basic string, which is the empty string.
At each iteration, we will need to add one *
.
Since we need to add exactly n
number of *
, we can use the fixed repetitions loop as follows.
Once we can construct this row, we need to look at the remaining problems.
Printing Each Row
As we can construct a row for a given some n
, the remaining problem is to print exactly n
rows.
We need another loop for this.
Within the loop, we will need to construct the row and print it.
Assuming we know how to construct a row, the code will look like the following.
Combining
So we have two separate codes.
- Code to construct row of
n
number of*
. - Code to print
n
rows.
The last task is to combine the two. Notice how we use the row construction inside the printing. So we need to copy the first code to the second. Unfortunately, a naïve copy will not work as you can test below.
The interesting question is, why does it fail? We need to become a detective and figure this out. One useful technique we want to introduce is to think about a code in terms of the following.
- Input: What are assumed to be initialized for the code to work?
- Output: What is the result of the code and in which variable?
- Local Variables: What variables are used for correct implementations of the code that is initialized within the code.
This way, we can use a grey box to represent the code1. Of course we need to know what the code is doing, but that can be put as side-note about the code. We do not have to care about how the code is implementing this procedure. We only to know the three aspects of it to be able to compose the code correctly. In the future, we will make this box into a black box by removing local variables from our design.
Using this idea, we can represent the construction of rows as the following grey box.
To fully combine the two codes, we need some conventions2.
Firstly, we need to consistently rename all variables including inputs and outputs.
In this case, because row
is not used by the outside, we can keep the name.
But let us change the following variables.
i
is renamed toj
.n
is renamed tom
.
Secondly, we add an initial assignment from input variables to the renamed variable.
In this case, let us add an assignment m = n
at the beginning.
Note that this second step is not necessary if the input variable is never modified by the code.
Renamed Row Construction
We can now use the renamed version of the row construction code. The combined code should work as intended.
Square
In subsequent problems, we will apply the same technique. However, the explanation will not be as thorough. Additionally, some steps may be skipped from the text but it is recommended that you attempt it without skipping any steps.
Left Triangle
Here, the idea is that instead of printing exactly n
number of *
on each row, we print k
number of *
where k
ranges from 1 to n
.
So in k
th row, we print exactly k
number of *
.
Sample Run
Here, we can see how the idea works for n = 5
.
Sub-Problems
The grey box is shown below so that we can apply renaming immediately.
Combining
Left Triangle
Right Triangle
Similar to left triangle, we print exactly k
number of *
.
However, the problem here is that we cannot do a right-aligned print.
What we can do instead is to print a whitespace ' '
(i.e., Space).
Sample Run
Here, we can see how the idea works for n = 5
by using _
to indicate whitespace.
Sub-Problems
Note that we have 2 inputs.
We need both n
and k
.
So we add two assignments to accommodate both.
Combining
Right Triangle
Inverted Right Triangle
With all the work we have done before, the last problem is actually quite straightforward. We do not even have to modify the row construction code. We can, but we do not have to. The only thing we need to modify is one of the inputs to the row construction for the right triangle problem.
The input we have to change is the first input, which is the variable k
.
In fact, the change is quite simple.
We simply change the way k
is generated.
Instead of generating from 1 upwards to n
, we simply reverses this and generate n
downwards to 1.
So the change is on the row print as highlighted below while the rest of the codes are the same as before.
Inverted Right Triangle
Managing Complexity
At this point, you may realize that our code is getting very very complicated. However, from a certain point of view, the solution is rather simple. Take for instance, the inverted right triangle problem above. If we look at it in terms of "simpler problems", then we can write the code as follows with details omitted.
Simpler Problem View
To manage complexities, we need to be able to name the highlighted section and execute the highlighted section when needed.
We may not even need to know how the highlighted section is implemented as long as we are sure it is doing the correct thing.
In fact, we can even replace the # : code omitted
section with string repetition.
Changing Row Construction
In fact, we have done something like this before.
When we invoke the print
function, we do not need to know how the print
function is implemented.
What we want is to create our own function.
This we will learn in the future.
But for now, simply note that problems can often be simpler if we think in terms of "simpler problems" and not the details.
That is what we are going to do going forward. We will describe problems in terms of simpler problems. The solutions to some simpler problems may be omitted especially if it does not introduce new ideas.