SICP Exercise 1.16: Iterative Exponentiation
2021-09-29
Categories: Programming
I am reading SICP.
Section 1.2.4 talks about the problem of computing the exponential of a given number.
The authors start with a recursive procedure:
#lang sicp
(define (expt b n)
(if (= n 0)
1
(* b (expt b (- n 1)))))
This requires O(n) steps and O(n) space.
then an iterative procedure:
#lang sicp
(define (expt b n)
(expt-iter b n 1))
(define (expt-iter b counter product)
(if (= counter 0)
product
(expt-iter b
(- counter 1)
(* b product))))
This version requires O(n) steps and O(1) space.
and finally he suggests a procedure which requires fewer steps by using successive squaring:
#lang sicp
(define (fast-expt b n)
(cond ((= n 0) 1)
((even? n) (square (fast-expt b (/ n 2))))
(else (* b (fast-expt b (- n 1))))))
This only requires O(log n) steps.
Design a procedure that evolves an iterative exponentiation process that uses successive squaring and uses a logarithmic number of steps, as does
fast-expt. (Hint: Using the observation that (bn/2)2 = (b2)n/2, keep, along with the exponentnand the baseb, an additional state variablea, and define the state transformation in such a way that the product abn is unchanged from state to state. At the beginning of the processais taken to be 1, and the answer is given by the value ofaat the end of the process. In general, the technique of defining an invariant quantity that remains unchanged from state to state is a powerful way to think about the design of iterative algorithms.)
Here’s my solution:
#lang sicp
(define (fast-expt b n)
(fast-expt-iter b n 1))
(define (fast-expt-iter b counter product)
(cond ((= counter 0)
product)
((even? counter)
(fast-expt-iter (square b)
(/ counter 2)
product))
(else
(fast-expt-iter b
(- counter 1)
(* b product)))))
Let’s see what is the difference between recursive and iterative version?
Enable debugging and run the recursive version:
$ racket 1.16-fast-expt-recursive.scm
>(fast-expt 2 10)
> (fast-expt 2 5)
> >(fast-expt 2 4)
> > (fast-expt 2 2)
> > >(fast-expt 2 1)
> > > (fast-expt 2 0)
< < < 1
< < <2
< < 4
< <16
< 32
<1024
1024
then the iterative version:
$ racket 1.16-fast-expt-iterative.scm
>(fast-expt 2 10)
>(fast-expt-iter 2 10 1)
>(fast-expt-iter 4 5 1)
>(fast-expt-iter 4 4 4)
>(fast-expt-iter 16 2 4)
>(fast-expt-iter 256 1 4)
>(fast-expt-iter 256 0 1024)
<1024
1024
The first thing you can easily spot is the recursive version requires more space.
The reason is after calling fast-expt recursively, Scheme must remember to either square or multiply the result with b depends on n is even or odd:
- (fast-expt 2 10) = (fast-expt 2 5)2
- (fast-expt 2 5) = (fast-expt 2 4) * 2
- …
In the iterative version, Scheme doesn’t have to remember anything: after it is done, it just returns the product.
Moreover, the iterative version also saves time:
$ time racket 1.16-fast-expt-recursive.scm
________________________________________________________
Executed in 540.91 millis fish external
usr time 407.01 millis 0.25 millis 406.77 millis
sys time 119.54 millis 1.84 millis 117.70 millis
$ time racket 1.16-fast-expt-iterative.scm
________________________________________________________
Executed in 518.87 millis fish external
usr time 389.04 millis 0.12 millis 388.92 millis
sys time 116.15 millis 1.09 millis 115.06 millis
References:
- http://www.owlnet.rice.edu/~comp210/96spring/Labs/lab09.html
- https://blog.moertel.com/posts/2013-05-11-recursive-to-iterative.html
Tags: sicp scheme recursion tail-recursion iteration
Quan Tong