Workshop on Scheme

Answers to exercises: Monday, July 17

The authors' answers to the exercises from Scheme and the art of programming are available only on MathLAN and only during the week of the workshop. Click here.


Scheme's name for the third procedure is caddr. It is described on page 16 of the Revised(4) report.


Exercises 2.21 and 3.5 in Scheme and the art of programming differ from the exercises that surround them in that in each case the simplest and most natural approach is unsatisfying. In exercise 2.21, the beginner is likely to write something like

(define all-same?
  (lambda (ls)
    (cond ((null? ls) #t)
          ((null? (cdr ls)) #t)
          ((equal? (car ls) (cadr ls))
           (all-same? (cdr ls)))
          (else #f))))
which works, but may make the more sensitive and thoughtful students uneasy: Each recursive call begins by testing whether the operand is the empty list, even though this test is needed only for the initial call and cannot succeed otherwise. This is also the only exercise in chapter 2 in which the object that is passed to the recursive call (the cdr of ls) is taken apart and partially examined before the recursive call, which may make students faintly uncomfortable even though it is correct.

The solution to both problems is to use a helper procedure and an iterative process:

(define all-same?
  (lambda (ls)
    (or (null? ls) (all-same?-helper (car ls) (cdr ls)))))

(define all-same?-helper
  (lambda (fore aft)
    (or (null? aft)
        (and (equal? fore (car aft))
             (all-same?-helper (car aft) (cdr aft))))))
or, once the named let expression has been covered:
(define all-same?
  (lambda (ls)
    (or (null? ls)
        (let loop ((fore (car ls))
                   (aft (cdr ls)))
          (or (null? aft)
              (let ((new-fore (car aft)))
                (and (equal? fore new-fore)
                     (loop new-fore (cdr aft)))))))))
In exercise 3.5, the problem is more serious. Students will tend to write
(define index
  (lambda (a ls)
    (cond ((null? ls) -1)
          ((equal? a (car ls)) 0)
          (else (add1 (index a (cdr ls)))))))
which unfortunately gives the wrong answer in the case of an unsuccessful search of a non-empty list, since 1 is added to the failure signal at the return from each recursive call. One can make the procedure shown above into a helper, invoked by an interface procedure only after a call to member? has revealed that the search will be successful, but this strikes me as an inefficient kludge. A coding like
(define failure -1)

(define add1-if-successful
  (lambda (result)
    (if (equal? result failure)
        failure
        (add1 result))))

(define index
  (lambda (a ls)
    (cond ((null? ls) failure)
          ((equal? a (car ls)) 0)
          (else (add1-if-successful (index a (cdr ls)))))))
seems slightly more elegant, but again will win no prizes for efficiency. The best solution is to use the iterative approach that is not explained until chapter 4:
(define index-it
  (lambda (a ls level)
    (cond ((null? ls) -1)
          ((equal? a (car ls)) level)
          (else (index-it a (cdr ls) (add1 level))))))

(define index
  (lambda (a ls)
    (index-it a ls 0)))
or, more concisely,
(define (index a ls)
  (let loop ((rest ls)
             (level 0))
    (cond ((null? rest) -1)
          ((equal? a (car rest)) level)
          (else (loop (cdr rest) (add1 level))))))


(define conjugate
  (lambda (z)
    (make-rectangular (real-part z)
                      (- (imag-part z)))))


(define hypotenuse
  (lambda (leg-1 leg2)
    (sqrt (+ (* leg-1 leg-1) (* leg-2 leg-2)))))
or, for extra credit,
(define hypotenuse
  (lambda (leg-1 leg-2)
    (magnitude (make-rectangular leg-1 leg-2))))


(rationalize 314/100 1/100) => 22/7, the ``simplest'' rational in the range from 313/100 to 315/100.


Appendix C4 of the IEEE standard presents essentially this algorithm, contributed by Alan Bawden and based on a discussion in G. H. Hardy and E. M. Wright, Introduction to the theory of numbers (New York: Oxford University Press; fifth edition, 1979).

(define rationalize
  (lambda (x epsilon)
    (simplest-in-range (r- x epsilon) (r+ x epsilon))))

(define simplest-in-range
  (lambda (bottom top)
    (cond ((r< top bottom)
           (simplest-in-range top bottom))
          ((r= bottom top)
           bottom)
          ((rpositive? bottom)
           (simplest-in-range-helper bottom top))
          ((rnegative? top)
           (rminus (simplest-in-range-helper (rminus top) (rminus bottom))))
          (else (make-ratl 0 1)))))

(define simplest-in-range-helper   ; precondition: 0 < bottom < top
  (lambda (bottom top)
    (if (= (denr bottom) 1)
        bottom
        (let ((bottom-integer (rtruncate bottom))
              (top-integer (rtruncate top)))
          (if (= bottom-integer top-integer)
              (r+ (make-ratl bottom-integer 1)
                  (rinvert (simplest-in-range-helper
                            (rinvert (rfrac top))
                            (rinvert (rfrac bottom)))))
              (make-ratl (add1 bottom-integer) 1))))))

(define rtruncate
  (lambda (rtl)
    (quotient (numr rtl) (denr rtl))))

(define rfrac
  (lambda (rtl)
    (make-ratl (remainder (numr rtl) (denr rtl)) (denr rtl))))


This document is available on the World Wide Web as

http://www.math.grin.edu/~stone/events/scheme-workshop/Monday-answers.html


created July 15, 1995
last revised July 16, 1995

John David Stone (stone@math.grin.edu)