上节课讲了关于序对的构建使用(cons),闭包以及过程和参数抽象的思考
https://blog.csdn.net/Changed117/article/details/109494109
视频地址
https://www.bilibili.com/video/BV1Xx41117tr
通过序对创建一个坐标向量

注意这里的参数和上次的略微不同,不再传入两个参数,而是一个序对(cons),他们没有什么不同。
根据闭包的定义,序对可以非常随意的自由组合,例如一个向量(两个坐标组合)

又或者更加随意的组合,比如我们要组合1234,有很多种组合方式

因此lisp中产生了某种约定,将序对组合成序对链来组合表示数据(类似单向链表的结构)

这是他的构造方式

这样就可以用CAR和CDR取出链表中的数据

使用CDR来取出表中的剩余数据,他表示的就是第一个序对的CDR部分所代表的数据

那么在表中如果我们想定义一个通用的处理过程。将表中所有的元素全部10,结果应该是这样的

该如何定义这个过程呢?如果使用递归策略,不停地缩放表,将取出的CAR部分10再创建一个新表,把处理过的数据用定义序对的方式加入新的序对,处理到表尾的时候返回表末指针。过程是这样的,注意看这个过程

将s表缩放l倍,首先判断这个表是不是空表,空表返回表末指针,然后创建一个序对,接收表的car部分将他乘l,然后下面的过程递归调用,用表的cdr部分(也就像上面说的剩下的部分的集合)拿去继续缩放l倍,继续组合就获得了一个缩放后的表。这里创建了一个新表,并不是原来的那个表了。
既然可以这样实现的话,那就不能止步于乘,有一个通用过程MAP

传入l为表,p为过程两个参数,和上面的处理方式一样,新建一个表,将所有的car部分经过p过程处理然后组合在一起。
使用MAP来处理(1.2.3.4)*10的方法就是
如果掌握了这种通用模式。而不是一种针对特定解决的模式,你就会发现共性。你只要给出一个处理过程,和一个表,他就可以求出你想要的结果,你甚至不需要关心他是如何实现的

还有一个非常接近于MAP的处理方式,for-each
他们的不同之处只是在于返回值,MAP返回的是一个新的表,但是for-each返回的是原来那个表,只是进行了处理(一个是else,一个是cons),其中MAP是一个递归过程(你需要得到整个表的所有参数处理后返回的末尾指针来完成你构建的新表)for-each则不需要,所以for-each是一个迭代的过程。
然后接下来介绍了教授的朋友通过lisp完成的一个图像生成系统,这个系统涵盖了之前学到的闭包,数据抽象,表过程,以及公理抽象。

这个系统是用来画这样的图的,通过把一个图片做旋转缩放等操作变成如下的形式

教授再次强调了本课的主题,过程和数据并没有本质上的区别
让我们来看下这个语言,这个语言的基本元素是“图像”但是此图像非彼图像来看下例子,这是George(老朋友了)

这里的图像指的是给定一个矩形空间,他会自动帮你填充你想要的图案,比如他们都是George,仅仅是给了不同的矩形区域让他填充

基本元素有了,接下来是其中的一些操作,例如旋转九十度就是如下的效果

还有BESIDE A B S其中AB是图像,而S是一个0~1之间的数字,举个例子S=0.5的时候结果是这样的

S为0.25的时候呢。它就变成了这样,这就是组合方法之一BESIDE,ABOVE则是在水平方向上做这样的操作。类似方法还有很多

例如这样的图片要如何生成

用BESID(S=0.5)生成左半边的G再使用ABOVE(S=0.5)生成右半边的G再将他们组合在一起,又或者有个图像P,是通过BSEIDE以及翻转180°的G生成的(ROT-180)

然后使用P做ABOVE和他被翻转后的图片生成,就变成了这样

为何我们可以如此迅速的增加复杂度呢?答案还是之前说过的闭包性质不管如何操作BSEID也好ABOVE也好,他们得到的结果都是闭包的,都是图像,而图像可以拿去继续进行下一次组合。所以不论何时我都可以以一个东西为基本元素去构造更复杂的东西
那么我们来探究一下他是如何实现的吧
其中有一个基本元素矩形,他由三个向量构成圆点(ORGIN),水平向量(HORIZ)以及垂直向量(VERT),用于计算左上和右下的坐标。

忽略细节,假设已经有了选择和构造函数

下面需要考虑的是如何选取图像让他缩放以适应你给出的矩形,有一种想法是这样的,无论何时给定一个矩形,都可以认为他是一个正方形缩放而来的,可以通过端点映射和拉伸来完成变换



好家伙,盲区来了,不过也不打紧,明白人家意思就行,原谅我数学炸裂,我直接把他的公式贴出来

通过公式将原本在正方形中的XY两点确定为缩放后在矩形中的位置,并且连接就完成了变化。

呜呜呜呜
这个过程用程序表示就是一个coord-map的过程,其中传入了一个矩形作为参数(rect),返回了一个以点为参数的过程point。参考上述过程,用圆点和矩形的horiz通过pointX的处理和矩形的vert部分通过point的Y的处理相加,就得到了缩放变化后的点(就是用哪个看不懂的公式把点从正方形变到对应的长方形上面)可以参考上面截图的公式。这个过程由每个矩形自己生成。

举个例子,将下图的图像变化为拉长版本

通过矩形自动生成的COORD-MAP,对每一个线段进行计算,得到线段的起点和终点,再将其相连,遍历一遍所有的线段,可以完成缩放后的图像


那么他要如何使用呢?
通过MAKE-RECT来构建矩形(省略部分为向量的部分,矩形由向量组成)
通过MAKE-PICT来生成图片,就是上面那个lisp程序,省略部分为线段的表,代表了图片中所有的线段,然后将矩形传入MAKE-PICT作为参数,无论G代表的是什么,他都会在矩形R中被生成出来(注意这里的G是一个过程,他接受一个矩形作为参数用于生成出被矩形表示的图片)

那么回过头来讨论,为什么说这个例子很好呢?他不奇怪吗?
他究竟好在哪里呢?我们来看一些例子
我们需要构建两个图片P1和P2,只需要给他们各自传递一个矩形就可以,因为这里的图像是一个过程,你传递给他一个矩形,他就会在矩形上画图

当我们要实现BESIDE这个过程的时候呢?我们将P1P2传进去,并且添加一个缩放参数,BESIDE做了什么呢?他只是通过A参数计算组成矩形的几个向量,最后算出了两个矩形给P1P2,剩下的绘图都是由图片自己完成的。


图中是BESIDE的代码。方框部分都是在计算组成矩形的向量,可以看到圆框中只是给P1和P2传递了一个通过计算创造的矩形而已,BESIDE需要的也就只是计算矩形

同样翻转九十度的过程也是一样的

实现这种效果的关键就在于,绘制图片的是一个过程,具有闭包性质。不管是图片本身(PICT,实际上是一个线段的集合)也好,又或者是ABOVE等操作也好,BESIDE并不关心你提供的是什么,他只管计算矩形,而得到矩形的图片会自动绘制。
这个语言的点睛之笔在于,他将所有的组合方法都抽象成了过程(BESIDE,ABOVE,PICT等)这意味着我们实现对这个语言的抽象的时候,lisp的所有特性都可以自动的使用(过程组合,参数代换,高阶过程抽象等)

接下来来看例子吧,如果我们想让ABCD四个图片变成一下格局,那么很简单,只需要用BD,ABOVE,还有AC,ABOVE,在通过BESIDE将结果组合到一起就行了(我们不需要对图片进行特殊的处理,因为他们本身都是过程)

又或者我们使用一些比较复杂的递归的方式,传入图片P,参数N和缩放参数A,然后用BESIDE递归的调用和组合这个图片,那么假设N=2,过程是这样的



这个过程为什么可以如此完美的兼容递归呢?因为他们本身全都是过程,而递归又允许过程调用,上述过程叫“RIGHT-PUSH”,可以很轻松的定义一个“UP-PUSH”把图片放在上面。
我们可以做一些更为复杂的调用,可以吧图像放在原有图片的上面和边上,并且递归调用,如下图,它相比原来的也不会复杂很多

那再皮一下,我们把这个过程应用在之前的Q图片上面,会变成什么样呢?


这里强调了一下,如果你想要在一个语言的基础上设计一个语言(类似包的概念),他要是想要拥有强大的能力,你必须让他嵌入于这个语言。那么更进一步,让我们吧上面的方法抽象为高阶过程。

OK这里有必要解释一下,COMB是处理过程(BISED,ABOVE),传入PICT图片 N重复次数 A分割参数,REPEATCD这个过程是一个重复执行COMB的处理过程,下面那个是我自己写的大概的处理过程,忽略我的return,我不熟悉lisp语法

P = 图片 N = 递归次数 COMB = 处理方法例如:BESIDE,ABOVE
(REPEAEED COMB P N A)
if N = 0
return p
(λ (COMB P (REPEAEED P (- N 1) A) A))
那么经过这样的抽象之后,之前的RIGHT-PUSH可以表示为

只需要传入BESIDE就可以
那么现在该总结了,回过头来看看,你就会发现,发生了什么?这其中用到的不管是图像还是处理方式又或者高阶抽象,图像本身是由矩形作为参数的过程,矩形是由向量作为参数的过程,他们全都是过程。我们的数据呢?
现在我们应该明白了,系统中什么是过程?什么是数据?我们会发现他们根本没有任何区别。
有一种观点,构建一个系统其实是创建一种语言,创建各种层次的语言。
另外一种观点是,构建一个系统,将其拆分为两个或者三个子任务,继续拆分,每个节点都被严谨的定义完成,就像这样。

第一种观点是,我们有一个语言的层次序列,像这样
第一个层级专注于讨论图像的构成,像是正方形中讨论的点,向量。

第二个层级是讨论图像组合的,关于几何物件的位置。


在此之上还可以有一个第三层,用于讨论组合的模式。比如PUSH
他们整体的结构层级是这样的,我们在每一层上所讨论的对象,都是基于前一层级所创立的

那这两种方式有什么区别呢?
树形结构的每一次分解,都在指定准确的严谨的任务
而层级结构的每一层不是这样的,他们都是在描述整件事情。
假设如果树形结构的某一个节点的需求被改变,整个节点以及子节点都需要推到从做。
而层级结构,比方说你要改变图片的构造又或者改变排列方式,那么只需要在第一层第二层做出改变,其他的层级丝毫不受影响
很明显层级结构无论是代码灵活性又或者是健壮性都是毋庸置疑的。
最后,用教授的话收尾.


本文探讨SICP中的序对构造、闭包概念及其在图像生成系统中的应用。通过示例展示了如何使用序对创建坐标向量,利用闭包和过程抽象实现数据的自由组合。文章提到了Lisp中的MAP和FOR-EACH过程,解释了它们的递归和迭代区别,并展示了如何通过这些过程进行图像的旋转、缩放和组合。作者强调了过程和数据的相似性,并探讨了构建系统时采用的多层次语言抽象方法,揭示了其灵活性和健壮性。
 Lec3a 的个人心得&spm=1001.2101.3001.5002&articleId=109643737&d=1&t=3&u=1c89e31234854674be76d1cad00faf7d)
3340

被折叠的 条评论
为什么被折叠?



