Note: this wiki is now retired and will no longer be updated!

The static final versions of the pages are left as a convenience for readers. Note that meta-pages such as "discussion," "history," etc., will not work.

SICP exercise 2.03

From Drewiki
Jump to: navigation, search

Problem

Implement a representation for rectangles in a plane. (Hint: You may want to make use of exercise 2.2.) In terms of your constructors and selectors, create procedures that compute the perimeter and the area of a given rectangle. Then implement a different representation for rectangles. Can you design your system with suitable abstraction barriers, so that the same perimeter and area procedures will work using either representation?

Solution

Let's start with the point and segment representations from exercise 2.2:

(define (print-point p)
  (newline)
  (display "(")
  (display (x-point p))
  (display ",")
  (display (y-point p))
  (display ")")
  (newline))
 
(define (make-point x y)
  (cons x y))
 
(define (x-point p)
  (car p))
 
(define (y-point p)
  (cdr p))
 
(define (make-segment p1 p2)
  (cons p1 p2))
 
(define (start-segment p)
  (car p))
 
(define (end-segment p)
  (cdr p))

The rectangle constructor takes as arguments 3 points which define a right triangle:

 p1 +
    |
    |
 p2 +------+ p3

The line segment p1-p3 is the diagonal of the rectangle.

To find the area and perimeter of a rectangle represented by line segments, we need to know the rectangle's width and height. The width of the rectangle is the length of the line segment p2-p3, and the height is the length of p1-p2. To calculate the length of a line segment, we need a square root procedure:

<math>l = \sqrt{(x_1 - x_2)^2 + (y_1 - y_2)^2}</math>

Most modern Scheme implementations have a sqrt primitive, so let's use that rather than one of the sqrt procedures from chapter 1 of the text.

(define (square x) (* x x))
 
(define (segment-length s)
  (let ((p1 (start-segment s))
        (p2 (end-segment s)))
    (sqrt (+ (square (- (x-point p2)
                        (x-point p1)))
             (square (- (y-point p1)
                        (y-point p2)))))))

Let's test the segment-length procedure:

(define s1 (make-segment (make-point 0 0) (make-point 4 0)))
 
(segment-length s1)

Output:

4.0
(define s2 (make-segment (make-point -1 -1) (make-point -1 -7)))
 
(segment-length s2)

Output:

6.0
(define s3 (make-segment (make-point 5 7) (make-point 2 3)))
 
(segment-length s3)

Output:

5.0


Here are rectangle constructor and the width and height selectors:

(define (make-rectangle p1 p2 p3)
  (cons (make-segment p1 p2) (make-segment p2 p3)))
 
(define (rectangle-width r)
  (segment-length (cdr r)))
 
(define (rectangle-height r)
  (segment-length (car r)))

Test them (results shown are from Chicken Scheme 3.1 on a MacBook Pro running OS X 10.5):

(define r1 (make-rectangle (make-point 4 0) (make-point 0 0) (make-point 5 0)))
 
(rectangle-width r1)

Output:

5.0
(rectangle-height r1)

Output:

4.0
(define r2 (make-rectangle (make-point 2 7) (make-point 4 2) (make-point 9 4)))
 
(rectangle-width r2)

Output:

5.3851648071345
(rectangle-height r2)

Output:

5.3851648071345


And here are the area and perimeter procedures:

(define (rectangle-perimeter r)
  (+ (* 2 (rectangle-width r))
     (* 2 (rectangle-height r))))
 
(define (rectangle-area r)
  (* (rectangle-width r) (rectangle-height r)))

Test:

(rectangle-perimeter r1)

Output:

18.0
(rectangle-area r1)

Output:

20.0
(rectangle-perimeter r2)

Output:

21.540659228538
(rectangle-area r2)

Output:

29.0


The first implementation has a problem: the constructor doesn't ensure that the 3 points form a right triangle. If they don't, then the perimeter and area procedures don't produce the correct result.

We could fix the implementation by calculating the height of an arbitrary triangle, but there's a better way: instead of constructing a rectangle by specifying two line segments that define a triangle, we can instead provide to the constructor the two points which define the base of the rectangle, and then a scalar value for the rectangle's height. The line segments that define the sides of the rectangle are implicit in this definition, and we don't need to introduce any trigonometric calculations to find the height of a triangle.

Here's the new implementation. The constructor takes as arguments a single line segment, base, and a scalar value, height.

(define (make-rectangle base height)
  (cons base height))
 
(define (rectangle-width r)
  (segment-length (car r)))
 
(define (rectangle-height r)
  (cdr r))

Test:

(define r3 (make-rectangle (make-segment (make-point 0 0) (make-point 5 0)) 4))
 
(rectangle-width r3)

Output:

5.0
(rectangle-height r3)

Output:

4
(rectangle-perimeter r3)

Output:

18
(rectangle-area r3)

Output:

20.0
(define r4 (make-rectangle (make-segment (make-point 4 2) (make-point 9 4)) 5.3851648071345))
 
(rectangle-width r4)

Output:

5.3851648071345
(rectangle-height r4)

Output:

5.3851648071345
(rectangle-perimeter r4)

Output:

21.540659228538
(rectangle-area r4)

Output:

29.0
Personal tools