图 3.6 游程编码 (Run-length encoding):压缩

    在餐厅的情境下,这个算法的工作方式如下。一个女服务生走向有四个客人的桌子。“你们要什么?” 她问。“我要特餐,” 第一个客人说。 “我也是,” 第二个客人说。“听起来不错,” 第三个客人说。每个人看着第四个客人。 “我要一个 cilantro soufflé,” 他小声地说。 (译注:蛋奶酥上面洒香菜跟酱料)

    瞬息之间,女服务生就转身踩着高跟鞋走回柜台去了。 “三个特餐,” 她大声对厨师说,“还有一个香菜蛋奶酥。”

    1. > (compress '(1 1 1 0 1 0 0 0 0 1))
    2. ((3 1) 0 1 (4 0) 1)

    当相同的元素连续出现好几次,这个连续出现的序列 (sequence)被一个列表取代,列表指明出现的次数及出现的元素。

    主要的工作是由递归函数 compr 所完成。这个函数接受三个参数: elt , 上一个我们看过的元素; n ,连续出现的次数;以及 lst ,我们还没检查过的部分列表。如果没有东西需要检查了,我们调用 n-elts 来取得 n elts 的表示法。如果 lst 的第一个元素还是 elt ,我们增加出现的次数 n 并继续下去。否则我们得到,到目前为止的一个压缩的列表,然后 cons 这个列表在 compr 处理完剩下的列表所返回的东西之上。

    要复原一个压缩的列表,我们调用 uncompress (图 3.7)

    1. (if (null lst)
    2. nil
    3. (let ((elt (car lst))
    4. (rest (uncompress (cdr lst))))
    5. (if (consp elt)
    6. (append (apply #'list-of elt)
    7. (cons elt rest)))))
    8. (defun list-of (n elt)
    9. (if (zerop n)
    10. nil
    11. (cons elt (list-of (- n 1) elt))))

    这个函数递归地遍历这个压缩列表,逐字复制原子并调用 list-of ,展开成列表。

    我们其实不需要自己写 。内置的 make-list 可以办到一样的事情 ── 但它使用了我们还没介绍到的关键字参数 (keyword argument)。

    图 3.6 跟 3.7 这种写法不是一个有经验的Lisp 程序员用的写法。它的效率很差,它没有尽可能的压缩,而且它只对由原子组成的列表有效。在几个章节内,我们会学到解决这些问题的技巧。

    1. 载入程序
    2. 在这节的程序是我们第一个实质的程序。
    3. 通常我们会把程序写在一个文件,
    4. 然后使用 load Lisp 读取函数的定义。
    5. 如果我们把图 3.6 3.7 的程序,
    6. 存在一个文件叫做,“compress.lisp”然后输入
    7. (load "compress.lisp")
    8. 到顶层,或多或少的,
    9. 我们会像在直接输入顶层一样得到同样的效果。
    10. 注意:在某些实现中,Lisp 文件的扩展名会是“.lsp”而不是“.lisp”。