lisp中的循环指针

Pet*_*teL 0 lisp clisp sbcl common-lisp circular-reference

在完成CLRS Intro to Algorithms一书并试图在常见的lisp中实现红黑二叉搜索树时,我遇到了以下问题:圆形指针:

(defstruct node (ptr nil))

(defparameter *x* (make-node))

(defparameter *y* (make-node :ptr *x*))

(setf (node-ptr *x*) *y*)
Run Code Online (Sandbox Code Playgroud)

此代码导致堆耗尽错误,可能是由于指针指向指向该指针的指针等引起的无限递归.

有没有办法防止这种无限递归发生,同时保持这里给出的指针结构?

我知道还有其他方法可以实现红黑树(例如,不使用setf),但我有兴趣在CLRS中复制命令式样式,因为常见的lisp是一种多范式语言.

PS.除了通常的左子指针和右子指针之外,CLRS中的BST还有指针.

tfb*_*tfb 8

Lisp中的循环没有问题.Common Lisp甚至有一个特殊的语法用于在读取时表达它:例如,类似于#1=(#1# . #1#)缺点,它们的元素都是缺点:你可以通过类似的表达式显式构造它

(let ((c (cons nil nil)))
  (setf (car c) c
        (cdr c) c))
Run Code Online (Sandbox Code Playgroud)

然而,有当一个问题印刷结构,其可包含圆形度.为了正确地执行此操作,您需要一种称为出现检查的东西:当您打印对象(特别是具有组件的对象)时,您需要跟踪是否已经看过此对象,以及是否已安排打印参考,CL通过打印进行#n#,其中n是一个整数,它告诉读者 - 人类读者和Lisp读者 - 这对应于哪个对象,因此他们/它可以重建结构,包括它的共享.更糟糕的是,#n=当你开始打印它时,你必须注释每个可能共享的对象(with ),这将是非常糟糕的,或者避免打印任何东西,直到你遍历所有对象以知道你需要注释哪些对象.

发生检查在空间上计算成本很高(或者在20世纪80年代,当CL被标准化时似乎如此:当然它仍然可以用于非常大的结构),因此它不是CL打印机的默认行为,而是受控制的通过一个特殊的变量*print-circle*:如果这是真的那么圆形(实际上是一般的共享结构)检测由打印机完成; 如果它是假的,则不是,并且打印机可能在打印圆形结构时无限循环或递归.

请注意,问题比圆形更通用:如何打印此对象:

(let ((c1 (cons nil nil))
      (c2 (cons nil nil)))
  (setf (car c1) c2
        (cdr c1) c2))
Run Code Online (Sandbox Code Playgroud)

好吧,我们可以像这样明确地构造它:

(#1=(nil) . #1#)
Run Code Online (Sandbox Code Playgroud)

这就是它的打印方式,如果 *print-circle*是真的,因为在这种情况下,读者会检测共享结构并正确打印.以下是所有这些的演示:

(let ((unshared (cons (cons nil nil) (cons nil nil)))
      (shared (cons (cons nil nil) nil))
      (circular (cons nil nil)))
  ;; construct the sharing explicitly rather than via syntax
  (setf (cdr shared) (car shared)
        (car circular) circular
        (cdr circular) circular)
  (with-standard-io-syntax
    (let ((*print-circle* nil))         ;it is anyway
      ;; don't print cicrular!
      (pprint unshared)
      (pprint shared))
    (let ((*print-circle* t))
      (pprint unshared)
      (pprint shared)
      (pprint circular)))
  ;; be careful not to return anything possibly toxic to the printer
  (values))
Run Code Online (Sandbox Code Playgroud)

这将打印

((NIL) NIL)
((NIL) NIL)
((NIL) NIL)
(#1=(NIL) . #1#)
#1=(#1# . #1#)
Run Code Online (Sandbox Code Playgroud)

请注意,某些非常古老的Lisps(特别是InterLisp)使用引用计数进行存储管理,而语言没有循环参数计数器的问题.我确信没有现代语言使用引用计数.