0

In response to the following exercise from the SICP,

Exercise 1.3. Define a procedure that takes three numbers as arguments and returns the sum of the squares of the two larger numbers.

I wrote the following (correct) function:

(define (square-sum-larger a b c)
  (cond ((or (and (> a b) (> b c)) (and (> b a) (> a c))) (+ (* a a) (* b b)))
        ((or (and (> a c) (> c b)) (and (> c a) (> a b))) (+ (* a a) (* c c)))
        ((or (and (> b c) (> c a)) (and (> c b) (> b a))) (+ (* b b) (* c c)))))

Unfortunately, that is one of the ugliest functions I've written in my life. How do I

(a) Make it elegant, and

(b) Make it work for an arbitrary number of inputs?

Matt
  • 74,352
  • 26
  • 153
  • 180
Elliot Gorokhovsky
  • 3,610
  • 2
  • 31
  • 56
  • René : May I suggest that there is a [magic book](http://mitpress.mit.edu/books/little-schemer)? I am 90% sure you can find it , or at least an earlier edition, online for free. – Dmitri Feb 22 '15 at 01:54
  • 1
    Lots of answers here: http://stackoverflow.com/questions/161666/sicp-exercise-1-3-request-for-comments?rq=1 – Elliot Gorokhovsky Feb 22 '15 at 02:13
  • One quick way to make it more elegant is to remember `<` and `>` can take more than two arguments. `(> a b c)` can replace `(and (> a b) (> b c))` – WorBlux Feb 23 '15 at 00:56
  • You can also use a `(let ((f (lambda (x y) (+ (* x x) (* y y)))) ...)` to avoid repeating that from in the body.` – WorBlux Feb 23 '15 at 00:59

4 Answers4

4

I found an elegant solution (though it only works for 3 inputs):

(define (square-sum-larger a b c)
 (+ 
  (square (max a b))
  (square (max (min a b) c))))
Elliot Gorokhovsky
  • 3,610
  • 2
  • 31
  • 56
1

If you're willing to use your library's sort function, this becomes easy and elegant.

(define (square-sum-larger . nums)
  (define sorted (sort nums >))
  (let ((a (car sorted))
        (b (cadr sorted)))
    (+ (* a a) (* b b))))

In the above function, nums is a "rest" argument, containing a list of all arguments passed to the function. We just sort that list in descending order using >, then square the first two elements of the result.

Alexis King
  • 43,109
  • 15
  • 131
  • 205
1

I don't know if it's elegant enough but for a 3 argument version you can use procedure abstraction to reduce repetition:

(define (square-sum-larger a b c)
  (define (square x)
    (* x x))

  (define (max x y)
    (if (< x y) y x))

  (if (< a b)
      (+ (square b) (square (max a c)))
      (+ (square a) (square (max b c)))))

Make it work for an arbitrary number of inputs.

(define (square-sum-larger a b . rest)
  (let loop ((a (if (> a b) a b)) ;; a becomes largest of a and b
             (b (if (> a b) b a)) ;; b becomes smallest of a and b
             (rest rest))
    (cond ((null? rest) (+ (* a a) (* b b)))
          ((> (car rest) a) (loop (car rest) a (cdr rest)))
          ((> (car rest) b) (loop a (car rest) (cdr rest)))
          (else (loop a b (cdr rest))))))

A R6RS-version using sort and take:

#!r6rs
(import (rnrs)
        (only (srfi :1) take))

(define (square-sum-larger . rest)
  (apply + 
         (map (lambda (x) (* x x))
              (take (list-sort > rest) 2))))
Sylwester
  • 47,942
  • 4
  • 47
  • 79
1

You don't need to bother sorting you just need the find the greatest two.

(define (max-fold L)
  (if (null? L)
      #f 
      (reduce (lambda (x y) 
                 (if (> x y) x y))
              (car L)
              L)))

(define (remove-num-once x L)
 (cond ((null? L) #f)
       ((= x (car L)) (cdr L))
       (else (cons (car L) (remove-once x (cdr L))))))

(define (square-sum-larger . nums) 
   (let ((max (max-fold nums)))
     (+ (square max) 
        (square (max-fold (remove-num-once max nums)))))) 

(square-sum-larger 1 8 7 4 5 6 9 2)

;Value: 145
WorBlux
  • 1,423
  • 11
  • 20