Have I uncovered an actual error in the SICP book? It says:
Exercise 3.27: Memoization (also called tabulation) is a technique that enables a procedure to record, in a local table, values that have previously been computed. This technique can make a vast difference in the performance of a program. A memoized procedure maintains a table in which values of previous calls are stored using as keys the arguments that produced the values. When the memoized procedure is asked to compute a value, it first checks the table to see if the value is already there and, if so, just returns that value. Otherwise, it computes the new value in the ordinary way and stores this in the table. As an example of memoization, recall from 1.2.2 the exponential process for computing Fibonacci numbers:
(define (fib n)
(cond ((= n 0) 0)
((= n 1) 1)
(else (+ (fib (- n 1))
(fib (- n 2))))))
The memoized version of the same procedure is
(define memo-fib
(memoize
(lambda (n)
(cond ((= n 0) 0)
((= n 1) 1)
(else
(+ (memo-fib (- n 1))
(memo-fib (- n 2))))))))
where the memoizer is defined as
(define (memoize f)
(let ((table (make-table)))
(lambda (x)
(let ((previously-computed-result
(lookup x table)))
(or previously-computed-result
(let ((result (f x)))
(insert! x result table)
result))))))
and then it says
Explain why memo-fib computes the nth Fibonacci number in a number of steps proportional to N.
The insert!
and lookup
procedures are defined in the book as follows:
(define (lookup key table)
(let ((record (assoc key (cdr table))))
(if record
(cdr record)
false)))
(define (assoc key records)
(cond ((null? records) false)
((equal? key (caar records))
(car records))
(else (assoc key (cdr records)))))
(define (insert! key value table)
(let ((record (assoc key (cdr table))))
(if record
(set-cdr! record value)
(set-cdr! table
(cons (cons key value)
(cdr table)))))
'ok)
Now, assoc
has number of steps proportional to n
. And since lookup
and insert!
use assoc
, they both have number of steps proportional to N.
I do not understand how memo-fib
has a number of steps proportional to N. My observations are:
- Due to the definition of the argument to
memo-fib
(the lambda which hasn
as the formal parameter), the table would have mostly ordered keys, And the keys would be looked up in an ordered way. So it is safe to assume any call to lookup would be close to a constant time operation. Insert!
on the other hand will not be aware that the keys would be added in some order. If a value does not exist in the table,insert!
will always scan the whole list, so it would have number of steps proportional ton
every time.- If we have
n-1
elements in the table and we wish to compute(memo-fib n)
, it would have number of steps proportional ton
due to theassoc
ininsert!
. - If we have no keys, then
(memo-fib n)
would have number of steps proportional to n2 due toinsert!
being called every recursive call tomemo-fib
.
If lookup
and insert!
are constant then it would make sense for memo-fib
to have number of steps proportional to n. But the real number of steps looks like n * (n-k) where k is the number of keys already in the table.
Am I doing it wrong? What am I missing?