用递归函数检验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)))))