Elliot Gorokhovsky
Elliot Gorokhovsky

Reputation: 3762

How to make this function elegant

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?

Upvotes: 0

Views: 92

Answers (4)

WorBlux
WorBlux

Reputation: 1413

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

Upvotes: 1

Elliot Gorokhovsky
Elliot Gorokhovsky

Reputation: 3762

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))))

Upvotes: 4

Sylwester
Sylwester

Reputation: 48745

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))))

Upvotes: 1

Alexis King
Alexis King

Reputation: 43852

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.

Upvotes: 1

Related Questions