用递归函数检验lisp-同一变量的循环性

用递归函数检验lisp-同一变量的循环性,lisp,common-lisp,circular-list,Lisp,Common Lisp,Circular List,我正在尝试创建一个函数来测试给定列表是否是循环的,重新开始点是列表的开始 预期成果: (setq liste '(a b c)) (rplacd (cddr liste) liste) (circular liste) => t (circular '(a b c a b c)) => nil 因为我只是想测试是否有任何后续项与第一项“相等”,所以我不想构建整个项目 这是我的密码: (defun circular (liste) (let (beginninglist

我正在尝试创建一个函数来测试给定列表是否是循环的,重新开始点是列表的开始

预期成果:

(setq liste '(a b c))
(rplacd (cddr liste) liste)

(circular liste) => t
(circular '(a b c a b c)) => nil  
因为我只是想测试是否有任何后续项与第一项“相等”,所以我不想构建整个项目

这是我的密码:

(defun circular (liste)
    (let (beginningliste (car liste)))
    (labels ( (circ2 (liste)
      (cond
        ((atom liste) nil)
        ((eq (car liste) beginningliste) t)
        (t (circ2 (cdr liste)))
        ) ) ) ) )
  • 它没有给出预期的结果,但我不知道我的错误在哪里
  • 我不确定我是否正确使用了“标签”
  • 有没有一种不用“标签”就能做到这一点的方法

  • 编辑。我想我已经回答了我的第三个问题,因为我觉得我找到了一个更简单的方法。这样行吗

    (defun circular (liste)
       (cond
          ((atom liste) nil)
          ((eq (car liste) (cadr liste)) t)
          (t  (circular (rplacd liste  (cddr liste))))
          )
       )
    

    首先,当您更改常量数据时,行为是未定义的:当您引用某个内容(此处为列表)时,Lisp环境有权将其视为常量。另请参阅了解为什么
    defparameter
    defvar
    优于
    setq
    。所以

    (setq list '(a b c))
    (rplacd (cddr list) list)
    
    。。。最好写为:

    (defparameter *list* (copy-list '(a b c)))
    (setf (cdr (last *list*)) *list*)
    
    其次,您的代码格式不好,命名约定不好(请使用破折号分隔单词);在emacs的帮助下,它采用了传统布局:

    (defun circularp (list)
      (let (first (car list)))
      (labels ((circ2 (list)
                 (cond
                   ((atom list) nil)
                   ((eq (car list) first) t)
                   (t (circ2 (cdr list))))))))
    
    使用这种格式,两件事应该显而易见:

    • let
      不包含任何主体形式:您定义局部变量,并且从不使用它们;您还可以删除
      let

    • 此外,
      let
      缺少一对括号:您编写的内容首先定义了一个变量名
      ,另一个名为
      car
      ,绑定到
      list
      。我假定您首先要将
      定义为
      (汽车列表)

    • 您定义了一个本地
      circ2
      函数,但从未使用它。我希望
      circularp
      函数(
      -p
      表示“谓词”,如
      numberp
      stringp
      )调用
      (circ2(cdr列表))
      。我更喜欢将
      circ2
      重命名为
      visit
      (或
      recurse
      ),因为这意味着什么

    根据上述更正,这将是:

    (defun circularp (list)
      (let ((first (car list)))
        (labels ((visit (list)
                   (cond
                     ((atom list) nil)
                     ((eq (car list) first) t)
                     (t (visit (cdr list))))))
          (visit (cdr list)))))
    
    但是,如果列表不是循环的,而是多次包含同一元素(如
    ”(a a b))
    ,则将其报告为循环的,因为您只检查它所保存的数据而不是结构。不要在此处查看
    汽车

    (defun circularp (list)
      (let ((first list))
        (labels ((visit (list)
                   (cond
                     ((atom list) nil)
                     ((eq list first) t)
                     (t (visit (cdr list))))))
          (visit (cdr list)))))
    
    此外,内部函数是尾部递归的,但不能保证公共Lisp实现自动消除尾部调用(您应该检查您的实现;大多数可以根据请求执行)。这意味着您可能会在列表中分配尽可能多的调用堆栈帧,这是不好的。最好直接使用循环:

    (defun circularp (list)
      (loop 
        for cursor on (cdr list)
        while (consp cursor)
        thereis (eq cursor list)))
    
    最后,但并非最不重要的一点是:您的方法是一种非常常见的方法,但当列表不是一个大的循环单元格链,而只是在某个地方包含一个循环时,您的方法就会失败。例如,考虑:

    CL-USER> *list*
    #1=(A B C . #1#)
    CL-USER> (push 10 *list*)
    (10 . #1=(A B C . #1#))
    CL-USER> (push 20 *list*)
    (20 10 . #1=(A B C . #1#))
    
    (参见我在哪里解释
    #1=
    #1
    的意思)

    前面有数字的列表显示循环性,但不能仅使用第一个cons单元格作为标记,因为您将永远在循环的子列表中循环。这就是龟兔算法解决的问题(可能还有其他技术,最常见的是将访问的元素存储在哈希表中)


    在您上次编辑之后,如果我想以递归方式检查循环性,而不使用
    标签,我会这样做:

    (defun circularp (list &optional seen)
      (and (consp list)
           (or (if (member list seen) t nil)
               (circularp (cdr list) (cons list seen)))))
    
    我们跟踪
    seen
    中访问的所有cons单元格,该单元格是可选的,并初始化为NIL(您可以传递另一个值,但可以将其视为功能)

    然后,如果列表是cons单元,则它相对于SEED是循环的,而cons单元是:(i)已经存在于SEED中,或者(ii)它的CDR相对于
    (cons list seen)
    是循环的

    这里唯一的额外技巧是确保结果是布尔值,而不是
    成员
    的返回值(这是正在搜索的元素是第一个元素的子列表):如果您的环境将
    *PRINT-CIRCLE*
    设置为NIL,并且列表实际上是循环的,则不希望它尝试打印结果

    除了
    (如果(看到成员列表)t nil)
    ,您还可以使用:

    • (查看成员列表时))
    • (查看职位列表)
    • 当然还有
      (不是(看不到成员名单))

    首先,当您更改常量数据时,行为是未定义的:当您引用某个内容(此处为列表)时,Lisp环境有权将其视为常量。另请参阅了解为什么
    defparameter
    defvar
    优于
    setq
    。所以

    (setq list '(a b c))
    (rplacd (cddr list) list)
    
    。。。最好写为:

    (defparameter *list* (copy-list '(a b c)))
    (setf (cdr (last *list*)) *list*)
    
    其次,您的代码格式不好,命名约定不好(请使用破折号分隔单词);在emacs的帮助下,它采用了传统布局:

    (defun circularp (list)
      (let (first (car list)))
      (labels ((circ2 (list)
                 (cond
                   ((atom list) nil)
                   ((eq (car list) first) t)
                   (t (circ2 (cdr list))))))))
    
    使用这种格式,两件事应该显而易见:

    • let
      不包含任何主体形式:您定义局部变量,并且从不使用它们;您还可以删除
      let

    • 此外,
      let
      缺少一对括号:您编写的内容首先定义了一个变量名
      ,另一个名为
      car
      ,绑定到
      list
      。我假定您首先要将
      定义为
      (汽车列表)

    • 您定义了一个本地
      circ2
      函数,但从未使用它。我希望
      circularp
      函数(
      -p
      表示“谓词”,如
      numberp
      stringp
      )调用
      (circ2(cdr列表))
      。我更喜欢将
      circ2
      重命名为
      visit
      (或
      recurse
      ),因为这意味着什么

    根据上述更正,这将是:

    (defun circularp (list)
      (let ((first (car list)))
        (labels ((visit (list)
                   (cond
                     ((atom list) nil)
                     ((eq (car list) first) t)
                     (t (visit (cdr list))))))
          (visit (cdr list)))))