Don't Forget about Recursion


Recursion is a substitute for a loop

We talked about recursion earlier. Recursion is really a substitute for a loop. When you solve a problem that seems to need some kind of repetition, you can choose either to use recursion or to use a loop. (Do not try to use both!)

Now that you have seen loops, you are probably tempted to use them for all repetition. The reason is that they are more like how you are accustomed to thinking about repetition. But let's compare definitions of isSubstring, one using a loop, the other using recursion. Here is the loop version that is simplified by using isPrefix.

  Define
    isSubstring(needle: String, haystack: String): Boolean by

    isSubstring(needle, haystack) = found |
      Let needleLen   = length(needle).
      Let haystackLen = length(haystack).
      Let i = 1.
      Let found = false.
      While (not found) and (i <= haystackLen - needleLen + 1) do
        If needle == haystack_*[i,..., i + needleLen - 1]) then
          Relet found = true.
        %If
        Relet i = i + 1.
      %While
  %Define
Here is a recursive definition of the same function.
  Define
    isSubstring(needle: String, haystack: String): Boolean by

    case isSubstring(s, t) = true                    when isPrefix(s, t)
    case isSubstring(s, t) = false                   when t == ""
    case isSubstring(s, t) = isSubstring(s, tail(t))
  %Define
The recursive definition is shorter. It also has the advantage that you can look at each case separately, and ask whether it makes sense (in the last case under the presumption that the recursive use of isSubstring works correctly). You do not need to hold everything up at once.

Experience has shown that defining functions by recursion often takes less of your time. With experience, you get correct definitions more quickly than with a loop, often requiring no debugging at all.


Problems

  1. [solve] Using a loop, and not recursion,, define function sumOdds(n) = 1 + 3 + ... + n. For example, sumOdds(5) = 1 + 3 + 5 = 9. Assume that n is odd.

    Add an Execute part that computes sumOdds(n) for n = 1, 3, 5, ..., 25.

  2. [solve] Using a recursion, and not a loop,, define function sumOdds(n) = 1 + 3 + ... + n. For example, sumOdds(5) = 1 + 3 + 5 = 9. Assume that n is odd.

    Add an Execute part that computes sumOdds(n) for n = 1, 3, 5, ..., 25.

  3. [solve] Using a loop (and not recursion), define a function isPrime(n) that tells whether a given positive integer n is prime. Write Add your own Execute part that performs a few tests.

  4. [solve] Using recursion (and not a loop), define function anyFactor(n,i,j), which yields true if one of the numbers i, i+1, ... j is a factor of n. (More precisely, it returns true if there is a factor k of n where ikj.)

    After defining anyFactor, define isPrime as by saying that n is prime is it does not have any factor from 2 to n-1. Add an Execute part that does some tests of isPrime.

    Hint.

    1. anyFactor(n,i,j) is always false when i > j, because then there are no numbers k at all that satisfy i < kj, let alone any that are factors of n.

    2. Otherwise, if i is a factor of n, then there certainly is a factor of n in that range.

    3. What should you do in the case where ij and i is not a factor of n?


Summary

Recursion is a substitute for a loop. You can typically solve a given algorithmic problem either using recursion or using a loop.

Although beginning programmers tend to select loops for their algorithms, experts have found that selecting recursion often takes less time, results in a shorter and more elegant program, and works surprisingly often without any debugging. Recursion also allows you to think about each case in an algorithm separately without the need to hold everything up in the air at once.


Review

Some loops (and recursive function definitions) go through a sequence of values accumulating a result. They need to look at all of the value in the sequence, no matter what. For example, to add up some values, you need to look at them all.

Some loops (and recursive function definitions) go through a sequence of values looking for one that has a desired property. Those algorithms can stop as soon as they have found what they are looking for. If they go through the entire sequence without finding what they are looking for, they need to do something appropriate.

Sometimes, a search algorithm is looking for a value that does not have a given property. For example, to ask whether two strings are the same, you look for an index where they have different characters.