SICP学习笔记 (2.2.4)

                                                            SICP学习笔记 (2.2.4)
                                                                    周银辉

 

1,Scheme的GUI编程

很幸运的是,PLT scheme提供了GUI库,叫做“MrEd”,在DrScheme中可以直接使用,但需要在IDE的左下角将语言选择为Module,并且在代码开始处加上#lang scheme/gui,具体的语法信息可以参考这里:http://docs.plt-scheme.org/gui/index.html

 下面这段代码,画了一个小头像

#lang scheme/gui

;定义一些画刷
(define no
-pen (make-object pen% "BLACK" 1 'transparent))
(define red-pen (make-object pen% "RED" 2 'solid))
(define black-pen (make-object pen% "BLACK" 2 'solid))
(define no-brush (make-object brush% "BLACK" 'transparent))
(define yellow-brush (make-object brush% "YELLOW" 'solid))
(define red-brush (make-object brush% "RED" 'solid))

;定义图形
(define (draw
-face dc)
  (send dc set
-smoothing 'smoothed)
  (send dc set-pen black-pen)
  (send dc set
-brush no-brush)
  (send dc draw
-ellipse 50 50 100 100)
  (send dc set
-brush yellow-brush)
  (send dc draw
-line 70 100 90 100)
  (send dc draw
-ellipse 50 90 20 20)
  (send dc draw
-ellipse 90 90 20 20)
  (send dc set
-brush no-brush)
  (send dc set
-pen red-pen)
  (let ([
-pi (atan 0 -1)])
    (send dc draw
-arc 50 60 60 80 (* 3/2 -pi) (* 7/4 -pi))))

;定义一个窗口
(define myWindow (new frame
% [label "example window"
                   [width 
300] [height 300]))

;定义一个面板,附着在刚才的窗口上
(define myCanvas (new canvas
% 
                      [parent myWindow]
                      ;事件处理,Paint回调时将draw
-face
                      [paint
-callback (lambda (canvas dc) (draw-face dc))]))

(send myWindow show 
#t)

 



2,向量和向量操作

我这里用List来定义的向量,其实也可以用cons以及其他任何可行的方式,但都比较简单:

(define (make-vect x y) (list x y))

(define (xcor-vect v) (car v))

(define (ycor-vect v) (cadr v))

(define (add-vect v1 v2)
  (make-vect (+ (xcor-vect v1) (xcor-vect v2))
             (+ (ycor-vect v1) (ycor-vect v2))))

(define (sub-vect v1 v2)
  (make-vect (- (xcor-vect v1) (xcor-vect v2))
             (- (ycor-vect v1) (ycor-vect v2))))

(define (scale-vect s v)
  (make-vect (* s (xcor-vect v))
             (* s (ycor-vect v))))

(define (length v)
  (sqrt (+ (* (xcor-vect v) (xcor-vect v))  (* (ycor-vect v) (ycor-vect v)))))

(define (sinθ v)
  (/ (ycor-vect v) (length v)))

(define (cosθ v)
  (/ (xcor-vect v) (length v)))

(define (rotation-vect v θ)
  (let ((x (xcor-vect v))
        (y (ycor-vect v)))
    (make-vect (- (* x (cos θ)) (* y (sin θ)))
               (+ (* x (sin θ)) (* y (cos θ))))))

 其中length是求向量的长度, sinθ和cosθ是求向量与x轴夹角的正弦与余弦值。 rotation-vect将向量绕X轴旋转θ度(弧度)

 

 

3, 定义Frame

 

(define (make-frame origin edge1 edge2)
  (list origin edge1 edge2))

(define (origin-frame f)
  (car f))

(define (edge1-frame f)
  (cadr f))

(define (edge2-frame f)
  (caddr f))


(define (frame-coord-map frame)
  (lambda (v)
    (add-vect
     (origin-frame frame)
     (add-vect (scale-vect (xcor-vect v)
                           (edge1-frame frame))
               (scale-vect (ycor-vect v)
                           (edge2-frame frame))))))

我这里只采用的List的方式来定义,练习2.47中要求用list和cons两种方式,cons方式这里就不给出了,依葫芦画瓢即可

 

 

4,定义线段

 

(define (make-segment v-start v-end)
  (cons v-start v-end))

(define (start-segment seg)
  (car seg))

(define (end-segment seg)
  (cdr seg))


(define (draw-segment dc seg)
  (let ((v-start (start-segment seg))
        (v-end (end-segment seg)))
    (send dc draw-line
      (xcor-vect v-start)
      (ycor-vect v-start)
      (xcor-vect v-end)
      (ycor-vect v-end))))

 

其中draw-segment 方法是关键,其用一个指定的dc来绘制线段,由于MrEd中绘制线段时要求传入的是x1 y1 x2 y2四个数值而非点坐标,所以上稍稍转换了一下

 

5,绘制线段列表

 

(define (segments->painter dc segment-list)
  (lambda (frame)
    (for-each
      (lambda (segment)
        (let ((new-start-segment ((frame-coord-map frame) (start-segment segment)))
              (new-end-segment ((frame-coord-map frame) (end-segment segment))))
        (draw-segment
          dc
          (make-segment new-start-segment new-end-segment))))
      segment-list)))

一个for-each语句就可以搞定了,但需要注意的是这里将frame拉了进来,所以在调用draw-segment时传入的点坐标必须是经过frame映射之后的,也就是我们上面的new-start-segment 和 new-end-segment

 

 

6,一个简单的实例

 

经过上面5点的预备知识,我们现在便可以定义一个线段列表来绘制一个由线段组成的图形了,下面是一个简单的示例代码:

 

#lang scheme/gui

;---------------vector---------------------------
(define (make-vect x y) (list x y))

(define (xcor-vect v) (car v))

(define (ycor-vect v) (cadr v))

(define (add-vect v1 v2)
  (make-vect (+ (xcor-vect v1) (xcor-vect v2))
             (+ (ycor-vect v1) (ycor-vect v2))))

(define (sub-vect v1 v2)
  (make-vect (- (xcor-vect v1) (xcor-vect v2))
             (- (ycor-vect v1) (ycor-vect v2))))

(define (scale-vect s v)
  (make-vect (* s (xcor-vect v))
             (* s (ycor-vect v))))

(define (length v)
  (sqrt (+ (* (xcor-vect v) (xcor-vect v))  (* (ycor-vect v) (ycor-vect v)))))

(define (sinθ v)
  (/ (ycor-vect v) (length v)))

(define (cosθ v)
  (/ (xcor-vect v) (length v)))

(define (rotation-vect v θ)
  (let ((x (xcor-vect v))
        (y (ycor-vect v)))
    (make-vect (- (* x (cos θ)) (* y (sin θ)))
               (+ (* x (sin θ)) (* y (cos θ))))))

;---------------Frame---------------------------
(define (make-frame origin edge1 edge2)
  (list origin edge1 edge2))

(define (origin-frame f)
  (car f))

(define (edge1-frame f)
  (cadr f))

(define (edge2-frame f)
  (caddr f))


(define (frame-coord-map frame)
  (lambda (v)
    (add-vect
     (origin-frame frame)
     (add-vect (scale-vect (xcor-vect v)
                           (edge1-frame frame))
               (scale-vect (ycor-vect v)
                           (edge2-frame frame))))))


;---------------segment---------------------------

(define (make-segment v-start v-end)
  (cons v-start v-end))

(define (start-segment seg)
  (car seg))

(define (end-segment seg)
  (cdr seg))


(define (draw-segment dc seg)
  (let ((v-start (start-segment seg))
        (v-end (end-segment seg)))
    (send dc draw-line
      (xcor-vect v-start)
      (ycor-vect v-start)
      (xcor-vect v-end)
      (ycor-vect v-end))))


(define (segments->painter dc segment-list)
  (lambda (frame)
    (for-each
      (lambda (segment)
        (let ((new-start-segment ((frame-coord-map frame) (start-segment segment)))
              (new-end-segment ((frame-coord-map frame) (end-segment segment))))
        (draw-segment
          dc
          (make-segment new-start-segment new-end-segment))))
      segment-list)))

;---------------------------------------------------------

(define red-pen (instantiate pen% ("RED" 2 'solid)))

;一个线段列表  -_-!
(define mySegmentList
  (list
    (make-segment
      (make-vect 0.1 0.4)
      (make-vect 0.3 0.4))
    (make-segment
      (make-vect 0.5 0.4)
      (make-vect 0.7 0.4))
    (make-segment
      (make-vect 0.3 0.6)
      (make-vect 0.5 0.6))
    (make-segment
      (make-vect 0.8 0.3)
      (make-vect 0.8 0.55))
    (make-segment
      (make-vect 0.78 0.6)
      (make-vect 0.80 0.6))
    (make-segment
      (make-vect 0.9 0.3)
      (make-vect 0.9 0.55))
    (make-segment
      (make-vect 0.88 0.6)
      (make-vect 0.90 0.6))))

;定义我们的Frame
(define myFrame
  (make-frame
    (make-vect 0 0)
    (make-vect 200 0)
    (make-vect 0 200)))

;定义一个窗口
(define myWindow (new frame% [label "example window"]
                   [width 300] [height 300]))

;定义一个面板,附着在刚才的窗口上
(define myCanvas (new canvas%
                      [parent myWindow]
                      ;事件回调    
                      [paint-callback (lambda (canvas dc)
                                        (begin
                                          (send dc set-pen red-pen)
                                          ( (segments->painter dc mySegmentList) myFrame)))]))

(send myWindow show #t)


运行效果如下:

 

 

 

7,beside 和 below

 

其实在SICP本节的最后是给了beside方法的(below被留成了练习2.51),但它们都是基于transform-painter方法的,在学会transform-painter 方法之前,我们还是有办法做到了,运用一点三角函数的知识就可以了(准备一张草稿纸,画画直角坐标系和三角函数):

 

(define (beside painter1 painter2)
  (lambda (frame)
    (let ((f1 (make-frame
               (origin-frame frame)
               (make-vect
                (* (/ (length (edge1-frame frame)) 2.0) (cosθ (edge1-frame frame)))
                (* (/ (length (edge1-frame frame)) 2.0) (sinθ (edge1-frame frame))))
               (edge2-frame frame )))
          (f2 (make-frame
               (make-vect
                (* (/ (length (edge1-frame frame)) 2.0) (cosθ (edge1-frame frame)))
                (* (/ (length (edge1-frame frame)) 2.0) (sinθ (edge1-frame frame))))
               (make-vect (/ (xcor-vect(edge1-frame frame)) 2.0) (/ (ycor-vect(edge1-frame frame)) 2.0))
               (edge2-frame frame ))))
      (painter1 f1)
      (painter2 f2))))


(define (below painter1 painter2)
  (lambda (frame)
    (let ((f1 (make-frame
               (origin-frame frame)              
               (edge1-frame frame )
               (make-vect
                (* (/ (length (edge2-frame frame)) 2.0) (cosθ (edge2-frame frame)))
                (* (/ (length (edge2-frame frame)) 2.0) (sinθ (edge2-frame frame))))))
          (f2 (make-frame
               (make-vect
                (* (/ (length (edge2-frame frame)) 2.0) (cosθ (edge2-frame frame)))
                (* (/ (length (edge2-frame frame)) 2.0) (sinθ (edge2-frame frame))))
               (edge1-frame frame )
               (make-vect (/ (xcor-vect(edge2-frame frame)) 2.0) (/ (ycor-vect(edge2-frame frame)) 2.0)))))
      (painter1 f1)
      (painter2 f2))))


 

 上面的代码有不少语句是重复的,你可以用let变量重构一下,然后看看我们的below效果:

 

 

 

8,练习2.45

(define (split combine-main combine-smaller)
  (lambda (painter n)
    (if (zero? n)
      painter
      (let ((smaller ((split combine-main combine-smaller) painter (- n 1))))
        (combine-main
          painter
          (combine-smaller smaller smaller))))))

 

 

9,练习2.46,2.47,2.48,2.49

2.46、2.47、2.48 前面已经给出答案了哈,copy 一下吧。2.49的直接略掉

 

 

10,练习2.50

(define (rotate90 painter)
  (transform-painter
    painter
    (make-vect 0.0 1.0)     ; new origin
    (make-vect 0.0 0.0)     ; new end of edge1
    (make-vect 1.0 1.0)))   ; new end of edge2

(define (rotate180 painter)
  (transform-painter
    painter
    (make-vect 1.0 1.0)
    (make-vect 0.0 1.0)
    (make-vect 1.0 0.0)))

(define (rotate270 painter)
  (transform-painter
    painter
    (make-vect 1.0 0.0)
    (make-vect 1.0 1.0)
    (make-vect 0.0 0.0)))

 

(define (flip-horiz painter)
  (transform-painter
    painter
    (make-vect 1.0 0.0)
    (make-vect 0.0 0.0)
    (make-vect 1.0 1.0)))

 

11,练习2.51

(define (below painter1 painter2)
  (let ( (split-point (make-vect 0.0 0.5))
          (paint-up
            (transform-painter
              painter2
              (make-vect 0.0 0.0)
              (make-vect 1.0 0.0)
              split-point))
          (paint-down
            (transform-painter
              painter1
              split-point
              (make-vect 1.0 0.5)
              (make-vect 0.0 1.0))))
    (lambda (frame)
      (paint-up frame)
      (paint-down frame))))

 

12,练习2.52

(define (corner-split painter n)
  (if (zero? n)
    painter
    (let ( (up (up-split painter (- n 1)))
            (right (right-split painter (- n 1)))
            (top-left up)
            (bottom-right right)
            (corner (corner-split painter (- n 1))))
      (beside (below painter top-left)
              (below bottom-right corner)))))

 

13,Functional Geometry

本节中所有的这些图形变换统称为“Functional Geometry ”,有专门的站点介绍这个: http://www.frank-buss.de/lisp/functional.html 
完整的代码在这里:

Functional Geometry (Common Lisp)

 

注:这是一篇读书笔记,所以其中的内容仅 属个人理解而不代表SICP的观点,并随着理解的深入其中 的内容可能会被修改

posted @ 2009-11-26 17:16  周银辉  阅读(2275)  评论(1编辑  收藏  举报