lisp初体验-Practical Common Lisp札记-14.因为list,它被称为Lisp
lisp初体验-Practical Common Lisp笔记-14.因为list,它被称为Lisp
上一章讲了向量、哈希表等比Lisp中较常用的数据结构。就如今的Common Lisp而言,的确选择很多,而过去却只有列表(list)这一个选项,so~
当然历史原因只是其一(话说能在历史中被选择的总有其合理之处),就当前通常可见的场景下,List也是一个不错的选择,其实用性早已被时间所证明(有点像数学啊,很多公理都早已被确定~)。所以,了解List,知道选择List的优劣还是很有必要的。
跳过一段"花非花"的论断,让我们直接进入叫做"cons cells"的东东(算是list的原型吧,无需深入,现在都用新的定义方式了):
我们用cons来定义、创建这个结构:
一些基本的动作:
合并、嵌套:
用直观的图形来表示:
原子状态:
模拟个(1 2 3)列表:
当当当...隆重引出我们的列表专用key:List
对应的一些基本动作:
列表还能轻松hold住各种类型的元素:
看起来就像这个样子:
原则上,如果需要,列表可以无限扩展任意深度。
关于列表,Lisp为其配套了一大堆功能性函数,在这儿就不一一阐述,取其典型:append
直观图:
(这图不错啊~)
细心的人可能已经发现,似乎并非是一个全新的列表啊,被你发现了~
似乎符合发现,但不符合预期啊。这种关联是有适用场景的,从某种程度上节约了数据空间的成本。所以,使用的时候需要小心。
其实大部分对列表的操作函数其实是不破坏原表的,除非你写成这个样子:
生成新列表对于确保粒子的原子性有帮助,不过对于空间就比较糟蹋了,如果对空间有需求的话,可以使用类似上面的语句来覆盖原表,达到空间复用的目的:
下面来看一个关于循环和结构共享的例子:
上面的代码创建了独立无外部关联的列表。对于数据空间共享,还有个小窍门:
如果上面的delete改用remove的话就不会出现串联同步修改的问题了。
有无损的函数,自然就会有会损坏原表的,在这种情况下,如果有必要,copy-list是一个不错的选择:
因为一些历史原因,我们还可以看到一些比较奇怪的表达式:
目前只要知道这些表达式的意思就可以了,最好别多用~
以下是一些基本的不完全的功能列表:
作为一种数据结构,可遍历也是list的基本特性,所以map类函数也都有对应的解决方案:
MAPCAR
MAPLIST 对应cdr
MAPCAR/MAPLIST 生成新的list
MAPCAN/MAPCON 生成组合的list
MAPC/MAPL 结果覆盖第一个list
跳过了很多描述性和观点性的东西(有些是晦涩,有些是暂不认同),至于具体的函数,用到时看看help,文档应该很容易覆盖吧。
其实,就总体而言,只要coder有好的代码风格,做到数据传递不被破坏(或者是预期的破坏),一切就都是浮云了。
(未完待续)
上一章讲了向量、哈希表等比Lisp中较常用的数据结构。就如今的Common Lisp而言,的确选择很多,而过去却只有列表(list)这一个选项,so~
当然历史原因只是其一(话说能在历史中被选择的总有其合理之处),就当前通常可见的场景下,List也是一个不错的选择,其实用性早已被时间所证明(有点像数学啊,很多公理都早已被确定~)。所以,了解List,知道选择List的优劣还是很有必要的。
跳过一段"花非花"的论断,让我们直接进入叫做"cons cells"的东东(算是list的原型吧,无需深入,现在都用新的定义方式了):
我们用cons来定义、创建这个结构:
(cons 1 2) ==> (1 . 2)
一些基本的动作:
(car (cons 1 2)) ==> 1 (cdr (cons 1 2)) ==> 2 (defparameter *cons* (cons 1 2)) *cons* ==> (1 . 2) (setf (car *cons*) 10) ==> 10 *cons* ==> (10 . 2) (setf (cdr *cons*) 20) ==> 20 *cons* ==> (10 . 20)
合并、嵌套:
(cons 1 nil) ==> (1) (cons 1 (cons 2 nil)) ==> (1 2) (cons 1 (cons 2 (cons 3 nil))) ==> (1 2 3)
用直观的图形来表示:
原子状态:
模拟个(1 2 3)列表:
当当当...隆重引出我们的列表专用key:List
(list 1) ==> (1) (list 1 2) ==> (1 2) (list 1 2 3) ==> (1 2 3)
对应的一些基本动作:
(defparameter *list* (list 1 2 3 4)) (first *list*) ==> 1 (rest *list*) ==> (2 3 4) (first (rest *list*)) ==> 2
列表还能轻松hold住各种类型的元素:
(list "foo" (list 1 2) 10) ==> ("foo" (1 2) 10)
看起来就像这个样子:
原则上,如果需要,列表可以无限扩展任意深度。
关于列表,Lisp为其配套了一大堆功能性函数,在这儿就不一一阐述,取其典型:append
(append (list 1 2) (list 3 4)) ==> (1 2 3 4)
直观图:
(这图不错啊~)
细心的人可能已经发现,似乎并非是一个全新的列表啊,被你发现了~
(defparameter *list-1* (list 1 2)) (defparameter *list-2* (list 3 4)) (defparameter *list-3* (append *list-1* *list-2*)) *list-1* ==> (1 2) *list-2* ==> (3 4) *list-3* ==> (1 2 3 4) (setf (first *list-2*) 0) ==> 0 *list-2* ==> (0 4) ; as expected *list-3* ==> (1 2 0 4) ; maybe not what you wanted
似乎符合发现,但不符合预期啊。这种关联是有适用场景的,从某种程度上节约了数据空间的成本。所以,使用的时候需要小心。
其实大部分对列表的操作函数其实是不破坏原表的,除非你写成这个样子:
(setf *list* (reverse *list*))
生成新列表对于确保粒子的原子性有帮助,不过对于空间就比较糟蹋了,如果对空间有需求的话,可以使用类似上面的语句来覆盖原表,达到空间复用的目的:
(defparameter *x* (list 1 2 3)) (nconc *x* (list 4 5 6)) ==> (1 2 3 4 5 6) *x* ==> (1 2 3 4 5 6)
下面来看一个关于循环和结构共享的例子:
(defun upto (max) (let ((result nil)) (dotimes (i max) (push i result)) (nreverse result))) (upto 10) ==> (0 1 2 3 4 5 6 7 8 9)
上面的代码创建了独立无外部关联的列表。对于数据空间共享,还有个小窍门:
*list-2* ==> (0 4) *list-3* ==> (1 2 0 4) (setf *list-3* (delete 4 *list-3*)) ==> (1 2 0) *list-2* ==> (0)
如果上面的delete改用remove的话就不会出现串联同步修改的问题了。
有无损的函数,自然就会有会损坏原表的,在这种情况下,如果有必要,copy-list是一个不错的选择:
CL-USER> (defparameter *list* (list 4 3 2 1)) *LIST* CL-USER> (sort *list* #'<) (1 2 3 4) ; looks good CL-USER> *list* (4) ; whoops!
因为一些历史原因,我们还可以看到一些比较奇怪的表达式:
(caar list) === (car (car list)) (cadr list) === (car (cdr list)) (cadadr list) === (car (cdr (car (cdr list)))) (caar (list 1 2 3)) ==> error (caar (list (list 1 2) 3)) ==> 1 (cadr (list (list 1 2) (list 3 4))) ==> (3 4) (caadr (list (list 1 2) (list 3 4))) ==> 3
目前只要知道这些表达式的意思就可以了,最好别多用~
以下是一些基本的不完全的功能列表:
Function | Description |
LAST | Returns the last cons cell in a list. With an integer, argument returns the last n cons cells |
BUTLAST | Returns a copy of the list, excluding the last cons cell. With an integer argument, excludes the last n cells. |
NBUTLAST | The recycling version of BUTLAST; may modify and return the argument list but has no reliable side effects. |
LDIFF | Returns a copy of a list up to a given cons cell. |
TAILP | Returns true if a given object is a cons cell that's part of the structure of a list. |
LIST* | Builds a list to hold all but the last of its arguments and then makes the last argument the CDR of the last cell in the list. In other words, a cross between LIST and APPEND. |
MAKE-LIST | Builds an n item list. The initial elements of the list are NIL or the value specified with the :initial-element keyword argument. |
REVAPPEND | Combination of REVERSE and APPEND; reverses first argument as with REVERSE and then appends the second argument. |
NRECONC | Recycling version of REVAPPEND; reverses first argument as if by NREVERSE and then appends the second argument. No reliable side effects. |
CONSP | Predicate to test whether an object is a cons cell. |
ATOM | Predicate to test whether an object is not a cons cell. |
LISTP | Predicate to test whether an object is either a cons cell or NIL. |
NULL | Predicate to test whether an object is NIL. Functionally equivalent to NOT but stylistically preferable when testing for an empty list as opposed to boolean false. |
作为一种数据结构,可遍历也是list的基本特性,所以map类函数也都有对应的解决方案:
MAPCAR
(mapcar #'(lambda (x) (* 2 x)) (list 1 2 3)) ==> (2 4 6) (mapcar #'+ (list 1 2 3) (list 10 20 30)) ==> (11 22 33)
MAPLIST 对应cdr
MAPCAR/MAPLIST 生成新的list
MAPCAN/MAPCON 生成组合的list
MAPC/MAPL 结果覆盖第一个list
跳过了很多描述性和观点性的东西(有些是晦涩,有些是暂不认同),至于具体的函数,用到时看看help,文档应该很容易覆盖吧。
其实,就总体而言,只要coder有好的代码风格,做到数据传递不被破坏(或者是预期的破坏),一切就都是浮云了。
(未完待续)