2.2.1 表示序列

 2.2.1 表示序列
我们用数据对构建的有用的结构之一是序列。即数据对象的有序的集合。
当然了,用数据对有多种方式表示序列。一个特别的常规的表示方式如图2.4
序列1,2,3,4被表示为一个数据对的链。 任何一个数据对的头部相对应的是链中的
一个项,数据对的尾部是链中的下一个数据对。 最后一个数据对的尾部通过指向
一个数据对中不存在的特殊值,来标识着序列的结尾处,这个特殊的值,在黑盒
与指针图的表示法中表示为一个斜线,在程序中是变量nil的值。整个序列被
级联的cons操作组装起来。
(cons 1
       (cons 2
              (cons 3
               (cons 4 nil)
               )
        )
)

由级联的cons形成的数据对的序列,被叫做列表。 为了组合生成列表,
SCHEME语言提供了一个原生的操作list.  上述的序列能用(list 1 2 3 4)
来生成。 通常来说,(list 1 2 3 4) 等价于 (cons 1 (cons 2 (cons 3 (cons 4 nil))))

LISP系统的传统的方式打印列表是打印元素的序列,外边包含一层括号。
所以图2.4中的数据对象被打印成 (1 2 3 4)
(define one-through-four (list 1 2 3 4))

one-through-four
(1 2 3 4)

 注意不要混淆表达式(list 1 2 3 4) 和 列表(1 2 3 4),它是表达式被执行的结果。
 尝试着执行表达式(1 2 3 4),即执行程序1,使用 2 3 4 这三个参数,将返回一个错误。

 我们能思考一下,CAR作为列表中第一个元素的选择函数,CDR作为列表中除了第一个元素
 之外的其它的元素的子集合的选择函数。CAR和CDR的嵌套使用,可以用来提取列表的第二个,
 第三个等等的元素。组合子 CONS能够生成一个列表,与原生的类似,却是把新的元素加在
 开头处。
 (car one-through-four)
 1

 (cdr one-through-four)
 (2 3 4)

 (car (cdr one-through-four))
 2

 (cons 10 one-through-four)
 (10 1 2 3 4)

 (cons 5 one-through-four)
 (5 1 2 3 4)

 nil的作用是,用来中止数据对的链条,能被认为是没有元素的序列,即一个空的列表。
 单词nil是一个拉丁单词nihil的略写,它的意思是什么都没有。

列表的操作

对于表示元素的序列的数据对的使用,成为了列表,通过使用传统的连续地遍历列表的方式
来操作列表的编程技术来实现。 例如 程序 list-ref 需要一个列表和一个常数N 作为参数,
返回第N个元素。它规定从0开始。 计算list-ref 的方法如下:
N=0,返回列表的CAR操作结果。否则,列表的CDR操作的第N-1项元素。

(define (list-ref items n)
   (if (= n 0)
        (car items)
 (list-ref (cdr items) (- n 1))
    )
)

(define squares (list 1 4 9 16 25))
(list-ref squares 3)
16

我们经常CDR操作整个列表,为了辅助实现这个目标,SCHEME包括了一个原生的判断 NULL?,
它测试它的参数是否是空列表。 程序length返回一个列表中的元素的个数。演示它的使用的
经典的模板如下:

(define (length items)
     (if (null? items)
           0
    (+ 1 (length (cdr items)))
      )
)

(define odds (list 1 3 5 7 ) )
(length odds)
4
程序length 实现了一个简单的递归的计划,递归的步骤是 任何一个列表的元素的个数等于
1加上 列表的CDR的结果。它递归到空列表的个数是0。 我们也能用迭代的方式计算 length.

(define (length items)
    (define (length-iter a count)
       (if (null? a )
          count
   (length-iter (cdr a ) (+ 1 count))
       )
     )
   (length-iter items 0) 
)

当遍历一个列表时,组装一个目标的列表的另一个传统的编程技术是使用程序 append.
它有两个列表作为参数,把这两个列表中的元素组成一个新的列表。

(append squares odds)
(1 4 9 16 25 1 3 5 7)
(append odds squares)
(1 3 5 7 1 4 9 16 25)

append能使用一个递归的方式实现。为了append 列表1 和2 ,操作如下:
如果列表1是空的,结果等于列表2。 否则 append 列表1 的尾部和列表2。
再加上列表1的头部。
(define (append list1 list2)
   (if (null? list1)
      list2
      (cons (car list1) (append (cdr list1) list2))
   )
)
练习2.17
定义一个程序last-pair返回给定的非空列表的最后一个元素。
例如: (last-pair (list 23 72 149 34))
(34)

练习2.18
定义一个程序reverse以一个列表作为参数返回有相同元素但是以倒序排列的列表。
(reverse (list 1 4 9 16 25))
(25 16 9 4 1)

练习2.19
考虑一下在1.2.2部分的换零钱程序。
例如为了计算换英镑的兑换方式的总数量,能够容易地更换程序中使用的货币就太好了。
正如程序中所写的那样,货币的知识被部分地分散到程序first-denomination 和
程序 count-change 中了。 为了兑换提供一个钱币面值的列表就太好了。

我们要重新写一个CC程序,它的第二个参数是钱币面值的列表,而不是指定钱币的数值。
我们要有定义各种钱币的列表。
(define us-coins (list 50 25 10 5 1))
(define uk-coins (list 100 50 20 10 5 2 1 0.5)

我们能够如下地调用CC。
(cc 100 us-coins)
292

为了实现目标,我们需要对程序CC进行一点修改。它仍然有相同的模型,但是它读取的第二个参数不同了。
如下所示:

(define (cc amount coin-values)
   (cond ((=  amount 0) 1
      (( or (< amount 0) (no-more? coin-values)) 0)
         (else
            (+ (cc amount
                  (except-first-denomination coin-values))
                (cc (- amount
                         (first-denomination coin-values))
                     coin-values)))))
用列表结构的原生的操作函数来定义如下的函数first-denomination,except-first-denomination和 no-more?
  coin-values的列表的顺序能影响到 cc的执行结果吗? 为什么能影响呢,或者是为什么不能呢?

练习2.20
程序+,*和list可以接受任意多个参数。定义这样的程序的一种方式是
使用define和点结尾的标识符。在一个程序定义中,一个参数列表中在最后一个参数名称前有一个点符号,
当程序被调用时,初始化参数得到相应的实际参数的值,像普通的情况一样,但是最后一个参数的值是
剩余的其它的实际参数组成的列表。例如,给定如下的定义:

(define (f x y . z) <body>)

程序f能被调用有两个或者更多个实际的参数。 如果我们解释如下的表达式:

(f 1 2 3 4  5 6)

那么在f的程序体中,x等于1,y等于2 ,z是列表(3 4 5 6).给定如下的定义:

(define (g . w) <body>)

程序g能被调用有0个或者更多个实际的参数。 如果我们解释如下的表达式:

(g 1 2 3 4 5 6)

那么在g的程序体中,w是列表(1 2 3 4 5 6).

使用这种标识法,写一个程序 same-parity 接受一个或者多个整数,
返回一个参数的列表,这些参数与第一个参数有相同的奇偶性。例如,

(same-parity 1 2 3 4 5 6 7)
(1 3 5 7)

(same-parity 2 3 4 5 6 7)
(2 4 6)

* 在列表上做映射

一个极其有用的操作是在列表上的每个元素上做一些变换,生成一个新的列表。
例如,下面的程序缩放列表中的每一个元素,以特定的比例值。

(define (scale-list  items factor)
      (if (null? items) 
           nil
           (cons (* (car items) factor)
                 (scale-list  (cdr items) factor)
            )
       )
)

(scale-list (list 1 2 3 4 5) 10)

(10 20 30 40 50)

我们能抽象这个通用的想法,捕捉到它的公共的模式,来表达成一个高级函数,
如部分1.3
这个高级函数在这里叫做MAP. MAP使用一个函数和一个列表做为参数。
返回一个对每个元素进行变换后的元素的列表。

(define (map proc items)
      (if (null? items) 
           nil
           (cons (proc (car items))
                 (map proc (cdr items))
            )
       )
)

(map abs (list -10 2.5 -11.6 17))
(10 2.5 11.6 17)

(map (lambda (x) (* x x)) (list 1 2 3 4))
(1 4 9 16)

现在我们能够用MAP给出scale-list的一个新定义。

(define (scale-list items factor)
   (map  (lambda (x) (* x factor))
         items))

MAP是一个重要的组合子,不仅因为它捕捉到一个公共的模式,
而是因为它建立起处理列表操作的一个高层次的抽象。在scale-list的原始
定义中,程序的递归结构把注意力吸引到了列表的逐个元素的操作上。
定义 scale-list用MAP,就屏蔽了操作的细节,强调了元素的列表到结果的列表
的缩放变换。 这两个定义的区别,不是计算机执行不同的操作流程,而是我们对
流程的思考不同。从效果上看,MAP帮助我们建立了一种抽象,隔离出变换程序的
元素被如何提取出来和如何组装起来的实现细节。象图2。1中显示的那样,
这种抽象给我们一种可以改变序列的低层实现的灵活性,并保持了变换一个序列到另一个序列的
操作的概念上的框架。在2.2.3部分中,作为一个组织程序的框架,拓展了序列的这种使用。

练习2.21
程序square-list以一个以数据为元素的列表作为参数,返回一个那些数的平方值的列表。

(square-list (list 1 2 3 4))
(1 4 9 16)

这有两个不同的程序square-list的定义。通过填上缺失的表达式完成这两个程序。

(define (square-list items)
   (if (null? items)
          nil
        (cons <??> <??>))
)

(define (square-list items)
       (map <??> <??>))

练习2.22
Louis Reasoner试着重写了练习2.21中的第一个程序square-list,为了
让它进化成迭代的执行过程:

(define (square-list items)
  (define (iter things answer)
     (if (null? items)
          answer
       (iter (cdr things) (cons  (square  (car things)) answer))
     )
   )
  (iter items nil)
)

不幸的是,这次定义的square-list生成的答案,是期望的答案的逆序。为什么?

Louis试着通过交换cons的参数的顺序来修正这个错误:

(define (square-list items)
  (define (iter things answer)
     (if (null? items)
          answer
       (iter (cdr things) (cons answer (square  (car things)) ))
     )
   )
  (iter items nil)
)

这也没有有效,解释一下为什么。

练习2.23
程序for-each与map是很相似的。它以一个程序和一个列表为参数。
然而,不是形成一个结果的列表,而是仅仅应用程序到各个参数中,
以从左到右的顺序。应用程序到列表中的元素得到的值,for-each并没有使用,而是
使用程序执行相应的动作,例如打印。例如:

(for-each (lambda (x) (newline) (display x))
          (list 57 321 88))
57
321
88
调用for-each返回的值可能是有些随意的,例如真值。
给出一个for-each的实现。

猜你喜欢

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