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
下面这段代码,画了一个小头像
;定义一些画刷
(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 (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
(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,绘制线段列表
(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
完整的代码在这里:

注:这是一篇读书笔记,所以其中的内容仅 属个人理解而不代表SICP的观点,并随着理解的深入其中 的内容可能会被修改
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· [AI/GPT/综述] AI Agent的设计模式综述