3.5.2 无限流

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,生成正切的幂的多项式。

猜你喜欢

转载自blog.csdn.net/gggwfn1982/article/details/82386714