20070622

Elisp入门5:基本数据类型之四──序列和数组(转寄)

发信人: happierbee (吾生也有涯,而知也无涯), 信区: Emacs
标 题: Elisp入门5:基本数据类型之四──序列和数组
发信站: 水木社区 (Fri Jun 22 22:08:42 2007), 转信

目录
前一节:Elisp入门4:基本数据类型之三──cons cell 和列表
后一节:Elisp入门6: 基本数据类型之五──符号

序列是列表和数组的统称,也就是说列表和数组都是序列。它们的共性是内部的
元素都是有序的。elisp 里的数组包括字符串、向量、char-table 和布尔向量
。它们的关系可以用下面图表示:

_____________________________________________
| |
| Sequence |
| ______ ________________________________ |
| | | | | |
| | List | | Array | |
| | | | ________ ________ | |
| |______| | | | | | | |
| | | Vector | | String | | |
| | |________| |________| | |
| | ____________ _____________ | |
| | | | | | | |
| | | Char-table | | Bool-vector | | |
| | |____________| |_____________| | |
| |________________________________| |
|_____________________________________________|

数组有这样一些特性:

数组内的元素都对应一个下标,第一个元素下标为 0,接下来是 1。数组内
的元素可以在常数时间内访问。
数组在创建之后就无法改变它的长度。
数组是自求值的。
数组里的元素都可以用 aref 来访问,用 aset 来设置。

向量可以看成是一种通用的数组,它的元素可以是任意的对象。而字符串是一种
特殊的数组,它的元素只能是字符。如果元素是字符时,使用字符串相比向量更
好,因为字符串需要的空间更少(只需要向量的1/4),输出更直观,能用文本
属性(text property),能使用 emacs 的 IO 操作。但是有时必须使用向量,
比如存储按键序列。

由于 char-table 和 bool-vector 使用较少,而且较难理解,这里就不介绍了

测试函数

sequencep 用来测试一个对象是否是一个序列。arrayp 测试对象是否是数组。
vectorp、char-table-p 和 bool-vector-p 分别测试对象是否是向量、
char-table、bool-vector。

序列的通用函数

一直没有提到一个重要的函数 length,它可以得到序列的长度。但是这个函数
只对真列表有效。对于一个点列表和环形列表这个函数就不适用了。点列表会出
参数类型不对的错误,而环形列表就更危险,会陷入死循环。如果不确定参数类
型,不妨用 safe-length。比如:

(safe-length '(a . b)) ; => 1
(safe-length '#1=(1 2 . #1#)) ; => 3

思考题
写一个函数来检测列表是否是一个环形列表。由于现在还没有介绍 let 绑
定和循环,不过如果会函数定义,还是可以用递归来实现的。

取得序列里第 n 个元素可以用 elt 函数。但是我建议,对于已知类型的序列,
还是用对应的函数比较好。也就是说,如果是列表就用 nth,如果是数组就用
aref。这样一方面是省去 elt 内部的判断,另一方面读代码时能很清楚知道序
列的类型。

copy-sequence 在前面已经提到了。不过同样 copy-sequence 不能用于点列表
和环形列表。对于点列表可以用 copy-tree 函数。环形列表就没有办法复制了
。好在这样的数据结构很少用到。

数组操作

创建字符串已经说过了。创建向量可以用 vector 函数:

(vector 'foo 23 [bar baz] "rats")

当然也可以直接用向量的读入语法创建向量,但是由于数组是自求值的,所以这
样得到的向量和原来是一样的,也就是说参数不进行求值,看下面的例子就明白
了:

foo ; => (a b)
[foo] ; => [foo]
(vector foo) ; => [(a b)]

用 make-vector 可以生成元素相同的向量。

(make-vector 9 'Z) ; => [Z Z Z Z Z Z Z Z Z]

fillarray 可以把整个数组用某个元素填充。

(fillarray (make-vector 3 'Z) 5) ; => [5 5 5]

aref 和 aset 可以用于访问和修改数组的元素。如果使用下标超出数组长度的
话,会产生一个错误。所以要先确定数组的长度才能用这两个函数。

vconcat 可以把多个序列用 vconcat 连接成一个向量。但是这个序列必须是真
列表。这也是把列表转换成向量的方法。

(vconcat [A B C] "aa" '(foo (6 7))) ; => [A B C 97 97 foo (6 7)]

把向量转换成列表可以用 append 函数,这在前一节中已经提到。

函数列表

;; 测试函数
(sequencep OBJECT)
(arrayp OBJECT)
(vectorp OBJECT)
(char-table-p OBJECT)
(bool-vector-p OBJECT)
;; 序列函数
(length SEQUENCE)
(safe-length LIST)
(elt SEQUENCE N)
(copy-sequence ARG)
(copy-tree TREE &optional VECP)
;; 数组函数
(vector &rest OBJECTS)
(make-vector LENGTH INIT)
(aref ARRAY IDX)
(aset ARRAY IDX NEWELT)
(vconcat &rest SEQUENCES)
(append &rest SEQUENCES)

问题解答

测试列表是否是环形列表

这个算法是从 safe-length 定义中得到的。你可以直接看它的源码。下面是我
写的函数。

(defun circular-list-p (list)
(if (consp list)
(circular-list-p-1 (cdr list) list 0)
nil))

(defun circular-list-p-1 (tail halftail len)
(if (eq tail halftail)
t
(if (consp tail)
(circular-list-p-1 (cdr tail)
(if (= (% len 2) 0)
(cdr halftail)
halftail)
(1+ len))
nil)))

--

※ 修改:・happierbee 于 Jun 22 23:06:17 修改本文・[FROM: 202.108.12.*]
※ 来源:・水木社区 newsmth.net・[FROM: 202.108.12.*]