Common Lisp学习笔记(六)

6 list data structure

首先注意cons的用法
(cons 'w '(x y z)) -> (w x y z)
但是想在list的结尾添加一个元素就不能这样实现,eg,
(cons '(w x y) 'z) -> ((w x y) . z)

6.3 the APPEND function

append函数可以参数是两个list,并将两个list的元素合并在一起,如果其中有一个是nil,则结果和另一个list相同

> (append '(a b) '(c d))
(a b c d)
> (append '(a b) nil)
(a b)
> (append nil '(a b))
(a b)
> (append nil nil)
nil
> (append '((a 1) (b 2)) '((c 3) (d 4)))
((a 1) (b 2) (c 3) (d 4)

append函数不会改变变量的值,是nondestructive的,eg,

> (setf who '(only the good))
(only the good)
> (append who '(die young))
(only the good die yound)
> who
(only the good)

append函数执行时,会将第一个list复制一份,然后将最后一个元素的cdr指向第二个list,因此如果参数中第一个不是list的话会出错,而如果第二个不是list则可以执行


cons, list, append都能用来构建list,注意区分它们的用法

6.5 more functions on lists

已经学习过的有cons,list,append,length,现在增加新的reverse,nth,nthcdr,lase,remove

  • reverse: 返回一个list的倒序,只对list的第一级元素操作,对list中的list不会改变顺序,也是nondestructive
  • nthcdr: 返回第n个元素的cdr,如果n为0则返回list本身,如果n大于长度则返回nil,eg,

(nthcdr 1 '(a b c)) -> (b c)

  • nth: 定义如下
    (defun nth (n x) (car (nthcdr n x)))
    

(nth 0 '(a b c)) -> a

  • last: 返回最后一个cons, eg, (last '(a b c . d)) -> (c . d)
  • remove: 将某个元素从list中删除,如果不是特别指明则删除这个元素的所有出现,remove是nondestructive的
    (remove 'a '(b a n a n a) -> ( b n n)
    
6.6 list as sets

集合是无序的,每个元素只出现一次,操作有union,intersection,set difference...

member:如果这个元素在list中能够找到则返回以这个item开头的一个子list,如果找不到则返回nil

> (setf ducks '(a b c))
(a b c)
> (member 'b ducks)
(b c)
> (member 'd ducks)
nil
  • intersection: 返回两个list的交集,顺序是无法保证即随机出现的,当一个list中某个元素不止一个的时候,也可以返回交集,但是该元素是否出现多个是未定义的
  • union
  • set-difference: 返回的差集顺序也是随机的
  • subsetp: 一个list是否是另一个的子集,返回t或者nil

ex 6.26

(defun right-side (x) 
  (rest (member '-vs- x)))

(defun left-side (x)
  (reverse (rest(member '-vs- (reverse x)))))

(defun count-common (x y) 
  (length (intersection x y)))

(defun compare (x) 
  (count-common (left-side x) (right-side x)))
6.8 list as tables

table: association list ,是list的list,其中的每一个元素都是一个list,它们的car是它们的key,eg,

(setf words
    '((one un)
      (two deux)
      (three trois)))

assoc函数在一个table中通过每一个list的第一个元素即car来作为关键字查找,返回这个list,eg,
(assoc 'three words) -> (three trois)
(assoc 'six words) -> nil

rassoc: reverse assoc, 通过每个list的cdr来进行查找,返回这个list,eg,

(setf sounds
    '((cow . moo)
      (pig . oink)
      (cat . meow)))

(rassoc 'moo sounds) -> (cow . moo)
6.9 program with tables

创建一个全局变量table things

(setf things '((object1 large green shiny cube)
               (object2 small red dull metal cube)
               (object3 red small dull plastic cube)
               (object4 small dull blue metal cube)
               (object5 small shiny red four-sided pyramid)
               (object6 large shiny green sphere)))

里面每个list的第一个item是对象名字,剩下的是描述

定义函数返回对象的描述:

(defun description (x)
  (rest '(assoc x things)))

> (description 'object3) -> (red small ... )

给出两个对象,要找出他们属性中不同的item的集合,定义函数:

(defun differences (x y)
  (set-exclusive-or (description x)
                    (description y)))

> (differences 'object2 'object3) -> (metal plastic)

假如有这样一个表格,可以把某项属性的具体描述映射到属性名字,eg,

(setf quality-table
  '((large . size)
    (small . size)
    (red . color)
    (green . color)
    ...)
)

这种表格可以通过assoc来获取属性名字,定义函数来获取属性名字:

(defun quality (x)
  (cdr (assoc x quality-table)))

> (quality 'red) -> color

现在希望给出两个对象,返回它们不同的属性名字的集合,函数如下:

(defun contrast (x y)
  (remove-duplicates
    (sublis quality-table (differences x y))))

> (contrast 'object3 'object4) -> (color material)

remove-duplicates是集合函数中消除重复出现的,sublis将(differences x y)中返回的具体描述的集合映射到属性名字中

ex 6.29

(length table-name)

ex 6.30

(setf books 
  '((war-and-peace  leo-tolstoy)
    (for-whom-the-bell-tolls  haimingwei)
    (lao-ren-yu-hai  haimingwei)
    (meng-de-jie-xi  fuluoyide)
    (lang-chao-zhi-dian  wujun)))

ex 6.31

(defun who-wrote (x)
  (first (cdr (assoc x books))))

> (who-wrote lang-chao-zhi-dian) -> wujun

ex 6.32

who-wrote函数还是返回相同的东西,因为reverse只是把table中所有list的顺序改变,没有改变每个list中内容的顺序

ex 6.33

用assco不同通过作者名字得到书名,除非作者和书名的顺序相反


总结:在lisp中,list可以用来实现其他的数据结构如settable

ex 6.36

(defun cut-first-last (x)
    (reverse (rest (reverse (rest x)))))

(defun swap-first-last (x)
    (append (append (last x) (cut-first-last x)) (cons (first x) nil)))

注意last返回的是最后一个cons,所以可以直接用append进行拼接而不是用cons再加一层list

ex 6.37

(defun rotate-left (x)
    (append (rest x) (cons (first x) nil)))
6.10 trees

subst: (subst x y z) : 用x替换掉z中的所有y,在z中如果没有找到y则不发生改变,eg,

> (subst 'fred 'bill '(bill jones sent me an bill))
(fred jones sent me an fred)

subst不只是在list的最上层进行操作,而是整个list的所有子结构进行查找并替换,eg,

> (subst 'the 'a 
    '((a hatter) (a hare) and (a dormouse)))
((the hatter) (the hare) and (the dormouse))

sublis: 第一个参数是一个table,里面每个item都是一个dot list,每个dot list有2个元素(x . y)表示将x替换为y,第二个参数是将要替代的list,eg,

> (sublis '((roses . violets) (red . blue)) '(roses are red))
(violets are blue)
6.12 shared structure

两个lists有时候可以共享一些cons,通常创建的list不会共享cons,但是可以特意使list有共享cons,eg,

> (setf x '(a b c))
(a b c)

> (setf y (cons 'd (cdr x)))
(d b c)

这个例子中的(b c)就是两个共享的cons

6.13 equality of objects

symbol: 每个symbol在内存中只存在一个地址中,这里的symbol可以理解为几个字母的一个特定组合,如(time after time)中的两个time就指向同一个地址,实际上是同一个对象

list: list则相反,如(a b c)和(a b c)可以是不同的cons,实际在内存的不同地址中存储,当使用equal判断两个list是否想等的时候,其实是对list中每个item的值逐个比较,如果相同就可以说是想等。

如果要知道list是否是地址中的同一个对象,可以使用eq,eq是直接比较地址的,速度也最快,eg,

> (setf x1 (list 'a 'b 'c)
(a b c)

> (setf x2 (list 'a 'b 'c)
(a b c)

> (equal x1 x2)
t

> (eq x1 x2)
nil

> (setf z x1)
(a b c)

> (eq z '(a b c))
nil

> (eq z x1)
t

eq不能用来比较数字,因为在不同的系统中实现不同


eql相对于eq来说可以用来比较数字,如果两个数字类型相同就比较它们的值,相同返回t,如果类型不同就nil,eg,

(eql 'foo 'foo) -> t
(eql 3 3) -> t
(eql 3 3.0) -> nil


对于比较数字,有一个专门的函数=,而且只能用于数字,eg,

(= 3 3.0) -> t

可以看出=只比较数字的值,类型可以不同


equalp: 可以忽视大小写比较字符串,eg,

(equal "foo" "FOO") -> t
  • 一般情况下我们使用equal即可,会对list中的item逐个进行比较
  • member,assoc这种内置函数,在进行比较的时候用的是eql
6.14 kwargs

remove函数的参数可以有关键字参数,如删除元素的个数,eg,

(setf text '(b a n a n a - p a n d a))

> (remove 'a text)
(b n n - p n d)

> (remove 'a text :count 3)
(b n n - p a n d a)

> (remove 'a text :count 2 :from-end t)
(b a n a n a - p n d)

例子中的:count, :from-end都是kwargs, 它们的名字本身就包含前面的:,:from-end表示是否从后面开始删除


kwargs是一种特别的symbol,它们的值和t,nil一样也是自己本身,可以用函数keywordp来判断是否是kwargs


另一个可以加kwargs的函数是member,默认情况下member函数使用eql来判断一个item是否在集合中,但是如果list中的每一个元素也是list,eql则无法判断想等,因为eql是通过比较地址来判断是否是同一个对象的,eg,

(setf cards 
  '((3 clubs) (5 diamonds) (ace spades)))

(member '(5 diamonds) cards) -> nil


为了(member '(5 diamonds) cards)返回t,可以使用equal来进行比较,

> (member '(5 diamonds) cards :test #'equal)
((5 diamonds) (ace spades))

这里参数equal是函数,所以前面加上#'来表示

原文地址:https://www.cnblogs.com/jolin123/p/4477694.html