Scheme语言直译为汉语(十)

一、有理数运算

接下来,我们要用一个有理数运算的例子来感受需要进一步抽象的程序要如何来设计。

作为开始,我们假定已经有了一种从分子和分母构造有理数的方法。并进一步假定, 如果有了一个有理数,我们有一种方法取得(选出)它的分子和分母。现在再假定有关的构造函数和选择函数都可以作为过程使用:

  • (make-rat ) 返回一个有理数,其分子是整数<n>,分母是整数<d>。
  • (numer <x>) 返回有理数<x>的分子。
  • (denom <x>) 返回有理数<x>的分母。

我们要在这里使用一种称为按愿望思维的强有力的综合策略。现在我们还没有说有理数将如何表示,也没有说过程numer、denom和make-rat应如何实现。然而,如果我们真的有了这三个过程,那么就可以根据下面关系去做有理数的加减乘除和相等判断了:

n 1 d 1 + n 2 d 2 = n 1 d 2 + n 2 d 1 d 1 d 2 \frac{n_{1}}{d_{1}} + \frac{n_{2}}{d_{2}} = \frac{n_{1}d_{2} + n_{2}d_{1}}{d_{1}d_{2}} d1n1+d2n2=d1d2n1d2+n2d1 n 1 d 1 − n 2 d 2 = n 1 d 2 − n 2 d 1 d 1 d 2 \frac{n_{1}}{d_{1}} - \frac{n_{2}}{d_{2}} = \frac{n_{1}d_{2} - n_{2}d_{1}}{d_{1}d_{2}} d1n1d2n2=d1d2n1d2n2d1 n 1 d 1 ⋅ n 2 d 2 = n 1 n 2 d 1 d 2 \frac{n_{1}}{d_{1}} \cdot \frac{n_{2}}{d_{2}} = \frac{n_{1}n_{2}}{d_{1}d_{2}} d1n1d2n2=d1d2n1n2 n 1 / d 1 n 2 / d 2 = n 1 d 2 d 1 n 2 \frac{n_{1} / d_{1}} {n_{2} / d_{2}} = \frac{n_{1}d_{2}}{d_{1}n_{2}} n2/d2n1/d1=d1n2n1d2 n 1 d 1 = n 2 d 2 当 且 仅 当 n 1 d 2 = n 2 d 1 \frac{n_{1}}{d_{1}} = \frac{n_{2}}{d_{2}} 当且仅当 n_{1}d_{2} = n_{2}d_{1} d1n1=d2n2n1d2=n2d1

我们可以将这些规则表述为如下几个过程:

(define (add-rat x y)
    (make-rat (+ (* (numer x) (denom y))
                 (* (numer y) (denom x)))
              (* (denom x) (denom y))))
(define (sub-rat x y)
    (make-rat (- (* (numer x) (denom y))
                 (* (numer y) (denom x)))
              (* (denom x) (denom y))))           
(define (mul-rat x y)
    (make-rat (* (number x) (number y))
              (* (denom x) (denom y))))
(define (div-rat x y)
    (make-rat (* (number x) (denom y))
              (* (denom x) (numer y))))
(define (equal-rat? x y)
    (= (* (numer x) (denom y))
       (* (numer y) (denom x))))

尝试把上述过程直译为汉语:

(定义 (分数相加 甲 乙)
    (构建分数 (+ (* (分子 甲) (分母 乙))
                (* (分子 乙) (分母 甲)))
             (* (分母 甲) (分母 乙))))
(定义 (分数相减 甲 乙)
    (构建分数 (- (* (分子 甲) (分母 乙))
                (* (分子 乙) (分母 甲)))
             (* (分母 甲) (分母 乙))))
(定义 (分数相乘 甲 乙)
    (构建分数 (* (分子 甲) (分子 乙))
             (* (分母 甲) (分母 乙))))
(定义 (分数相除 甲 乙)
    (构建分数 (* (分子 甲) (分母 乙))
             (* (分母 甲) (分子 乙))))
(定义 (分数相等? 甲 乙)
    (= (* (分子 甲) (分母 乙))
       (* (分子 乙) (分母 甲))))

(定义 (有理数转为分数后相加 甲 乙)
    (分数相加 甲 乙))
(定义 (有理数转为分数后相减 甲 乙)
    (分数相减 甲 乙))   
(定义 (有理数转为分数后相乘 甲 乙)
    (分数相乘 甲 乙))   
(定义 (有理数转为分数后相除 甲 乙)
    (分数相除 甲 乙))   

(定义 (有理数相加 甲 乙)
    (有理数转为分数后相加 甲 乙))
(定义 (有理数相减 甲 乙)
    (有理数转为分数后相减 甲 乙))   
(定义 (有理数相乘 甲 乙)
    (有理数转为分数后相乘 甲 乙)) 
(定义 (有理数相除 甲 乙)
    (有理数转为分数后相除 甲 乙))
(定义 (有理数相等 甲 乙)
    (分数相等 甲 乙))

这样,我们已经有了定义在选择和构造过程numer、denom和make-rat基础之上的各种有理数运算,而这些基础还没有定义。现在需要有某种方式,将一个分子和一个分母粘接起来,构成一个有理数。

序对
序对形式上就是一个有且只有两个元素的数组,在scheme语言中,由cons关键字构造出来,例:

(define x (cons 1 2))

尝试将上面这句直译为汉语:

(定义 序对甲 (序对 1 2))

其中序对中的前者元素可用关键字car提取出来,后者元素可用关键字cdr提取出来:

(define x (cons 1 2))
(car x)
1
(cdr x)
2

尝试把上面这段直译为汉语:

(定义 序对甲 (序对 1 2))
(前项 序对甲)
1
(后项 序对甲)
2

还可以构建元素也是序对的序对:

(define x (cons 1 2))
(define y (cons 3 4))
(define z (cons x y))
(car (car z))
1
(car (cdr z))
3

尝试把上面这段直译为汉语:

(定义 序对甲 (序对 1 2))
(定义 序对乙 (序对 3 4))
(定义 序对丙 (序对 序对甲 序对乙))
(前项 (前项 序对丙))
1
(前项 (后项 序对丙))
3

从序对构造起来的数据对象称为表结构数据

序对为完成这里的有理数系统提供了一种自然方式,我们可以将有理数简单表示为两个整数(分子和分母)的序对。这样就很容易做出下面make- rat、numer和denom的实现:

(define (make-rat n d) (cons n d))

(define (numer x) (car x))

(define (denom x) (cdr x))

尝试把上面几句直译为汉语:

(定义 (构建分数 分子 分母) (序对 分子 分母))

(定义 (分子 甲) (前项 甲))

(定义 (分母 甲) (后项 甲))

为了显示分数,我们可以先把分子打印出来,再显示出一个“/”分数线,紧接着再把分母显示出来:

(define (print-rat x)
    (newline)
    (display (numer x))
    (display "/")
    (display (denom x)))

尝试把上面这段直译为汉语:

(定义 (显示分数 甲)
    (换行)
    (显示 (分子 甲))
    (显示 "/")
    (显示 (分母 甲)))

我们之前只是按愿望设想出的过程现已实现了,因而可以实验一下整个有理数运算过程了:

(define one-half (make-rat 1 2))

(print-rat one-half)
1/2

(define one-third (make-rat 1 3))

(print-rat (add-rat one-half one-third))
5/6

(print-rat (mul-rat one-half one-third))
1/6

(print-rat (add-rat one-third one-third))
6/9

尝试把上面几句直译为汉语:

(定义 二分之一 (构建分数 1 2))

(显示分数 二分之一)
1/2

(定义 三分之一 (构建分数 1 3))

(显示分数 (分数相加 二分之一 三分之一))
5/6

(显示分数 (有理数相乘 二分之一 三分之一))
1/6

(显示分数 (分数相加 三分之一 三分之一))
6/9

接下来我们要想办法显示分数被约分之后的结果,还记得之前的求取最大公因数的欧几里得算法吗?假如我们已经拥有了一个求取最大公因数的过程(GCD)可以直接调用,我们便可以用它来实现在构造序对之前将分子和分母约化为最简单的项:

(define (make-rat n d)
    (let ((g (gcd n d)))
        (cons (/ n g) (/d g))))

尝试把上述过程直译为汉语:

(定义 (构建分数 分子 分母)
    (命 ((分子分母最大公因数 (求取最大公因数 分子 分母)))
        (序对 (/ 分子 分子分母最大公因数) (/分母 分子分母最大公因数))))

而后我们就可以有:

(print-rat (add-rat one-third one-third))
2/3
(显示分数 (分数相加 三分之一 三分之一))
2/3

可以注意到,为了实现显示约分后的分数,只需要修改make-rat,完全不必修改实际运算的过程(例如add-rat和mul-rat)。

练习1:
请定义出make-rat的一个更好的版本,使之可以正确处理正数和负数。当有理数为正时,make-rat应当将其规范化,使它的分子和分母都是正的。如果有理数为负,那么就应只让分子为负。

(define (make-rat a b)
    (define (sign x)
        (if (> (* b x) 0)
            x
            (- x)))
        (let ((c (sign (gcd a b))))
            (cons (/ a c) (/ b c))))

尝试把上述过程直译为汉语:

(定义 (构建分数 分子 分母)
    (定义 (确定正负性 某个数)
        (如果 (> (* 分母 某个数) 0)
              某个数
             (- 某个数)))
        (命 ((带正负性分子分母最大公因数 (确定正负性 (求取最大公因数 分子 分母))))
            (序对 (/ 分子 带正负性分子分母最大公因数) (/ 分母 带正负性分子分母最大公因数))))
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

X-jazz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值