Like lists, natural numbers have a recursive structure of which we can take advantage when we write direct-recursion procedures. A natural number is either (a) zero, or (b) the successor of a smaller natural number, which we can obtain by subtracting 1.
Standard Scheme provides the predicate zero? to distinguish
between the (a) and (b) cases, so we can again use an
if-expression to ensure that only the expression for the
appropriate case is evaluated. So we can write a procedure that applies to
any natural number if we know (a) what value it should
return when the argument is 0 and (b) how to convert the value that
the procedure would return for the next smaller natural number into the
appropriate return value for a given non-zero natural number.
For instance, let's consider a procedure that computes the termial
of any natural number number, that is, the result of adding
together all of the natural numbers up to and including
number. Here are some illustrative sample calls:
> (termial 7) ; = 7 + 6 + 5 + 4 + 3 + 2 + 1 + 0 28 > (termial 1) ; = 1 + 0 1 > (termial 0) 0
And here's how I'd define termial:
;;; termial: compute the sum of natural numbers not greater
;;; than a given natural number
;;; John David Stone
;;; Department of Mathematics and Computer Science
;;; Grinnell College
;;; stone@cs.grinnell.edu
;;; created February 3, 2000
;;; last revised August 8, 2001
;;; Given:
;;; NUMBER, an integer.
;;; Result:
;;; SUM, an integer.
;;; Precondition:
;;; NUMBER is exact and non-negative (in other words,
;;; a natural number).
;;; Postcondition:
;;; SUM is the sum of the natural numbers not greater
;;; than NUMBER.
(define termial
(lambda (number)
(if (zero? number)
0
(+ number (termial (- number 1))))))
Whereas in a list recursion, we
called the cdr procedure to reduce the length of the list in
making the recursive call, the operation that we apply in recursion with
natural numbers reduces the natural-number argument in the recursive call
by 1. Here's a summary of what actually happens during the evaluation of a
call to the termial procedure -- say, (termial
5):
(termial 5)
--> (+ 5 (termial 4))
--> (+ 5 (+ 4 (termial 3)))
--> (+ 5 (+ 4 (+ 3 (termial 2))))
--> (+ 5 (+ 4 (+ 3 (+ 2 (termial 1)))))
--> (+ 5 (+ 4 (+ 3 (+ 2 (+ 1 (termial 0))))))
--> (+ 5 (+ 4 (+ 3 (+ 2 (+ 1 0)))))
--> (+ 5 (+ 4 (+ 3 (+ 2 1))))
--> (+ 5 (+ 4 (+ 3 3)))
--> (+ 5 (+ 4 6))
--> (+ 5 10)
--> 15
The restriction that termial takes only natural numbers as
arguments is an important one. If we gave it a negative number or a
non-integer, we'd have a runaway recursion, because we cannot get to 0
by subtracting 1 repeatedly from a negative number or from a non-integer,
and so the base case would never be reached. If we gave the
termial procedure an approximation rather than an exact
number, we might or might not be able to reach zero, depending on how
accurate the approximation is and how much of that accuracy is preserved by
the subtraction procedure.
The important part of getting recursion to work is making sure that the base case is inevitably reached by performing the simplification operation enough times. For instance, we can use direct recursion on exact positive integers with 1, rather than 0, as the base case, provided that we always start from an exact integer greater than or equal to 1.
As an illustration of this, consider the factorial procedure,
which finds the product of the positive integers up to and including its
argument. (The computation would be pointless if we included 0 as one of
the factors, since 0 times anything is 0.) Again, we can begin by writing
out some sample calls:
> (factorial 7) ; = 7 x 6 x 5 x 4 x 3 x 2 x 1 5040 > (factorial 2) ; = 2 x 1 2 > (factorial 1) 1
Here is the definition:
;;; factorial: compute the product of positive integers not
;;; greater than a given positive integer
;;; John David Stone
;;; Department of Mathematics and Computer Science
;;; Grinnell College
;;; stone@cs.grinnell.edu
;;; created February 3, 2000
;;; last revised August 8, 2001
;;; Given:
;;; NUMBER, an integer.
;;; Result:
;;; PRODUCT, an integer.
;;; Precondition:
;;; NUMBER is positive and exact.
;;; Postcondition:
;;; PRODUCT is the product of the positive integers not
;;; greater than NUMBER.
(define factorial
(lambda (number)
(if (= number 1)
1
(* number (factorial (- number 1))))))
Similarly, we can use direct recursion to approach the base case from below
by repeated additions of 1, if we know that our starting point is less than
or equal to that base case and that the difference between them is an exact
integer. As an example, let's develop a procedure that takes two exact
integers, lower and upper, as arguments, and
returns a list of the exact integers from lower to upper, inclusive, in
ascending order, thus:
> (count-from 1 10) (1 2 3 4 5 6 7 8 9 10) > (count-from -3 +4) (-3 -2 -1 0 1 2 3 4) > (count-from 116 117) (116 117) > (count-from -38 -38) (-38)
Here is the definition:
;;; count-from: given two natural numbers, construct
;;; a list of the natural numbers from the first to
;;; the second, inclusive, in ascending order
;;; John David Stone
;;; Department of Mathematics and Computer Science
;;; Grinnell College
;;; stone@cs.grinnell.edu
;;; created February 3, 2000
;;; last revised August 8, 2001
;;; Given:
;;; LOWER and UPPER, both integers.
;;; Result:
;;; LS, a list.
;;; Preconditions:
;;; (1) LOWER and UPPER are exact.
;;; (2) LOWER is less than or equal to UPPER.
;;; Postconditions:
;;; (1) The length of LS is UPPER - LOWER + 1.
;;; (2) For every natural number k less than the length of
;;; LS, the element in position k of LS is LOWER + k.
(define count-from
(lambda (lower upper)
(if (= lower upper)
(list upper)
(cons lower (count-from (+ lower 1) upper)))))
This document is available on the World Wide Web as
http://www.cs.grinnell.edu/~gum/courses/151/readings/recursion-with-integers.xhtml
created February 3, 2000
last revised September 12, 2002