cons
(cons 1 nil)
列表可以当作是链表,其中的每个节点node
包含了两个指针
car
:节点的值cdr
:下一个节点
cons
的操作,可以理解为在一个链表的基础上,前插一个值。
(cons 1 nil); (1); value: 1, next: nil
(cons 1 '(2 3 4)); (1 2 3 4); value: 1, next: (2 3 4)
(cons '(1) '(2 3 4)); ((1) 2 3 4); value: (1), next: (2 3 4)
也就是说,cons
的第一个元素是head
的值,然后指向后续的链表。
(consp '(1 2 3));T
(consp nil); NIL
检查一个数据是否是这种链表类型,与之相反的是
(atom '(1 2 3)); NIL
(atom nil); T
equal
(eql 'a 'a); T
(eql '(a) '(a)); NIL
前面使用的eql
有一定的局限性:
- 对于基本值来说,它的判断没有问题
- 对于基本元素的集合,它并不判断两个元素列表是否相同
也就是说,它是判断内存地址是否相等,而不是判断属性是否相等。
operation | function |
---|---|
eql | addr_1 == addr_2 |
equal | values_1 == values_2 |
用java
进行区分,就是如下方式
lisp | java |
---|---|
eql | == |
equal | equals |
copy
浅拷贝
go
是值传递的,lisp
中用没用指针呢。
(setf x '(a b c))
(setf y x)
(eql x y); T; x == y
两者相等,也就是说,全部的复制,其实都是指针传递,也就是引用传递,也就是浅拷贝了。
深拷贝
(setf x '(1 2 3))
(setf y (copy-list x))
(eql x y); NIL
(equal x y); T
这种办法,就能够完成全部的值拷贝了
(defun my-copy (lst) (
if (atom lst)
lst
; else
(cons (car lst) (my-copy (cdr lst)))
))
append
(append '(1 2 3) '(4 5 6));(1 2 3 4 5 6); 拼接两个列表
compress
(1 1 1 1 1 1)
太长了,简写一下,记作(1 5)
repeat
(defun repeat (sign times) (lisp sign times) )
_compress
; sign :标志符号
; times:重复计数
; lst :后续列表
(defun _compress (sign times lst) (
if (null lst)
; 如果后续没有元素了,返回当前元素计数列表的列表
; 因为拼接使用的cons,如果只是一层列表,会拆开添加元素
(list (repeat sign times))
; 取得剩余列表的首个元素
(let ((next (car lst)))(
if (eql sign next)
; 如果和原来的字符一样,计数+1,递归调用
(_compress sign (+ 1 times) (cdr lst))
; 如果字符不一样,生成当前表示列表,拼接上后续生成的
(cons (repeat sign times) (_compress (car lst) 1 (cdr lst)))
))
))
compress
(defun compress (lst) (
if (null lst)
lst
(_compress (car lst) 1 (cdr lst))
))
uncompress
repeat
(defun repeat (sign times) (
if (> times 1)
(cons sign (repeat sign (- times 1)))
(list sign)
))
uncompress
(defun unpress (lst) (
if (null lst)
lst
; 取出单个元素
(let ((next (car lst)))(
; 判断元素是列表还是原子
if (listp next)
; 列表的话,取出符号和重复,添加
(let ((sign (car next)) (times (car (cdr next)))) (
append (repeat sign times) (unpress (cdr lst))
))
; 如果是原子的话,直接进行列表转换
(append (list next) (unpress (cdr lst)))
))
))
load
如果不是只想写的函数只在交互串口存在,可以保存到文本。
如果不想每次都需要复制粘贴,那就可以load
一下。
(load "~/lisp/function.lsp")
lisp
的后缀一般为.lsp
或者.lisp
列表访问
什么都是列表,所以访问变得至关重要
(setf lst '(0 1 2 3 4 5 6 7 8 9))
nth
(nth 4 lst) ; 4
你可以理解为它是指定下标的car
.
同数组一般,下标以0
开始。
nthcdr
(nthcdr 4 lst) ; (4 5 6 7 8 9)
指定下标的cdr
last
(last lst); (9)
如果只需要最后一个,直接last
。
注意:返回的是cons
对象,如果需要取出值,需要额外加操作。
(defun my-last (lst) (
car (last lst)
))
len
(defun len (lst) (
if (null lst)
0
(+ (len (cdr lst)) 1)
))
真·深拷贝
浅拷贝没问题了,但是深拷贝够深么。
(setf lst '(1 2 3)) ; 声明一个元素
(setf a (list lst)) ; 创建a
(setf b (list lst)) ; 创建b
(equal a b) ; T ; 两个列表值相同
(eql a b) ; NIL ; 两个列表,而非同一个列表
(eql (car a) (car b)) ; 两个列表的car元素值是同一个
也就是说,前面的深拷贝,只适合元素为原子,也就是基本值类型的时候才适用。
如果是特殊的值,比如列表,都是指针复制,实体值都是同一份,也就算不得深复制。
(defun copy (lst) (
if (atom lst)
lst
(cons (copy (car lst) (copy (cdr lst)))
))
所以需拷贝左右两端,也就是当作树来进行看待了。
需要注意的是,
NIL
作为特殊的元素,有很多的用法。(cons 'a nil) (car nil) (cdr nil)
原本后续必须跟列表的,可以直接跟上
NIL
映射操作
mapcar
(setf values '(0 1 2 3 4 5 6 7 8 9)); values
(defun pow(value) (* value value)); pow
(mapcar #'pow values); (0 1 4 9 16 25 36 49 64 81)
把每一个数据取出来,然后进行操作,生成全新的数据。
maplist
(maplist #'(lambda (x) x) values)
; ((0 1 2 3 4 5 6 7 8 9) (1 2 3 4 5 6 7 8 9) (2 3 4 5 6 7 8 9) (3 4 5 6 7 8 9) (4 5 6 7 8 9) (5 6 7 8 9) (6 7 8 9) (7 8 9) (8 9) (9))
每个元素分别是nthcdr index lst
集合
member
(setf lst '(0 1 2 3 4 5 6 7 8 9))
(member 7 lst); (7 8 9)
(member 23 lst); NIL
检测到了,返回此元素开始的后续列表。
不包含的话,就返回NIL
test
可以传入,自定义的比较方法
(member 5 lst :test #'eql)
所以,可以自定义一个试试
(defun meq (a b) (= (+ a 1) b))
(member 5 lst :test #'meq); (6 7 8 9)
key
就是比较之前,对集合中的数据进行操作一下。
结合前面的test
,我们调整一下
(defun offset (a) (+ a 1))
(member 5 lst :key #'offset :test #'meq); (5 6 7 8 9)
应该能够体会了,还有,两个参数是部分顺序的。
member-if
元素条件检测
(member-if #'(lambda (x) (= 6 x)) lst); (6 7 8 9)
oddp
(oddp 1); T; 奇数
(oddp 2); NIL; 偶数
evenp
(evenp 1); NIL; 奇数
(evenp 2); T; 偶数
addjoin
(setf lst '(1 2 3))
(adjoin 1 lst); (1 2 3)
(adjoin 4 lst); (4 1 2 3)
- 新元素才添加
- 添加到行首
(defun join (value lst) (
if (null (member value lst))
(cons value lst)
lst
))
union(并)
(union '(1 2 3) '(2 3 4)); (1 2 3 4)
intersection(交)
(intersection '(1 2 3) '(2 3 4)); (2 3)
set-difference(补)
(set-difference '(1 2 3) '(2)); (1 3)
(set-difference '(1 2 3) '(2 4)); (1 3)
其实就是移除A
中B
包含的元素,如果B
中本来就不存在的元素,不操作。
序列
length
(length '(1 2 3)); 3
subseq
(subseq '(1 2 3) 0 2); (1 2)
(subseq '(1 2 3) 0); (1 2 3)
- 包头不包尾
- 只传一个,默认为
start
,结尾默认为length
reverse
(reverse '(1 2 3 4 5)); (5 4 3 2 1)
(defun mirror (lst) (equal lst (reverse lst)))
sort
(setf seq '(1 2 3 4 5))
(sort seq #'>); (5 4 3 2 1)
seq; (5 4 3 2 1)
也就是说,按照方式排序以后,是直接改变数据排列的。
如果不想改动原有数据,需要传入副本。
方法在末尾
every
(every #'oddp '(1 3 5 7 9)); T
(every #'oddp '(13 5 7 9 0)); NIL
全部满足,相当于python
中的all
some
(some #'oddp (1 2 4 6 8)); T
(some #'evenp (0 1 3 5 7 9)); T
存在一个, 相当于python
中的any
stack
push
(setf lst '(1))
(push 9 lst); (9 1)
(defun my-push (value lst) (cons value lst))
pop
(setf lst '(2 1))
(pop lst); 2
(defun my-pop (lst)(
let((x (car lst)))
(setf lst (cdr lst))
x
))
点列表
(cons 'a 'b); (A . B)
之前就发现这个问题,现在终于明白
node
一个节点包含两个东西
- car:值
- cdr:下一个值的指针
当cdr
指向的是值而非指针的时候,就会使用.
进行表示。
两者也都可以相互转换
'(A . (B .(C . nil))); (A B C)
不是存粹数值的话,本身也是指针
map
如果节点可以连续两个值,当只有两个值的时候,这不就是字典里么,可以很方便的映射。
(setf man '((name . godme) (age . 99) (gender . male)))
(assoc 'name man); (name . godme)
其实, .
不.
效果效果都一样,只在于取出来的时候。
(setf _name (assoc 'name man)); ((name . godme))
(cdr _name); godme
; list
(serf _name (assoc 'name _man)); (name godme)
(cdr _name); (godme)
(car (cdr _name)); godme
也就是说,.
是正宗的值存储,直接cdr
就能够获取,如果是列表的话,需要额外car
。
来源:CSDN
作者:wait_for_eva
链接:https://blog.csdn.net/wait_for_eva/article/details/103470249