SICP 問題2.3

2017-09-12

平面上の長方形[rectangle]の表現を実装せよ.(ヒント: 問題2.2が使いたくなるであろう.)
構成子と選択子を使い, 与えられた長方形の周囲の長さ[perimeter]と面積[area]を計算する手続きを作れ.
次に長方形の違う表現を実装せよ.
同じ周囲の長さと面積の手続きが, どちらの表現でも働くように, システムは良好な抽象の壁で設計されているか.

色々自分で考えてみたのだけど、なんかうまくいかないので、長方形の表現方法については答えを見た。なるほどなるほど。
1つ目は「長方形を相隣る2辺の長さ」で表現する方法。

;; point method
(define (make-point x y) (cons x y))
(define (x-point p) (car p))
(define (y-point p) (cdr p))
(define (mid-point a b)
  (if (or (and (>= a 0) (>= b 0)) (and (< a 0) (< b 0)))
      (/ (+ a b) 2)
      (+ a b)))
(define (print-point p)
  (display "(")
  (display (x-point p))
  (display ",")
  (display (y-point p))
  (display ")")
  (newline))
(define (compare-point compare-operator f p1 p2)
  (if (compare-operator (f p1) (f p2))
      (f p1)
      (f p2)))
(define (bigger-x-point p1 p2)
  (compare-point > x-point p1 p2))
(define (bigger-y-point p1 p2)
  (compare-point > y-point p1 p2))
(define (smaller-x-point p1 p2)
  (compare-point < x-point p1 p2))
(define (smaller-y-point p1 p2)
  (compare-point < y-point p1 p2))

;; segment method
(define (make-segment start-point end-point) (cons start-point end-point))
(define (start-segment segment) (car segment))
(define (end-segment segment) (cdr segment))
(define (midpoint-segment segment)
  (let ((start-point (start-segment segment)) (end-point (end-segment segment)))
        (cons
          (mid-point (x-point start-point) (x-point end-point))
          (mid-point (y-point start-point) (y-point end-point)))))
(define (segment-length seg)
  (let ((s (start-segment seg)) (e (end-segment seg)))
       (if (= (- (bigger-x-point s e) (smaller-x-point s e)) 0)
           (- (bigger-y-point s e) (smaller-y-point s e))
           (- (bigger-x-point s e) (smaller-x-point s e)))))

;; rectangle method
(define (make-rectangle seg1 seg2) (cons seg1 seg2))
(define (width-segment rect) (car rect))
(define (height-segment rect) (cdr rect))

(define (rect-width-length rect)
  (segment-length (width-segment rect)))
(define (rect-height-length rect)
  (segment-length (height-segment rect)))

(define (calc-rect-area rect)
  (* (rect-width-length rect) (rect-height-length rect)))
(define (calc-rect-perimeter rect)
  (+ (* (rect-width-length rect) 2) (* (rect-height-length rect) 2)))

;;;;;;;;;;;;;;;;;;;;;;;
;; test
;;;;;;;;;;;;;;;;;;;;;;;
(define s1 (make-point 1 1))
(define e1 (make-point 5 1))
(define seg1 (make-segment s1 e1))

(define s2 (make-point 1 1))
(define e2 (make-point 1 3))
(define seg2 (make-segment s2 e2))

(define rect1 (make-rectangle seg1 seg2))
(print (calc-rect-area rect1))
; => 8
(print (calc-rect-perimeter rect1))
; => 12

(define s3 (make-point 0 0))
(define e3 (make-point 10 0))
(define seg3 (make-segment s3 e3))

(define s4 (make-point 0 0))
(define e4 (make-point 0 10))
(define seg4 (make-segment s4 e4))

(define rect2 (make-rectangle seg3 seg4))
(print (calc-rect-area rect2))
; => 100
(print (calc-rect-perimeter rect2))
; => 40

こんな感じかな?無駄に長くなってしまった。

2つ目は「辺がx軸, y軸に平行な長方形を対角で相対する頂点の座標」で表現する方法。
こちらは変更点のみ記載。

長方形の表現方法が変わるので、そこの部分のみ変更。抽象化しているのでそれだけで他のメソッドはそのまま使える。

(define (make-rectangle p1 p2) (cons p1 p2))
(define (start-point rect) (car rect))
(define (end-point rect) (cdr rect))

(define (width-segment rect)
  (make-segment
    (start-point rect)
    (make-point
      (x-point (end-point rect))
      (y-point (start-point rect)))))
(define (height-segment rect)
  (make-segment
    (start-point rect)
    (make-point
      (x-point (start-point rect))
      (y-point (end-point rect)))))