Exercise 1.10
The Question
The following procedure computes a mathematical function called Ackermann's function:
(define (A x y)
(cond ((= y 0) 0)
((= x 0) (* 2 y))
((= y 1) 2)
(else (A (- x 1) (A x (- y 1))))))
What are the values of the following expressions?
(A 1 10)
(A 2 4)
(A 3 3)
Consider the following procedures, where A
is the procedure defined above:
(define (f n) (A 0 n))
(define (g n) (A 1 n))
(define (h n) (A 2 n))
(define (k n) (* 5 n n))
Give concise mathematical definitions for the functions computed by the
procedures f
, g
, and h
for positive integer values of \(n\). For example, (k n)
computes \(5n^2\) .
The Answer
Evaluating the expressions
Let us first define A
again:
(require racket/trace)
(define (A x y)
(cond ((= y 0) 0)
((= x 0) (* 2 y))
((= y 1) 2)
(else (A (- x 1) (A x (- y 1))))))
(trace A)
Now evaluating:
(A 1 10)
>(A 1 10) > (A 1 9) > >(A 1 8) > > (A 1 7) > > >(A 1 6) > > > (A 1 5) > > > >(A 1 4) > > > > (A 1 3) > > > > >(A 1 2) > > > > > (A 1 1) < < < < < 2 > > > > >(A 0 2) < < < < <4 > > > > (A 0 4) < < < < 8 > > > >(A 0 8) < < < <16 > > > (A 0 16) < < < 32 > > >(A 0 32) < < <64 > > (A 0 64) < < 128 > >(A 0 128) < <256 > (A 0 256) < 512 >(A 0 512) <1024
The trace doesn't give us the picture as whole, unlike the substitution model. A
calls itself twice in the else clause. Once as a tail-call and once as parameter
to the tail-call. As the chain expands (represented by >
in the trace), we see
these inner calls. Once the innermost call is evaluated, and a value is achieved
(represented by <
in the trace) we start to see values of the inner calls become
substituted in the outer calls.
Similarly:
(A 2 4)
>(A 2 4) > (A 2 3) > >(A 2 2) > > (A 2 1) < < 2 > >(A 1 2) > > (A 1 1) < < 2 > >(A 0 2) < <4 > (A 1 4) > >(A 1 3) > > (A 1 2) > > >(A 1 1) < < <2 > > (A 0 2) < < 4 > >(A 0 4) < <8 > (A 0 8) < 16 >(A 1 16) > (A 1 15) > >(A 1 14) > > (A 1 13) > > >(A 1 12) > > > (A 1 11) > > > >(A 1 10) > > > > (A 1 9) > > > > >(A 1 8) > > > > > (A 1 7) > > > >[10] (A 1 6) > > > >[11] (A 1 5) > > > >[12] (A 1 4) > > > >[13] (A 1 3) > > > >[14] (A 1 2) > > > >[15] (A 1 1) < < < <[15] 2 > > > >[14] (A 0 2) < < < <[14] 4 > > > >[13] (A 0 4) < < < <[13] 8 > > > >[12] (A 0 8) < < < <[12] 16 > > > >[11] (A 0 16) < < < <[11] 32 > > > >[10] (A 0 32) < < < <[10] 64 > > > > > (A 0 64) < < < < < 128 > > > > >(A 0 128) < < < < <256 > > > > (A 0 256) < < < < 512 > > > >(A 0 512) < < < <1024 > > > (A 0 1024) < < < 2048 > > >(A 0 2048) < < <4096 > > (A 0 4096) < < 8192 > >(A 0 8192) < <16384 > (A 0 16384) < 32768 >(A 0 32768) <65536
(A 3 3)
>(A 3 3) > (A 3 2) > >(A 3 1) < <2 > (A 2 2) > >(A 2 1) < <2 > (A 1 2) > >(A 1 1) < <2 > (A 0 2) < 4 >(A 2 4) > (A 2 3) > >(A 2 2) > > (A 2 1) < < 2 > >(A 1 2) > > (A 1 1) < < 2 > >(A 0 2) < <4 > (A 1 4) > >(A 1 3) > > (A 1 2) > > >(A 1 1) < < <2 > > (A 0 2) < < 4 > >(A 0 4) < <8 > (A 0 8) < 16 >(A 1 16) > (A 1 15) > >(A 1 14) > > (A 1 13) > > >(A 1 12) > > > (A 1 11) > > > >(A 1 10) > > > > (A 1 9) > > > > >(A 1 8) > > > > > (A 1 7) > > > >[10] (A 1 6) > > > >[11] (A 1 5) > > > >[12] (A 1 4) > > > >[13] (A 1 3) > > > >[14] (A 1 2) > > > >[15] (A 1 1) < < < <[15] 2 > > > >[14] (A 0 2) < < < <[14] 4 > > > >[13] (A 0 4) < < < <[13] 8 > > > >[12] (A 0 8) < < < <[12] 16 > > > >[11] (A 0 16) < < < <[11] 32 > > > >[10] (A 0 32) < < < <[10] 64 > > > > > (A 0 64) < < < < < 128 > > > > >(A 0 128) < < < < <256 > > > > (A 0 256) < < < < 512 > > > >(A 0 512) < < < <1024 > > > (A 0 1024) < < < 2048 > > >(A 0 2048) < < <4096 > > (A 0 4096) < < 8192 > >(A 0 8192) < <16384 > (A 0 16384) < 32768 >(A 0 32768) <65536
As you can see the behaviour of Ackermann's function is quite erratic.
Defining the functions
Turning our attention to the single-arity procedures given above.
(f n)
(define (f n) (A 0 n))
(trace f)
This function directly evaluates to the second clause of A
, and doubles y:
(f 2)
>(f 2) >(A 0 2) <4
(f 3)
>(f 3) >(A 0 3) <6
Therefore it can be defined as:
\[f(n) = 2n\]
(g n)
(define (g n) (A 1 n))
(trace g)
This function simplifies into (A 0 (A 1 (- n 1)))
. The outer call simplifies
into (* 2 (A 1 (- n 1)))
. Thus, each call to (g n)
is just double of (g (- n
1))
, until the base case of (g 1)
(i.e. (A 1 1)
) is reached, whereby the value 2
is substituted. This is equivalent to:
(define (g n)
(if (= n 1)
2
(* 2 (g (- n 1)))))
Therefore, \(2\) is just multiplied \(n\) times:
(g 4)
>(g 4) >(A 1 4) > (A 1 3) > >(A 1 2) > > (A 1 1) < < 2 > >(A 0 2) < <4 > (A 0 4) < 8 >(A 0 8) <16
(g 6)
>(g 6) >(A 1 6) > (A 1 5) > >(A 1 4) > > (A 1 3) > > >(A 1 2) > > > (A 1 1) < < < 2 > > >(A 0 2) < < <4 > > (A 0 4) < < 8 > >(A 0 8) < <16 > (A 0 16) < 32 >(A 0 32) <64
We can define \(g(n)\) as:
\[g(n) = 2^n\]
(h n)
(define (h n) (A 2 n))
We can simplify (h n)
in terms of (g n)
and itself:
(define (h n)
(if (= n 1)
2
(g (h (- n 1)))))
This means (g n)
is chained till \(n\) equal \(1\). In other words, \(2\) is repeatedly exponentiated (i.e. tetrated) \(n\) times:
(h 1)
2
(h 2)
4
(h 3)
16
Using Knuth's up arrow notation, we can define \(h(n)\) as:
\[h(n) = 2 \uparrow\uparrow n\]