scheme实现最基本的自然数下的运算
版权申明:本文为博主窗户(Colin Cai)原创,欢迎转帖。如要转贴,必须注明原文网址 http://www.cnblogs.com/Colin-Cai/p/9123363.html 作者:窗户 QQ:6679072 E-mail:6679072@qq.com
教一个基本没编过什么程序的朋友scheme,为什么教scheme呢?因为他想学,因为一直听我鼓吹,而他觉得他自己多少有C语言一点基础,而又因为我觉得函数式才像数学,而过程式是偏向物理现实的,感觉不够抽象。当然,对于一个成年人来说,有着太多的生活、学习、工作经验,这些很多因为是物理现实,很有过程式的意思,对于理解递归这种数学抽象总觉得是不容易的。我告诉他,这个和你曾经读书时学的C语言有天壤之别。但无论如何,我决定试一试。
理解了前缀表达,理解了基本的递归,想想还是从这里开始吧。
我给出了三个函数:eq0,用来判断是否为0;inc,用来得到一个自然数的后继数;dec,用来得到一个自然数是哪个自然数的后继(有个特例,0不是任何数的后继,这里返回0)。然后让他借助scheme的递归,其余的只利用这三个函数来构造加减乘除乃至余数、乘方、对数。
;使用这三个函数实现自然数内的加减乘除乘方对数(《递归论》里的运算,除法和对数都是向下取整,减法被减数小于减数得到0)
(define (eq0 x) (= x ))
(define (inc x) (+ x ))
(define (dec x) (if (= x ) (- x )))
递归论里都是自然数内部的函数,当然递归论其实本质上不过是用自然数(一个特殊的可列集)内的递归来模拟所有的运算,因为本质上我们一个确定的计算都是针对可列集。自然数里的计算搞定了,所有可计算问题都可以等价的转为自然数内的计算。
当然,上升到递归论层次,有些东西还是难懂的,比如一般递归算子和原始递归算子的理解。不过,如果只是学习函数式编程,而不是为了学习数学,一些东西可以庸俗化一点。
很显然,这个问题是难到他了,半天连加法似乎没有出来。于是我决定启发一下他。我们可以这样去考虑递归:
我们知道任何一个数加0都等于自身,那么假设计算4+3,可以这样考虑递归的过程,
4+3=5+2=6+1=7+0=7
也就是x+y往(x+1)+(y-1)的方向去递归,直到第二个数字递归到0为止。
很快这位仁兄写出了以下代码
(define (add x y)
(if (eq0 y) x
(add (inc x) (dec y))
)
)
再接再厉,减法也是前后两个数逐渐dec,直到两个有一个变为0,嗯,也很快搞定(因为是自然数内的,这里的减法小的数减大的数结果为0,递归论里定义的减法就是这样)
(define (sub x y)
(if (eq0 x)
(if (eq0 y) x
(sub (dec x) (dec y))
)
)
)
我然后说,这里其实可以缩减一下(0 dec还是0)
(define (sub x y)
(if (eq0 y) x
(sub (dec x) (dec y))
)
)
做乘法的时候,遇到了困难,于是我就提醒了一下,其实x*y,y=0的时候为0,y不为0的时候,x*y=x*(y-1)+x,所以可以这样写
(define (mul x y)
(if (eq0 y) 0
(add x (mul x (dec y)))
)
)
他不可理解了,说add不是刚才定义过的吗,为什么现在就可以用了?我顿时明白了他之所以为难的原因,于是解释说,当然可以用,我们数学里面不是为了证明一个定理,很多时候先去证明一个引理嘛。他顿时理解了。
然后我接着说,如果不直接用也是可以的,只是复杂一些。我用3*3来解释这个问题,我需要记录过程状态:
3 3 0 0
3 2 3 0
3 2 2 1
3 2 1 2
3 2 0 3
3 1 3 3
3 1 2 4
3 1 1 5
3 1 0 6
3 0 3 6
3 0 2 7
3 0 1 8
3 0 1 9
左边第一个是被乘数,第二个是乘数,第四个是累计的结果。
计算过程规则如下:
(1)最开始的时候,第三个数和第四个数都为0。
(2)当第三个数为0的时候,第三个数减1,第四个数加1。
(3)当第三个数为0的时候,如果乘数不为0,就借一个被乘数过来,然后乘数减1;如果乘数为0,那么第四个累计的结果就是乘积。
作为函数式编程使用附加的值只能再加参数,于是有了这样的计算方法:
(define (_mul x y x2 r)
(if (eq0 x2)
(if (eq0 y) r
(_mul x (dec y) x r)
)
(_mul x y (dec x2) (inc r))
)
)
(define (mul x y)
(_mul x y )
)
他很惊讶于原来还可以这么干,不过很快理解了我的做法,然后我又告诉他,因为这个_mul是为了解决mul临时定义出来的函数,如同证明中的引理,可以写到mul的定义里面,写成
(define (mul x y)
(define (_mul x y x2 r)
(if (eq0 x2)
(if (eq0 y) r
(_mul x (dec y) x r)
)
(_mul x y (dec x2) (inc r))
)
)
(_mul x y )
)
接着,很快,他很快完成了乘方,
(define (pow x y)
(if (eq0 y)
(mul x (pow x (dec y)))
)
)
嗯,还算可以,至少会举一反三,只是0的任意次幂为0,但0的0次幂嘛。。。算了,索性x不能等于0,算解决了。
除法(自然数内的除法这里只考虑整数部分)也很快做完,
(define (div x y)
(if (> y x)
(inc (div (sub x y) y))
)
)
我“嗯?”了一下,然后说我这里只定义了一个谓词eq0,并没有定义>
然后我提醒他需要定义>,他搞不定了。
于是还是得我来写,
(define (> x y)
(if (eq0 x) #f
(if (eq0 y)
#t
(> (dec x) (dec y))
)
)
)
其实,也可以用之前的减法定义>
(define (> x y) (if (eq0 (- x y)) #f #t))
接着,他完成了余数和对数
(define (rem x y)
(if (> y x) x
(rem (sub x y) y)
)
)
;这里的对数,是实数下对数的整数部分
(define (log x y)
(if (> y x)
(inc (log (div x y) y))
)
)
然而对数的实现稍有问题(当然,不考虑x,y为0,y为1的情况),也不符合这个函数我想让他写成的样子,不过我不得不说他写对了,与其说是写对了,倒不如说是蒙对了,因为这个的写法是需要一个数学证明的(不要忘了,div返回的只是整数)。
证明在这里我就不赘述了,我希望他完成的大致应该如下:
(define (log x y)
(define (_log x y y^r r)
(if (> y^r x) (dec r)
(_log x y (mul y^r y) (inc r))
)
)
(_log x y )
)
不过整体来说,我还算满意,毕竟只学了一点点时间,能在指导下搞定这么些,已经挺不错。
scheme实现最基本的自然数下的运算的更多相关文章
- iOS系统提供开发环境下命令行编译工具:xcodebuild
iOS系统提供开发环境下命令行编译工具:xcodebuild[3] xcodebuild 在介绍xcodebuild之前,需要先弄清楚一些在XCode环境下的一些概念[4]: Workspace:简单 ...
- 算法语言Scheme修订6报告 R6RS简体中文翻译
算法语言Scheme修订6报告 R6RS简体中文翻译 来源 https://r6rs.mrliu.org/ MICHAEL SPERBERR. KENT DYBVIG, MATTHEW FLATT ...
- Android开发之旅: Intents和Intent Filters(理论部分)
引言 大部分移动设备平台上的应用程序都运行在他们自己的沙盒中.他们彼此之间互相隔离,并且严格限制应用程序与硬件和原始组件之间的交互. 我们知道交流是多么的重要,作为一个孤岛没有交流的东西,一定毫无意义 ...
- mac xcworkspace xcodebuild
xcodebuild 在介绍xcodebuild之前,需要先弄清楚一些在XCode环境下的一些概念[4]: Workspace:简单来说,Workspace就是一个容器,在该容器中可以存放多个你创建的 ...
- ALV用例大全
一.ALV介绍 The ALV Grid Control (ALV = SAP List Viewer)是一个显示列表的灵活的工具,它提供了基本功能的列表操作,也可以通过自定义来进行增强,因此可以允 ...
- 【nginx】关于fastcgi_cache
一.简介 Nginx版本从0.7.48开始,支持了类似Squid的缓存功能.这个缓存是把URL及相关组合当做Key,用Md5算法对Key进行哈希,得到硬盘上对应的哈希目录路径,从而将缓存内容保存在该目 ...
- Vim配置及说明——IDE编程环境
Vim配置及说明——IDE编程环境 Vim配置及说明——IDE编程环境 1.基本及字体 2.插件管理 3.主题风格 4.窗口设置 5.目录树导航 6.标签导航 7.taglist 8.多文档编辑 9. ...
- 3n+1问题
猜想: 对于任意大于1的自然数n,若n为奇数,则将n变为3n+1,否则变为n的一半. 经过若干次这样的变换,一定会使n变为1.例如3->10->5->16->8->2-& ...
- acm算法模板(1)
1. 几何 4 1.1 注意 4 1.2 几何公式 4 1.3 多边形 6 1.4 多边形切割 9 1.5 浮点函数 10 1.6 面积 15 1.7 球面 16 1.8 三角形 17 1.9 三维几 ...
随机推荐
- 使用ML.NET预测纽约出租车费
有了上一篇<.NET Core玩转机器学习>打基础,这一次我们以纽约出租车费的预测做为新的场景案例,来体验一下回归模型. 场景概述 我们的目标是预测纽约的出租车费,乍一看似乎仅仅取决于行程 ...
- ES 04 - 安装Kibana插件(6.6.0版本)
目录 1 Kibana是什么 2 安装并启动Kibana 2.1 准备安装包 2.2 修改配置文件 2.3 启动Kibana并验证 2.4 关闭Kibana服务 3 Kibana功能测试 3.1 关于 ...
- .net core通过多路复用实现单服务百万级别RPS吞吐
多路复用其实并不是什么新技术,它的作用是在一个通讯连接的基础上可以同时进行多个请求响应处理.对于网络通讯来其实不存在这一说法,因为网络层面只负责数据传输:由于上层应用协议的制订问题,导致了很多传统服务 ...
- 初学Java Web(8)——过滤器和监听器
什么是过滤器 过滤器就是 Servlet 的高级特性之一,就是一个具有拦截/过滤功能的一个东西,在生活中过滤器可以是香烟滤嘴,滤纸,净水器,空气净化器等,在 Web 中仅仅是一个实现了 Filter ...
- web进修之—Hibernate 关系映射(3)
概述 Hibernate的关系映射是Hibernate使用的难点或者是重点(别担心,不考试哦~),按照不同的分类方式可以对这些映射关系做一个分类,如: 按对象对应关系分: 一对一 多对一/一对多 多对 ...
- PC逆向之代码还原技术,第二讲寻找程序入口点
PC逆向之代码还原技术,第二讲寻找程序入口点 一丶简介 程序逆向的时候.我们需要知道程序入口点.动态分析的时候以便于看是什么程序编写的. 比如VC++6.0 我们可以写一个程序测试一下 我们写一段代码 ...
- Oracle学习笔记三
一.创建表空间 表空间是ORACLE数据库的逻辑单元.数据库--表空间 一个表空间可以与多个数据文件(物理结构)关联一个数据库下可以建立多个表空间,一个表空间可以建立多个用户个用户下可以建立多个表. ...
- Docker Compose 引用环境变量
在项目中,往往需要在 docker-compose.yml 文件中使用环境变量来控制不同的条件和使用场景.本文集中介绍 docker compose 引用环境变量的方式.说明:本文的演示环境为 ubu ...
- Django学习笔记(8)——前后台数据交互实战(AJAX)
这里将自己这段时间学习的关于前后台数据交互的笔记写在这里,这里包含了Django传输数据给JS,AJAX的相关问题,跨域问题,如何解决AJAX的跨域问题等等.比较凌乱,请看到这篇博客的盆友见谅,如果我 ...
- Linux学习笔记之MySql的安装(CentOS)
一.移除mariadb 由于CentOS默认安装了mariadb,所以在安装MySql之前先移除mariadb,使用命令:yum remove mariadb-libs.x86_64,如下图所示: 二 ...