Repetition
Learning Objectives
At the end of this sub-unit, students should
- know how to connect recursion with iteration.
Repeating Operations
We can think of recursion as a way to repeat an operation. The operation we want to repeat is computing the same problem but using simpler input. Another way to repeat an operation is via iteration. Can we connect recursion and iteration?
The answer is yes. A proof of that is our computer. A computer can be thought of as a single giant simple loop.
- Fetch the next operation.
- Execute the operation.
- Repeat Step 1.
Since we can execute a recursive function on our computer, it means we can convert any recursion into an iteration. However, the conversion may not be easy. Fortunately, there is a restricted kinds of recursion that has a more direct connection to iteration. This is called linear recursion.
This kinds of recursion is a recursion that we have done before where the function \(F(x)\) is calling itself at most once. Before we continue, we will show the prototypical non-linear recursive formula called the Fibonacci sequence. This sequence is defined only for non-negative \(n\).
Notice how \(F(n)\) is calling \(F\) twice. The first is \(F(n - 1)\) and the second is \(F(n - 2)\). Although there is a way to convert a Fibonacci-like recursion into an iteration, such conversion is not general. What we are going to show is a general way of converting a linear recursion into iteration. We will try to discover these conversion via observations.
Comparing Factorial
Iterative Factorial
Our iterative factorial function is given below.
We can try evaluating factorial(6)
and produce the trace table below.
Iteration | n | res3 | i3 | res5 = res3 * i3 | i5 = i3 + 1 |
---|---|---|---|---|---|
pre | 6 | 1 | 1 | ||
1 | 6 | 1 | 1 | 1 | 2 |
2 | 6 | 1 | 2 | 2 | 3 |
3 | 6 | 2 | 3 | 6 | 4 |
4 | 6 | 6 | 4 | 24 | 5 |
5 | 6 | 24 | 5 | 120 | 6 |
6 | 6 | 120 | 6 | 720 | 7 |
post | 6 | 720 | 7 |
If we look at the value of res5 over the entire iteration, we can see that it computing the following expression.
Yet another factorial function is the following. We will use this alternative as it will provide a simpler comparison to the recursion. Recap that due to the abstraction into a function, we do not care about the implementation as long as it solves the same problem.
Since we do not have the local variable i
, the trace table is simpler.
Iteration | n3 | res3 | n5 = n3 - 1 | res5 = res3 * n3 |
---|---|---|---|---|
pre | 6 | 1 | ||
1 | 6 | 1 | 5 | 6 |
2 | 5 | 6 | 4 | 30 |
3 | 4 | 30 | 3 | 120 |
4 | 3 | 120 | 2 | 360 |
5 | 2 | 360 | 1 | 720 |
6 | 1 | 720 | 0 | 720 |
post | 0 | 720 |
The unrolling is shown below.
Recursive Factorial
Now we look at the recursive factorial function below.
While this is not exactly a trace table, we can actually construct some kind of table that captures the computation. To highlight the time component of the component, we separate them into two parts. The first part is the sequence of recursive call to the base case. The second part is the sequence of return value and deferred operation.
Call | n | Deferred | Return |
---|---|---|---|
factorial(6) | 6 | 6 \(\times\) ░░░ | ░░░ |
factorial(5) | 5 | 5 \(\times\) ░░░ | ░░░ |
factorial(4) | 4 | 4 \(\times\) ░░░ | ░░░ |
factorial(3) | 3 | 3 \(\times\) ░░░ | ░░░ |
factorial(2) | 2 | 2 \(\times\) ░░░ | ░░░ |
factorial(1) | 1 | 1 \(\times\) ░░░ | ░░░ |
factorial(0) | 0 | 1 |
Call | n | Deferred | Return |
---|---|---|---|
factorial(6) | 6 | 6 \(\times\) 120 | 720 |
factorial(5) | 5 | 5 \(\times\) 24 | 120 |
factorial(4) | 4 | 4 \(\times\) 6 | 24 |
factorial(3) | 3 | 3 \(\times\) 2 | 6 |
factorial(2) | 2 | 2 \(\times\) 1 | 2 |
factorial(1) | 1 | 1 \(\times\) 1 | 1 |
factorial(0) | 0 | 1 |
By the same idea of unrolling but applied to recursion, we get the following expression.
Now that we have both table and expression, we can compare them.
Comparison
Let us look at the table side by side.
-
Iterative Factorial
Iteration n3 res3 1 6 1 2 5 6 3 4 30 4 3 120 5 2 360 6 1 720 post 0 720 (((((1 * 6) * 5) * 4) * 3) * 2) * 1 = 720 -
Recursive Factorial
Call n Deferred Return f(6) 6 6 \(\times\) 120 720 f(5) 5 5 \(\times\) 24 120 f(4) 4 4 \(\times\) 6 24 f(3) 3 3 \(\times\) 2 6 f(2) 2 2 \(\times\) 1 2 f(1) 1 1 \(\times\) 1 1 f(0) 0 1 6 * (5 * (4 * (3 * (2 * (1 * 1))))) = 720
There are few similarities but the most obvious one is probably the expression produced.
This is not surprising because they compute the same result.
In fact, because *
is associative (i.e., x * (y * z) == (x * y) * z
) we can remove all the parentheses.
We keep the main parentheses to highlight the similarities.
-
Iterative Factorial
1 * (6 * 5 * 4 * 3 * 2 * 1) = 720 -
Recursive Factorial
(6 * 5 * 4 * 3 * 2 * 1) * 1 = 720
Because *
is also commutative (i.e., x * y == y * x
), we can then reorder the expression make it exactly identical.
By comparing the table and mapping how the cell in the table is computed on the code, we can compare the code to see how they can produce the same value in the end.
We will use some of the terminologies used for recursion in our comparison.
However, by "recursion", we will simply look at the value of the next argument.
-
Iterative Factorial
-
Recursive Factorial
Caution
Note that we split the operation n * factorial(n - 1)
into two lines (i.e., Line 5 and Line 6) for a more precise comparison.
However, this causes syntax error.
It should be treated as a single line.
-
Iterative Factorial
-
Recursive Factorial
Explanation
The base case stops the recursion. In iteration, it stops the loop. But the loop condition must continue the loop, so we take the negation.
-
Iterative Factorial
-
Recursive Factorial
Explanation
This happens when the base case occurs.
If we look at factorial(0)
, both must return 1.
So that corresponds to the initial value of res
before the loop.
-
Iterative Factorial
-
Recursive Factorial
Explanation
In recursion, we assign n = n - 1
during call by value before executing the function body.
So it makes sense that it corresponds to the n = n - 1
at the bottom of the loop before we start the next iteration.
-
Iterative Factorial
-
Recursive Factorial
Explanation
This operation is done on each iteration and recursion.
The only issue is that the order for iteration (i.e., _ * n
) is different from recursion (i.e., n * _
).
In our case, the behavior is identical but may not always be.
So be careful.
Generalized Comparison
We can now use the idea of code generalization to further compare the two codes. Since we are only interested in linear recursion, we will look at our idealized linear recursion.
-
Iteration
-
Recursion
The table of comparison is also shown below.
Recursive Code | Recursive Element | Iterative Element | Iterative Code |
---|---|---|---|
cond(x, y, z) |
Base Case | Loop Condition | not cond(x, y, z) |
return base |
Base Value | Initial Value | res = base |
nx(x), ny(y), nz(z) |
Next Argument | Next Value Update | x, y, z = nx(x), ny(y), nz(z) |
op(..., x, y, z) |
Deferred Operation | Accumulation | res = op(res, x, y, z) |
So what is the use of knowing this? This will simplifies our task in case we need one form and not the other. In solving certain problems, we may be required to solve it iteratively and recursively. If we are stuck in one, but we can derive the other, then we can try to use this comparison to come up with the alternative code.
Let us apply this to some problem involving strings. We will not explain what the function is, so as to show how the comparison can be used as it is. Try to do it on your own first before looking at the other version.
Example #1
-
Iteration
-
Recursion
It is easier to come up with the table of comparison first. One thing to note is that we need to think which of the following accumulation is the correct one.
res = res + s[-1]
res = s[-1] + res
Table of Comparison
Recursive Code | Recursive Element | Iterative Element | Iterative Code |
---|---|---|---|
s == '' |
Base Case | Loop Condition | not (s == '') alternatively s != '' |
return '' |
Base Value | Initial Value | res = '' |
s[:-1] |
Next Argument | Next Value Update | s = s[:-1] |
s[-1] + |
Deferred Operation | Accumulation | res = res + s[-1] |
Example #2
-
Iteration
-
Recursion
Try to come up with the table of comparison on your own.