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的观点,并随着理解的深入其中 的内容可能会被修改

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
"SICP中文版"是指计算机科学经典教材《Structure and Interpretation of Computer Programs》(计算机程序的构造和解释)的中文翻译PDF版。这本教材由麻省理工学院的Harold Abelson和Gerald Jay Sussman等人编写,是计算机科学领域中一本重要的教材。 "SICP中文版PDF"提供了更方便的学习方式。无论是学生、程序员还是计算机科学爱好者,都可以在任何时候通过电子设备访问和学习这本教材。使用PDF格式的好处是可以在不同的平台上都能打开和阅读,而不受限于特定的操作系统或设备。 通过"SICP中文版PDF",读者可以学习计算机科学的基本原理和概念,如过程、数据抽象、递归、高阶函数、并发等。这本教材以Scheme语言为示例,帮助读者理解计算机程序的结构、设计和解释。通过逐步的案例和练习,读者可以锻炼解决问题和编写高质量代码的能力。 "SICP中文版PDF"也提供了沟通和讨论的平台。读者可以通过在线社群或论坛,与其他人分享学习心得、解答疑问和参与讨论。这为读者提供了一个学习交流的机会,促进了学习者之间的互动和共同成长。 总之,"SICP中文版PDF"是一本经典的计算机科学教材的中文翻译版本,使得更多的读者可以方便地学习和掌握其中的知识。无论是对于计算机科学专业的学生还是对计算机科学感兴趣的人,这本教材都是一本很好的参考书,并提供了丰富的实例和练习,让读者深入理解计算机程序的核心概念和设计原则。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值