3.5.2 无限流
我们已经看到了如何支持操作流作为一个整体的对象的虚像,在事实上,我们仅
计算我们需要读取的流的那一部分。我们能探索这种技术来有效的表示序列作为流,
即使序列非常长。更让注目的是,我们能使用流来表示无限长的序列。例如,考虑如下的
正整数的流的定义:
(define (integers-starting-from n)
(cons-stream n (integers-starting-from (+ n 1))))
(define integers (integers-starting-from 1))
这是智能的,因为Integers 是一个数对,它的头部是1,它的尾部是产生一个由2开始的整数集合的承诺。
这是一个无限长的流,但是在任何给定的时刻,我们能仅检查它的一部分。因此,我们的程序将根本不知道
整个无限流是不存在的。
使用integers我们能定义其它的无限的流,例如不能被7整除的流:
(define (divisible? x y) (= (remainder x y) 0))
(define no-sevens (stream-filter (lambda (x) (not (divisible? x 7)))
integers))
然后我们能通过读取这个流的元素,来找到不被7整除的整数了。
(stream-ref no-sevens 100)
117
与整数流类似,我们能定义斐波那些数的无限流:
(define (fibgen a b)
(cons-stream a (fibgen b (+ a b))))
(defne fibs (fibgen 0 1))
Fibs是一个数据对,它的头部是0,尾部是执行(fibgen 1 1)的结果.
当我们解释这个延迟的(fibgen 1 1),它将产生一个数据对,它的头部是1,尾部是
执行(fibgen 1 2)的结果.等等
看一个更令人兴奋的无限流,我们能生成一个无法被7整除的流的例子,
通过组装素数的无限流,使用一个有名的方法叫做,埃拉托色尼的筛法。
当我们开始于整数2,它是第一个素数。为了得到其余的素数,我们开始从其它的整数中,
过滤2的倍数。这得到了一个从3开始的流,它就是下一个素数。现在我们从这个流中过滤
3的倍数。这得到了一个以5开始的流,它是下一个素数,等等。换一句话说,我们以一个筛的过程
组装了一个素数的流,描述如下:为了筛一个流S,组装出素数的流,这个素数流,它的第一个元素
就是S的第一个元素,这个素数流的其它的元素是S的其它元素中过滤出S的第一个元素的
所有的倍数后的结果。这个过程能被描述成如下的流的过程:
(define (sieve stream)
(cons-stream
(stream-car stream)
(sieve (stream-filter (lambda (x) (not (divisible? x (stream-car stream))))
(stream-cdr)))))
(define primes (sieve (integers-starting-from 2)))
现在要找到一个特定的素数,我们仅需要调用如下的方法:
(stream-ref primes 50)
233
仔细考虑由筛法安装而成的信号处理系统是很有趣的,显示成图3.61中的亨德森图。
输入流进入一个非保护区,这个区域把它的第一个元素与其它的元素分离开。
第一个元素被用来组装一个整除的过滤器,其它元素需要通过这个过滤器,过滤器的输出
被送入下一个筛法的盒子。然后这个原始的第一个元素作为内部筛的输出,用来生成输出流。
因此,不仅流是无限的,而且信号处理器也是无限的,因为筛中包括了一个筛。
* 隐式的定义流
如上的integers和fibs流都是被定义为生成的程序,显式的计算流中的一个一个的元素。
指定一个流的一个可行的方式是利用隐式的定义流的延迟解释。例如,如下的表达式定义一个流
ones以一个1的无限流:
(define ones (cons-stream 1 ones))
这工作起来更像是一个递归的程序的定义:ones是一个数据对,它的头部是1,它的尾部是解释
One的结果。解释尾部,给我们一个1,和一个解释ones的结果,等等。
通过以加法等方式操作流,我们能做更有趣的事,它能生成两个给定的流的加法。
(define (add-streams s1 s2)
(stream-map + s1 s2))
现在我们能定义整数的流如下:
(define integers (cons-stream 1 (add-streams ones integers)))
这定义了一个整数的流,它的第一个元素是1,它的其它的元素是1的流与整数的流的和。
因此整数的流的第二个元素是1加上整数流的第一个元素。即是2;
整数流的第三个元素是1加上整数流的第二个元素,即是3。等等。
这个定义是有效的,因为在任何一个点上,为了生成下一个整数,整数流中的元素已经
生成了足够的元素,我们反馈了这些到定义中。
我们能够以同样的方式定义斐波那些数:
(define fibs
(cons-stream 0
(cons-stream 1
(add-streams (stream-cdr fibs)
fibs))))
这个定义说,fibs是一个流,开始于0和1,例如,流的其它部分能被生成以fibs
的本身和它的左移一个位置的流相加。
1 1 2 3 5 8 13 21 .....=(stream-cdr fibs)
0 1 1 2 3 5 8 13 ....= fibs
0 1 1 2 3 5 8 13 21 34 ....= fibs
Scale-stream 在规范化这样的流的定义方面,是另一个有用的程序。
这是在流中的每一个元素上乘以一个特定的常数:
(define (scale-stream stream factor)
(stream-map (lambda (x) (* x factor)) stream))
例如:
(define double (cons-stream 1 (scale-stream double 2)))
生成2的幂的流:1 2 4 8 16 32 ....
素数的流的另一个可能的定义是,它能是以整数开始,并且通过测试素数性来过滤它们。
我们将需要第一个素数2,然后开始:
(define primes
(cons-stream
2
(stream-filter prime? (integers-starting-from 3)))
)
这个定义不如以前的定义自然,因为我们将测试一个整数是否是素数,通过检查这个数能否
被 一个小于等于这个数的平方根的素数整队。
(define (prime? n)
(define (iter ps)
(cond ((> (square (stream-car ps)) n) true)
((divisible? n (stream-car ps)) false)
(else (iter (stream-cdr ps))))
)
(iter primes)
)
这是一个递归的定义,因为primes被定义时使用了prime?这个判断式,
这个判断式也使用了primes这个流。这个程序这么做的原因是,在任何一点处,
为了检查下一个数,我们需要测试数的素数性,我们已经生成了素数流中的足够的部分了。
也就是对于任何一个数,我们测试它的素数性,它或者是素数,或者不是。
练习3.53
没有运行程序,描述流的元素组成。它的定义如下:
(define s (cons-stream 1 (add-streams s s)))
练习3.54
定义一个程序mul-streams,类似于add-streams,生成两个输入流的元素的乘积。
使用这个结合整数流,来完成如下的定义,这个流中的第n个元素是第n+1的阶乘:
(define factorials (cons-stream 1 (mul-streams <??> <??>)))
练习3.55
定义一个程序partial-sums,它的输入参数是一个流S,返回一个流,它的元素是
S0 S0+S1,S0+S1+S2,...... 例如(partial-sums integers)应该返回 1,3,6,10,15,...
练习3.56
一个著名的问题首先被汉明提出的,以升序的,非重复的,列举正整数,并且满足它的素因子仅允许
出现2,3,5。实现这个任务的 一个明显的方式是简单的测试每个数,看它是否有除了2,3,5之外的素因子。
但是这个很低效的,因为数越大,满足要求的数就越来越少了。作为一个可选的方法,让我们调用数的
要求的流S,并且注意如下的事实:
S开始于1,
(scale-stream S 2)的元素也是S的元素,
(scale-stream S 3)和(scale-stream S 5)也是类似的。
以上的就是S的所有的元素了。
现在我们不得不做的所有的事就是把这些源中得到的元素合并起来。为了这个目的,
我们定义一个程序merge,它负责合并两个有序的流成为一个有序的结果的流,并且
消除重复的数据:
(define (merge s1 s2)
(cond ((stream-null? s1) s2)
((stream-null? s2) s1)
(else
(let ((s1car (stream-car s1))
(s2car (stream-car s2)))
(cond ((< s1car s2car)
(cons-stream s1car (merge (stream-cdr s1) s2)))
((> s1car s2car)
(cons-stream s2car (merge s1 (stream-cdr s2))))
(else (cons-stream s1car (merge (stream-cdr s1) (stream-cdr s2))))))))
)
然后需要的流可能被组装使用merge,以如下的方式:
(define S (cons-stream 1 (merge <??> <??>)))
在如上的空白处填上需要的内容。
练习3.57
当我们计算第n个斐波那些数时,使用fibs的定义,基于add-streams程序,
有多少的加法被执行?如果我们实现的(delay <exp>)是简单的如(lambda () <exp>),
而没有使用在3.5.1部分中提供的memo-proc程序所描述的优化,那么,
显示出加法的数量是如何指数级增加的?
练习3.58
给出如下的程序计算的流的一个解释:
(define (expand num den radix)
(cons-stream
(quotient (* num radix) den)
(expand (remainder (* num radix) den) den radix))
)
(Quotient 是一个原生的程序,返回一个整数是两个整数的商)。
由(expand 1 7 10)生成的连续的元素是什么? (expand 3 8 10)
生成的是什么?
练习3.59
在2.5.3部分中,我们看到了如何实现一个多边形的算法系统把多边形
表示为项的列表,在相似的方式,我们能把幂的系列,例如:
e^x=1+x+x^2/2+x^3/(3*2)+x^4/(4*3*2)+....,
cos(x)=1-x^2/2+x^4/(4*3*2)-....,
sin(x)=x-x^3/(3*2)+x^5/(5*4*3*2)-.....,
表示为无限流。我们将表示多项式 a0+a1*x+a2*x^2+a3*x^3+.... 作为一个流,
它的元素的系数是a0,a1,a2,a3,.....
a. 多项式 a0+a1*x+a2*x^2+a3*x^3+....的积分结果是一个多项式
c+a0x+(1/2)*x^2+(1/3)*x^3+(1/4)*x^4+....
c是一个常数,定义一个程序,integrate-series是有一个输入参数是一个流a0,a1,a2,...
表示一个幂的多项式,返回一个流 a0,(1/2)a1,(1/3)a2,....多项式的积分的非常数项的系数。
(因为结果有非常数项,它没有表示一个多项式;当我们使用integrate-series,
我们能在合适的常数上使用cons)
b. 函数f(x)->e^x,是自己的导数。这个应用是e^x与e^x的积分是一样的多项式,除了常数项。
当然了e^0=1,根据我们能生成e^x的多项式如下
(define exp-series (cons-stream 1 (integrate-series exp-series)))
显示如何生成正弦与余弦的多项式,开始于sine的导数是余弦,余弦的导数是负的正弦的事实:
(define cosine-series
(cons-stream 1 <??>))
(define sine-series
(cons-stream 0 <??>))
练习3.60
正如上面的练习3.59,幂的多项式表示为系数的流,加多项式是被实现为add-stream.
为了多项式的乘法的计算,完成如下程序的定义:
(define (mul-series s1 s2)
(cons-stream <??> (add-streams <??> <??>))
)
通过验证(sin(x))^2+(cos(x))^2=1,使用练习3.59的多项式来测试你的程序。
练习3.61
让S成为一个幂的多项式,它的常数项是1。假定我们要找到
1/S的幂的多项式,也就是多项式X,有S*X=1。写S=1+SR,SR是
S的常数项后的部分。然后我们能解X如下:
S*X=1
(1+SR)*X=1
X+SR*X=1
X=1-SR*X
换句话说,X是幂的多项式,它的常数项是1,它的高次项负的SR乘以X。
使用这种思想写程序invert-unit-series,计算1/S,S的常数项是1。你需要使用
练习3.60中的mul-series。
练习3.62
使用练习3.60和练习3.61中的结果,定义一个div-series,用它来除两个幂的多项式,
Div-series,能工作为了任何两个多项式,提供一个除数,开始一个非零的常数项,
(如果除数有一个为零的常数项,这个程序应该报错)显示如何使用div-series,
结合练习3.59,生成正切的幂的多项式。