目录 前一节: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) (circular-list-p-1 (cdr list) list 0))(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)))
转自:
阅读(1050) | 评论(0) | 转发(0) |