• 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 0))
    (define (inc x) (+ x 1))
    (define (dec x) (if (= x 0) 0 (- x 1)))

      递归论里都是自然数内部的函数,当然递归论其实本质上不过是用自然数(一个特殊的可列集)内的递归来模拟所有的运算,因为本质上我们一个确定的计算都是针对可列集。自然数里的计算搞定了,所有可计算问题都可以等价的转为自然数内的计算。

      当然,上升到递归论层次,有些东西还是难懂的,比如一般递归算子和原始递归算子的理解。不过,如果只是学习函数式编程,而不是为了学习数学,一些东西可以庸俗化一点。

      很显然,这个问题是难到他了,半天连加法似乎没有出来。于是我决定启发一下他。我们可以这样去考虑递归:

      我们知道任何一个数加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) 0
    (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 0 0)
    )

      他很惊讶于原来还可以这么干,不过很快理解了我的做法,然后我又告诉他,因为这个_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 0 0)
    )

      接着,很快,他很快完成了乘方,

    (define (pow x y)
     (if (eq0 y) 1
      (mul x (pow x (dec y)))
     )
    )

      嗯,还算可以,至少会举一反三,只是0的任意次幂为0,但0的0次幂嘛。。。算了,索性x不能等于0,算解决了。

      除法(自然数内的除法这里只考虑整数部分)也很快做完,

    (define (div x y)
     (if (> y x) 0
      (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) 0
      (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 1 0)
    )

      不过整体来说,我还算满意,毕竟只学了一点点时间,能在指导下搞定这么些,已经挺不错。

  • 相关阅读:
    ios7 苹果原生二维码扫描(和微信类似)
    ios7之后 根据UILabel的文字计算frame的方法
    [Luogu1944] 最长括号匹配
    [bzoj3916] friends
    [NOIp2006] 能量项链
    [NOIp2003] 加分二叉树
    [Luogu1353] 跑步Running
    [Luogu2214] Mooo Moo S
    [POJ2452] Sticks Problem
    [POJ2406] Power Strings
  • 原文地址:https://www.cnblogs.com/Colin-Cai/p/9123363.html
Copyright © 2020-2023  润新知