在浏览Bert Burgemeister的“Common Lisp Quick Reference”时,我偶然发现了tailp
。
首先,我误解了这个函数的定义。我试过了:
(tailp '(3 4 5) '(1 2 3 4 5))
但它返回了
NIL
CLTL2说,tailp
是真的 iff 第一个参数是现有(nthcdr n list)
的任何n
。
(nthcdr 2 '(1 2 3 4 5))
;; (3 4 5)
我进一步尝试了:
(tailp '(3 4 5) '(1 2 3 4 5))
;; NIL - and I would expect: T following the definition above.
(tailp '() '(1 2 3 4 5))
;; T
(tailp '5 '(1 2 3 4 . 5))
;; T
在我尝试之前(然后理解tailp
查找l的cdr
,即使是相同的地址。
(defparameter l '(1 2 3 4 5 6))
(tailp (nthcdr 3 l) l)
;; T
但接下来我提出了下一个问题:
For what such a function is useful at all?
查看子列表是否属于alist的一部分是不是更有用的函数? (或看起来像列表的一部分,而不是它必须共享相同的地址?)
注:
啊,好吧,我开始明白,也许这对于列表的eq
部分来说是cdr
......有点......“任何cdr
- 给定列表eq
的衍生物到第一个参数?“。
但也许有人可以解释我在哪种情况下这样的测试非常有用?
答案 0 :(得分:6)
tailp
的基本目的是检查是否共享了列表结构。这意味着 cons cells 是否相同(这意味着EQL
作为谓词) - 而不仅仅是缺点单元格的内容。
还可以检查某个项目是否在最后cdr
:
CL-USER 87 > (tailp t '(1 2 3 4 . t))
T
CL-USER 88 > (tailp nil '(1 2 3 4 . nil))
T
CL-USER 89 > (tailp nil '(1 2 3 4))
T
CL-USER 90 > (tailp #1="e" '(1 2 3 4 . #1#))
T
这是Common Lisp中很少使用的函数之一。
答案 1 :(得分:3)
我非常确定(tailp '(3 4 5) '(1 2 3 4 5))
的答案可以是t
和nil
,因为智能编译器可以(tailp '#1=#(3 4 5) '(1 2 . #1#))
来减少内存占用。引用的东西是不可变的文字,所以为什么不两次使用相同的内存?
以下是tailp
的工作原理:
(defparameter *tail* (list 3 4 5))
(defparameter *larger* (list* 1 2 *tail*))
(defparameter *replica* (copy-list *larger*))
(tailp *tail* *replica*) ; ==> nil
(tailp *tail* *larger*) ; ==> t
由于copy-list
为列表中的每个元素创建了新的缺点,因此它将共享
除了任何其他列表的空列表。它是独一无二的。
*larger*
使用*tail*
的公共尾部进行制作,因此(tailp *tail* *larger*)
将为t
。
将参数作为相同的对象进行比较非常重要。由于尾部不需要是列表,因此它与eql
进行比较。在比较内容是否相同时,您使用equal
,因此tailp
更具体。它必须是指针等于(eq
)或eql
原子值。
那么你在哪里使用它?我正在考虑一种功能数据结构,您通常会重用共享结构。 tailp
可能用于标识父项的子树。它基本上是一个更高性能的版本:
(defun my-tailp (needle haystack)
(cond ((eql needle haystack) t)
((atom haystack) nil)
(t (my-tailp needle (cdr haystack)))))
答案 2 :(得分:3)
以下是tailp
有用的案例:
(defun circular-list-p (l)
(and (consp l)
(tailp l (rest l))))
几个笔记。
这在所有情况下终止:如果第一个参数不是第二个参数的尾部(即没有要求它检查循环性),则允许tailp
不在循环列表上终止,但是如果它不需要检查循环,则它必须终止。第一个参数是第二个的尾巴。但是,如果列表是循环的,那正是我们在这里检查的,所以它终止了。 (我有一段时间对此感到困惑)。
consp
检查结果是(circular-list-p nil)
是错误的:我认为这是实用的选择,尽管你可能会认为nil
是循环的。
答案 3 :(得分:1)
@Sylwester:
谢谢@Sylwester!
我最近在Edi Weitz的书中读过关于尾部摇摆的书:
(defparameter *list* (list 'a 'b 'c 'd))
(defparameter *tail* (cdddr *list*))
这将列表中最后一个cons单元格的汽车命名为*tail*
- 现在,可以添加一个新元素并重命名列表中最后一个cons单元格的新最后一辆汽车{{1 }}。
*tail*
现在列表是:
(setf (cdr *tail*) (cons 'e 'nil)
*tail* (cdr *tail*))
;; (E)
并且可以通过*list*
;; (A B C D E)
将其他内容添加到setf
,而无需再次遍历列表。 (因此提高了性能。但当然有警告,因为这是一种破坏性行为)。
也许,如果有人像你那样命名一个列表:
*tail*
和(defparameter *my-new-tail* '(F G H))
它到新列表的末尾
tail wagg
啊或者:
(setf (cdr *tail*) *my-new-tail*
*tail* (cddr *my-new-tail*))
然后
(defparameter *tail-of-my-new-tail* (cddr *my-new-tail*))
;; and then
(setf (cdr *tail*) *my-new-tail*
*tail* *tail-of-my-new-tail*)
(tailp *my-new-tail* *list*)
之外的新尾部,或者*my-new-tail*
是尾部摇摆到*my-new-tail*
的最后尾部。 .. *list*
在尾部摆动的情况下是一个非常有用的测试...... tailp
来测试是否一个给定列表作为最近添加的尾部贡献给另一个列表...... 在阅读你的答案时,我想到了这一点,@ Sylwester! (在阅读书中的尾巴摇摆时我没有意识到这一点 - (这是一个非常有用的方式!)谢谢你的答案!