Common Lisp学习之五:集合数据结构

1 向量
向量是基于数字索引的集合,分为变长和定长两种。

(vector values) 用来创建并初始化一个定长的向量。

(make-array dim &key initial-element fill-pointer adjustable ...) 
用来 创建变长向量,其中命名参数initial-element可以指定元素的初始值,fill-pointer可以指定初始自动填充的容量,而adjustable用来说明数组是否为可增长的。

vector-push 在固定容量的向量尾部添加一个元素
vector-pop 弹出最近压入的数据

vector-push-extend 对于非固定容量的向量添加数据。

向量的字面表示是 #(...),这一点和字符串"hello"的字面值类似,使用字面方式赋值变量得到的是常量,不可修改。

2 向量子类型
可以使用make-array创建多种类型的向量,这点可以通过指定:element-type来控制。例如字符串则指定'character,位数组'bit

3 序列函数
vector和list都是抽象类型sequence的子类。下面讨论的函数均可以应用于向量和列表。

3.1 位置访问
(length seq) 返回序列的长度
(elt pos seq) 返回指定位置的值,支持setf

3.2 迭代函数
CL提供了一组迭代函数使得无需编写显式循环就可以遍历序列。
函数名 格式 输出
count (count x seq) 序列中出现项的次数
find (find x seq) 项或NIL
position (position x seq) 索引或NIL
remove (remove x seq) 移除后序列,返回类型同
substitute (substitute new old seq) 替换后的序列,返回类型同

这些函数可以增加参数来增强其行为。
:test 关键字可以传递接受两个参数返回bool的函数,用于替代默认的EQL比较函数
:key  关键字用来传递单参数函数,这个函数被应用到序列中每个元素上,此值随后被送入比较函数,此参数并不影响返回值。
:start :end 限制迭代的范围,若:end为nil或不存在,则与指定其长度相同
:from-end 序列的元素以相反的顺序被检查,其会影响find和position的结果
:count 对于remove和substitue用来指定最多进行多少次指定操作,若希望移除所有重复,则使用remove-duplicates。

以下这些函数各有两个变体,即增加后缀if/if-not,变体函数接受一个谓词函数,只有满足谓词的元素才会被处理。在变体中,不再需要参数:test。

3.3 序列操作
(copy-seq seq) 返回相同类型的新序列
(reverse seq) 返回代表团的同类型新序列
(concatenate &body) 创建一个将任意数量序列连接在一起的新序列,参数可以是不同类型的,因而需要指定结果的类型,描述符如'vector,'list,'string。
(merge type seq1 seq2 funp)  合并两个序列

3.4 排序
(sort seq func) 根据func比较进行排序,类似的有stable-sort
这两个函数都是破坏性的,原序列会破坏,因而在需要时,需要保存原数组,并需要记录返回结果,因为结果并不在原引用上。

3.5 子序列操作
(subseq seq start end) 获取子序列,subseq也支持setf,但不会扩大或缩小所指向的序列
(search sub seq)  在seq中查找sub的出现位置
(mismatch seq seq :start1 :end1 :start2 :end2) 返回两个串不匹配的位置,如果串匹配返回NIL,此外也支持:test :from-end参数

3.6 序列谓词
(every  funp  seq)   判断seq是否全部满足谓词
(some   funcp seq)   判断seq部分满足谓词
(notany  funp seq)   判断seq都不满足谓词返回真
(notevery funp seq)  在seq都满足时返回假

3.7 序列映射函数
(map type n-fun seq1 ... seqn) 将N元函数依次应用到N个序列的对应元素上,返回一个序列
(reduce 2-fun seq)  将2元函数应用到seq的前两个元素上,将结果与第三个元素用于此函数,依次类推,其接受:key,:start,:end,:from-end,:initital-value用于初始值。

4 HASH
(make-hash-table :test)可以创建HASH表,CL中hash表的字面形式为#<hash-table ...>
(gethash key ht)  获取表中key的值,若不存在,返回NIL,支持setf;其返回值是两个结果(multiple-value-bind (x y) (func))用于绑定多个返回结果。
(remhash key ht)  删除相应的项
(clrhash ht)  清空HASH中的项

4.1 HASH迭代
(maphash 2-func ht)  接受一个二元的K,V处理函数,遍历hash表。

5 列表处理

5.1 点对
点对以(X.Y)的字面形式表示,可以使用cons来构造,点对的两个元素分别用car和cdr获取,这两个位置也都支持setf操作。如下图,点对可以看作如下的形式。

x y

点对中的元素可以是任何类型对象的引用,因此通过点对可以构造出更大型的结构。列表便是通过将点对以链状连接一起构成的。列表的首元素保存在car中,后续点对的链接保存在cdr中,最后一个元素的CDR为NIL。如'(1 2 3)的形式如下:

对于列表,定义了First Second ...tenth,REST等众多的访问函数,可以方便的取得相要的数据。
列表的嵌套可以表示任意复杂的数据结构,如树形结构,列表("foo" (1 2) 10)的表示如下:


5.2 列表的不同类型操作
列表定义了众多的操作函数,大部分是满足函数式范式的;但为了性能,也有一些是破坏性副作用,需要仔细区分开来。

对于诸如(append seq1 seq2)会创建seq的复本,但会利用seq2的数据,因而结果会和seq2数据共享,修改seq2的同时也会修改返回结果。这个操作并没有修改已有的对象,因而并不是破坏性的,但它产生了共享结构。如果将副作用操作和返回结构共享结果的函数混合使用,那就需要小心不要疏忽修改共享结构。

修改已有对象的操作被称作是破坏性的,但存在两种相当不同的破坏性操作,即副作用目的和回收目的的破坏。
对于副作用目的的,例如setf/vector-push等,其目的就是为了使用其副作用;

而另一种用于回收目的的副作用,将会利用实参的点对单元作为原料。因而只有原始实参对象不再需要的情况下,回收性函数才可以安全的使用。例如,对于reverse操作,其是函数性的,直接返回一个倒序的版本,原列表的点对如果不再被引用,则可被垃圾回收。而nreverse是回收性的,进行原地操作,只是倒转了原链表的指针方向,不需要垃圾回收。但是,这样引用原列表的引用此时将指向新引用的尾结点。

通常回收函数是非回收函数在前面加N来命名,但也有特殊的。
append  的回收版本为nconc,nconc将后续lst共享式的并入第一个参数,使得不需要回收节点
remove  回收版本为delete delete-if delete-if-not delete-duplicates

只有当你知道参数在函数返回之后不再被使用时,才能安全的使用它们。多数回收性函数的副作用并未严格说明。但也有严格说明副作用的一组函数。nconc/nsubstitute[if/if-not]。

最好的编程风格是,不管是什么函数,我们只用它来产生返回值。

5.3 组合回收性和共享结构

使用共享结构是基于不在乎由哪些点对单元构成列表这一前提,而使用回收性函数则需要精确知道哪些点对单元会在哪里被引用到。

在实践中,回收性函数会有一些惯用法。
最常见的一个是构造一个列表,它由一个在列表前端不断做点对分配操作的函数返回,通常是将元素push进保存局部变量的列表里,然后返回对其nreverse的结果。
另一种常见的用法是将回收性函数的返回值重新赋值给原参数的引用。如(setf foo (nreverse foo))

5.4 列表处理函数
(nth idx lst)   返回lst中idx位的元素
(nthcdr n lst)  返回对lst使用cdr若干次的结果


5.5 列表映射
CL专门提供了6个特定于列表的映射处理函数。
名称 描述                                                   
mapcar (mapcar n-func list1 ... listn) 函数式,将函数应用到各个列表的对应元素上,结果构成列表
maplist (maplist n-func lst1 ... lstn)  函数式,将函数依次应用到各列表及cdr元素,结果构成列表
mapcan
(apply #'nconc (mapcar f x1 ... xn))
mapcon
(apply #'nconc (maplist f x1 ... xn))
mapc 类似mapcar,非函数式,只返回第一个列表实参,当函数副作用有用时才有用
mapl 类似maplst,非函数式,只返回第一个列表实参,当函数副作用有用时才有用


6 树及其它数据结构

6.1 树
由点对单元构建的数据结构即可看作列表,也可以看作树。有两类不同的函数,列表函数和树函数。两类函数的区别在于函数将访问哪些点对单元里去寻找此列表或树的值。
列表函数可访问的点对单元称为列表结构,其查找方式是以第一个点对单元开始,然后跟着CDR引用直到遇到NIL。
树函数则是同时随着CAR和CDR引用前进。
下面两张图展示了针对列表((1 2) (3 4) (5 6))应用COPY-LIST和COPY-TREE区别。
                           COPY-LIST的结果


                           COPY-TREE的结果
TREE-EQL可以比较两棵树是否相同。
以下一些函数均是以树模式进行处理的函数:substitute,nsubstitute,subst以及这些函数的IF/IF-NOT版本。

6.2 集合
可以将任何列表看作集合,CL提供几个函数用于列表进行集合论意义上的操作。但要记住,这并不高效,当数据量较大时,性能会成为问题。

(adjoin item set) 将item放入set中,并返回新的set
(pushnew item set) 修改set将元素插入
(member item set) 中item存在则返回其点对,否则为NIL
(intersection set1 set2)     交
(union set1 set2)            并
(set-difference set1 set2)   差
(set-exclusive-or set1 set2) 互斥或
(subset sub set)  是否为子集判断

6.3 关联表和属性表
关联表,简称alist;属性表,简称为plist。
6.3.1 alist
alist是一种数据结构,它可将一些键映射到值上,同时也支持反射查询,当给定一个值时,可以查找其对应的键。alist支持添加键值来掩盖已有的映射,并且当此映射 删除后,原先的映射会再暴露出来。

下图是alist a:1 b:2 c:3的结构。键保存在点对的CAR中,值保存在CDR中。
(assoc key alist)  查询alist中第一次匹配的键的点对单元,存在对应的IF/IF-NOT形式
(acons k v alist)  向alist添加映射
(rassoc v alist)   通过值反射查询映射
(pairlis list-a list-b)  按两个列表的对应元素构造一个alist a[i]:b[i]

6.3.2 plist
plist是另一种数据结构。下图是plist a:1 b:2 c:3的结构。

(getf plist key) 使用EQ来查找指定的键
(remf plist key) 使用EQ来移除指定的键
(get-prperties plist keylist) 根据key列表查找plist并返回。

6.3.3 plist与符号的关系
每个符号对象都有一个相关联的plist,以便用于保存关于此符号的信息。可以用symbol-plist整个列表。

(get 'symbol key)  获取符号的指定key
(remprop 'symbol key)  删除符号的指定的key

6.4 destructuring-bind
这个宏可以用来拆分列表
(destructuring-bind (param*) list
    body-form*)
参数列表可以包含宏参数列表支持的任何参数类型,如&optional &rest &key参数。
(destructuring-bind (x y z) (list 1 2 3)
  (list :x x :y y :z z)) ==> (:X 1 :Y 2 :Z 3)

(destructuring-bind (x y z) (list 1 (list 2 20) 3)
  (list :x x :y y :z z)) ==> (:X 1 :Y (2 20) :Z 3)

(destructuring-bind (x (y1 y2) z) (list 1 (list 2 20) 3)
  (list :x x :y1 y1 :y2 y2 :z z)) ==> (:X 1 :Y1 2 :Y2 20 :Z 3)

(destructuring-bind (x (y1 &optional y2) z) (list 1 (list 2 20) 3)
  (list :x x :y1 y1 :y2 y2 :z z)) ==> (:X 1 :Y1 2 :Y2 20 :Z 3)

(destructuring-bind (x (y1 &optional y2) z) (list 1 (list 2) 3)
  (list :x x :y1 y1 :y2 y2 :z z)) ==> (:X 1 :Y1 2 :Y2 NIL :Z 3)

(destructuring-bind (&key x y z) (list :x 1 :y 2 :z 3)
  (list :x x :y y :z z)) ==> (:X 1 :Y 2 :Z 3)

(destructuring-bind (&key x y z) (list :z 1 :y 2 :x 3)
  (list :x x :y y :z z)) ==> (:X 3 :Y 2 :Z 1)


最后还有另一种参数,&whole,如果被使用,它必须位于参数列表的第一个参数,并且其会绑定到整个列表形式上。在其后,其他参数可以无视&whole的存在,完成List的解构。
(destructuring-bind (&whole whole &key x y z) (list :z 1 :y 2 :x 3)
  (list :x x :y y :z z :whole whole))
==> (:X 3 :Y 2 :Z 1 :WHOLE (:Z 1 :Y 2 :X 3))


7 位操作
(mask-field spec num)   返回按spec的位模式与的num的值

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
函数名称 描述 PL_3d? 如果多段线的检查是3D或不 PL_AddPoint 加入一个新的多段线之前指定的顶数量VxNum PL_Arced? 如果检查多段线有至少一个弧段 PL_ArcPl2LinearPl 重建更换指定的距离直线段只有所有弧段的多段线列表。 PL_BoundPoly 创建直线,多段线和/或圆周围的边界多边形 PL_BreakX 在这一上打断多段线 PL_DelPoint 从多段线删除 PL_DividedPoints 返回除以给定的多段线(无论是在“实体”的形式或列表的形式)所获得的 PL_DrawLeaders 沿线各段多段线绘制的领袖,从而呈现出多段线的流动方向。 PL_DrawLeaders2 沿线各段多段线绘制两端箭头。 PL_facelist 返回从一个多面对象的面部定义列表 PL_Fitted? 检查是否有多段线在选集中 PL_FlipArcedPolyline 翻转(反转)的圆弧(2D)多段线使用特殊技巧 PL_Get2dPolyElev 找到一个二维多段线的标高 PL_GetBulgeLst 返回凸出圆弧多段线列表 PL_GetMiddlePoint 查找行的中间,多段线,圆弧或样条线对象 PL_GetSlope @ 在某一时间的斜率的曲线对象 PL_GetVxEntl 返回完整的实体,包括多段线顶实体的实体列表的列表, PL_GetWidths 返回一个列表的开始和结束段多段线宽度 PL_MapMask 创建出多段线和/或圆形的不透明的面具,变成区实体。 PL_MeasuredPoints 返回通过测量给定的多段线(无论是在“实体”的形式或列表的形式)获得的分 PL_Mesh? 如果一个多段线对象的检查是一个三维网格对象或否 PL_mk_pl 给个列表,绘制多段线或LWPOLYLINE的entmake功能 PL_mk_pl_feed 给个列表,绘制多段线或LWPOLYLINE的,使用命令行坐标传输; PL_NewPoint 在指定的顶更新多段线 PL_Open? 判断多段线的检查是打开“或”不 PL_plist 返回一个LWPOLYLINE或多段线表 PL_PlJoin 连接选择集中多段线或线段。 PL_plpick 多段线挑毛,挑个毛,看里面。 PL_PolyFace? 检查多段线对象是否是一个多面网格 PL_ReconstructPoly 重建炸开的多段线对象 PL_SeekMain 寻求从顶名主要实体名称 PL_Spl2Pl_Int 转换到一个正常的多段线样条或ELLISPE对象的插值方法 PL_Splined? 检查是否拟合多段线 PL_SplitPoly 拟合多条多段线。 PL_SplPl2Pl 拟合多段线转换到正常的多段线 PL_UpdateLine 更新与两个新端的线对象。保持所有行属性,包括句柄 PL_UpdatePoly 更新一个新的顶列表的多段线实体。保留所有多段线特性:

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值