4000-520-616
欢迎来到免疫在线!(蚂蚁淘生物旗下平台)  请登录 |  免费注册 |  询价篮
主营:原厂直采,平行进口,授权代理(蚂蚁淘为您服务)
咨询热线电话
4000-520-616
当前位置: 首页 > 新闻动态 >
热卖商品
新闻详情
基本数据类型之三 ── cons cell 和列表 - 水木社区Emacs版
来自 : smacs.github.io/elisp/05-cons- 发布时间:2021-03-25

所以 elisp的hash table 不是一个首要的数据结构,只要不对效率要求很高,通常直接用association list。数组可以作为关联表,但是数组不适合作为与人交互使用数据结构(毕竟一个有意义的名字比纯数字的下标更适合人脑)。所以关联表的地位在 elisp 中就非比寻常了,emacs 为关联表专门用 c 程序实现了查找的相关函数以提高程序的效率。在 association list 中关键字是放在元素的 CAR 部分,与它对应的数据放在这个元素的 CDR 部分。根据比较方法的不同,有 assq 和assoc 两个函数,它们分别对应查找使用 eq 和 equal 两种方法。例如:

(assoc a (( a 97) ( b 98))) ; = ( a 97)(assq a ((a . 97) (b . 98))) ; = (a . 97)

通常我们只需要查找对应的数据,所以一般来说都要用 cdr 来得到对应的数据:

(cdr (assoc a (( a 97) ( b 98)))) ; = (97)(cdr (assq a ((a . 97) (b . 98)))) ; = 97

assoc-default 可以一步完成这样的操作:

(assoc-default a (( a 97) ( b 98))) ; = (97)

如果查找用的键值(key)对应的数据也可以作为一个键值的话,还可以用 rassoc 和 rassq 来根据数据查找键值:

(rassoc (97) (( a 97) ( b 98))) ; = ( a 97)(rassq 97 ((a . 97) (b . 98))) ; = (a . 97)

如果要修改关键字对应的值,最省事的作法就是用 cons 把新的键值对加到列表的头端。但是这会让列表越来越长,浪费空间。如果要替换已经存在的值,一个想法就是用 setcdr 来更改键值对应的数据。但是在更改之前要先确定这个键值在对应的列表里,否则会产生一个错误。另一个想法是用 assoc 查找到对应的元素,再用 delq 删除这个数据,然后用 cons 加到列表里:

(setq foo (( a . 97) ( b . 98))) ; = (( a . 97) ( b . 98));; update value by setcdr(if (setq bar (assoc a foo)) (setcdr bar this is a ) (setq foo (cons ( a . this is a ) foo))) ; = this is a foo ; = (( a . this is a ) ( b . 98));; update value by delq and cons(setq foo (cons ( a . 97) (delq (assoc a foo) foo))) ; = (( a . 97) ( b . 98))

如果不对顺序有要求的话,推荐用后一种方法吧。这样代码简洁,而且让最近更新的元素放到列表前端,查找更快。

把列表当树用

列表的第一个元素如果作为结点的数据,其它元素看作是子节点,就是一个树了。由于树的操作都涉及递归,现在还没有说到函数,我就不介绍了。(其实是我不太熟,就不班门弄斧了)。

遍历列表最常用的函数就是 mapc 和 mapcar 了。它们的第一个参数都是一个函数,这个函数只接受一个参数,每次处理一个列表里的元素。这两个函数唯一的差别是前者返回的还是输入的列表,而 mapcar 返回的函数返回值构成的列表:

(mapc 1+ (1 2 3)) ; = (1 2 3)(mapcar 1+ (1 2 3)) ; = (2 3 4)

另一个比较常用的遍历列表的方法是用 dolist。它的形式是:

(dolist (var list [result]) body...)

其中 var 是一个临时变量,在 body 里可以用来得到列表中元素的值。使用 dolist 的好处是不用写lambda 函数。一般情况下它的返回值是 nil,但是你也可以指定一个值作为返回值(我觉得这个特性没有什么用,只省了一步而已):

(dolist (foo (1 2 3)) (incf foo)) ; = nil(setq bar nil)(dolist (foo (1 2 3) bar) (push (incf foo) bar)) ; = (4 3 2)
其它常用函数

如果看过一些函数式语言教程的话,一定对 fold(或叫 accumulate、reduce)和 filter 这些函数记忆深刻。不过 elisp 里好像没有提供这样的函数。remove-if 和 remove-if-not 可以作 filter 函数,但是它们是 cl 里的,自己用用没有关系,不能强迫别人也跟着用,所以不能写到 elisp 里。如果不用这两个函数,也不用别人的函数的话,自己实现不妨用这样的方法:

(defun my-remove-if (predicate list) (delq nil (mapcar (lambda (n) (and (not (funcall predicate n)) n)) list)))(defun evenp (n) (= (% n 2) 0))(my-remove-if evenp (0 1 2 3 4 5)) ; = (1 3 5)

fold 的操作只能用变量加循环或 mapc 操作来代替了:

(defun my-fold-left (op initial list) (dolist (var list initial) (setq initial (funcall op initial var))))(my-fold-left + 0 (1 2 3 4)) ; = 10

这里只是举个例子,事实上你不必写这样的函数,直接用函数里的遍历操作更好一些。

产生数列常用的方法是用 number-sequence(这里不禁用说一次,不要再用 loop 产生 tab-stop-list 了,你们 too old 了)。不过这个函数好像 在emacs21 时好像还没有。

解析文本时一个很常用的操作是把字符串按分隔符分解,可以用 split-string 函数:

(split-string key = val \\\\s-*=\\\\s-* ) ; = ( key val )

与 split-string 对应是把几个字符串用一个分隔符连接起来,这可以用 mapconcat 完成。比如:

(mapconcat identity ( a b c ) \\t ) ; = a b c 

identity 是一个特殊的函数,它会直接返回参数。mapconcat 第一个参数是一个函数,可以很灵活的使用。

;; 列表测试(consp OBJECT)(listp OBJECT)(null OBJECT);; 列表构造(cons CAR CDR)(list rest OBJECTS)(append rest SEQUENCES);; 访问列表元素(car LIST)(cdr LIST)(cadr X)(caar X)(cddr X)(cdar X)(nth N LIST)(nthcdr N LIST)(last LIST optional N)(butlast LIST optional N);; 修改 cons cell(setcar CELL NEWCAR)(setcdr CELL NEWCDR);; 列表操作(push NEWELT LISTNAME)(pop LISTNAME)(reverse LIST)(nreverse LIST)(sort LIST PREDICATE)(copy-sequence ARG)(nconc rest LISTS)(nbutlast LIST optional N);; 集合函数(delete-dups LIST)(memq ELT LIST)(member ELT LIST)(delq ELT LIST)(delete ELT SEQ)(remq ELT LIST)(remove ELT SEQ);; 关联列表(assoc KEY LIST)(assq KEY LIST)(assoc-default KEY ALIST optional TEST DEFAULT)(rassoc KEY LIST)(rassq KEY LIST);; 遍历函数(mapc FUNCTION SEQUENCE)(mapcar FUNCTION SEQUENCE)(dolist (VAR LIST [RESULT]) BODY...);; 其它(number-sequence FROM optional TO INC)(split-string STRING optional SEPARATORS OMIT-NULLS)(mapconcat FUNCTION SEQUENCE SEPARATOR)(identity ARG)

用 list 生成 (a b c)

答案是 (list a b c)。很简单的一个问题。从这个例子可以看出为什么要想出用 来输入列表。这就是程序员“懒”的美德呀!

nthcdr 的一个实现
(defun my-nthcdr (n list) (if (or (null list) (= n 0)) (car list) (my-nthcdr (1- n) (cdr list))))

这样的实现看上去很简洁,但是一个最大的问题的 elisp 的递归是有限的,所以如果想这个函数没有问题,还是用循环还实现比较好。

my-subseq 函数的定义
(defun my-subseq (list from optional to) (if (null to) (nthcdr from list) (butlast (nthcdr from list) (- (length list) to))))

(setcdr foo foo) 是什么怪东西?

可能你已经想到了,这就是传说中的环呀。这在 info elisp - Circular Objects 里有介绍。elisp 里用到这样的环状列表并不多见,但是也不是没有,org 和 session 那个 bug 就是由于一个环状列表造成的。

本文链接: http://constab.immuno-online.com/view-729890.html

发布于 : 2021-03-25 阅读(0)
公司介绍
品牌分类
联络我们
服务热线:4000-520-616
(限工作日9:00-18:00)
QQ :1570468124
手机:18915418616