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, here is 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:
;;; termial: compute the sum of natural numbers not greater than a given
;;; natural number
;;; Given:
;;; NUMBER, a natural number.
;;; Result:
;;; SUM, a natural number.
;;; Preconditions:
;;; None.
;;; 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 is reducing the number 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 zero
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.
Using natural-number recursion, define and test a Scheme procedure named
power-of-two that takes a natural number as its argument and
returns the result of raising 2 to the power of that number. (For
instance, the value of (power-of-two 3) should be
23, or 8.)
It's possible to define this procedure non-recursively, using Scheme's
primitive expt procedure, but the point of the exercise is to
use recursion.
Define and test a Scheme procedure named fill-list that takes
two arguments, the second of which is a natural number, and returns a list
consisting of the specified number of repetitions of the first argument:
> (fill-list 'sample 5) (sample sample sample sample sample) > (fill-list (list 'left 'right) 3) ((left right) (left right) (left right)) > (fill-list null 1) (())
Define and test a Scheme procedure named count-down that takes
a natural number as argument and returns a list of all the natural
numbers less than or equal to that number, in descending order:
> (count-down 5) (5 4 3 2 1 0)
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.
;;; factorial: compute the product of positive integers not
;;; greater than a given positive integer
;;; 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))))))
We require the invoker of this factorial procedure to provide
an exact, strictly positive integer. (Zero won't work in this case,
because we can't reach the base case, 1, by repeated subtractions if we
start from 0.)
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. Here's an example:
;;; count-from: given two natural numbers, construct a list of the
;;; natural numbers from the first to the second, inclusive, in ascending
;;; order
;;; Given:
;;; LOWER and UPPER, both natural numbers.
;;; Result:
;;; LS, a list.
;;; Precondition:
;;; 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 or equal to 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)))))
What is the value of the call (count-from 15 25)? Write down
what you think that it should be, then copy the definition of
count-from into DrScheme and use it to find out what the call
actually returns.
Using count-from, define and test a Scheme procedure that
takes a natural number as argument and returns a list of all the natural
numbers that are strictly less than the argument, in ascending order. (The
traditional name for this procedure is iota -- another
Greek.letter.)
Here is a procedure that computes the product of all of the odd natural
numbers up to and including number:
(define odd-factorial
(lambda (number)
(if (= number 1)
1
(* number (odd-factorial (- number 2))))))
What precondition does odd-factorial impose on its argument?
What will happen if this precondition is not met?
Here is the definition of a procedure that computes the number of digits in
the decimal representation of number:
(define number-of-decimal-digits
(lambda (number)
(if (< number 10)
1
(+ (number-of-decimal-digits (quotient number 10)) 1))))
Test this procedure.
The definition of number-of-decimal-digits uses direct
recursion. Describe the base case of this recursion. Identify and
describe the way in which a simpler instance of the problem is created for
the recursive call. Explain how the procedure correctly determines that
the decimal numeral for the number 2000 contains four digits.
What preconditions does number-of-decimal-digits impose on its
argument?
For this exercise, you'll need to know about two predefined Scheme procedures that operate on strings:
The string-length procedure takes a string as argument and
returns a natural number indicating how many characters are in that string:
> (string-length "Grinnell") 8 > (string-length "Hello, world!") 13 > (string-length " ") 1 > (string-length "") 0
The string-append procedure takes any number of strings as
arguments and returns a string containing all the characters from its
arguments, in order:
> (string-append "alpha" "beta") "alphabeta" > (string-append "a1" "b2" "c3" "d4" "e5") "a1b2c3d4e5" > (string-append "alif") "alif" > (string-append "" "" "" "" "") ""
Define and test a Scheme procedure named duplicate-to-fit that
takes two arguments, a string and a maximum length, and returns a string
consisting of as many repetitions of the given string as possible, provided
that the specified maximum length is not exceeded.
> (duplicate-to-fit "alf" 11) "alfalfalf" ; Four copies would exceed the maximum length, but three will fit. > (duplicate-to-fit "alf" 12) "alfalfalfalf" ; An exact fit is okay. Four copies of "alf" make a string of 12 characters. > (duplicate-to-fit "*" 7) "*******" ; A one-character string always fits exactly; just duplicate it. > (duplicate-to-fit "*-" 9) "*-*-*-*-" > (duplicate-to-fit "invisible" 3) "" ; If the maximum length is less than the length of the string, return ; the null string (zero copies of the given string).
Define and test a procedure named runs that takes a natural
number n as argument and returns a list of Boolean values consisting
of n runs, where a run consists of zero or more occurrences of
#f followed by one occurrence of #t. The first
of the n runs should be of length 1, the second of length 2, and so
on.
> (runs 5) (#t #f #t #f #f #t #f #f #f #t #f #f #f #f #t)
This document is available on the World Wide Web as
http://www.cs.grinnell.edu/~stone/courses/scheme/recursion-with-natural-numbers.xhtml
created February 3, 2000
last revised September 15, 2000