Recursion Lisp-拆分递归
我试图创建一个递归函数,根据所需元素的数量将一个列表拆分为两个列表 例: (一三五七九)(一三五)(七九) (一三五七九)(一三五七九)零) (拆分0'(135 79))(零(135 79)) 我的代码如下:Recursion Lisp-拆分递归,recursion,lisp,common-lisp,Recursion,Lisp,Common Lisp,我试图创建一个递归函数,根据所需元素的数量将一个列表拆分为两个列表 例: (一三五七九)(一三五)(七九) (一三五七九)(一三五七九)零) (拆分0'(135 79))(零(135 79)) 我的代码如下: (defun split (e L) (cond ((eql e 0) '(() L)) ((> e 0) (cons (car L) (car (split (- e 1) (cdr L)))))))) 我找不到一种方法来连接第一个列表元素并返回第二个列表。
(defun split (e L)
(cond ((eql e 0) '(() L))
((> e 0) (cons (car L) (car (split (- e 1) (cdr L))))))))
我找不到一种方法来连接第一个列表元素并返回第二个列表。记住:split返回一个包含两个列表的列表
(defun split (e L)
(cond ((eql e 0)
'(() L)) ; you want to call the function LIST
; so that the value of L is in the list,
; and not the symbol L itself
((> e 0)
; now you want to return a list of two lists.
; thus it probably is a good idea to call the function LIST
; the first sublist is made of the first element of L
; and the first sublist of the result of SPLIT
; the second sublist is made of the second sublist
; of the result of SPLIT
(cons (car L)
(car (split (- e 1)
(cdr L)))))))
好吧,让我们试着推导我们应该做的递归
(split 0 l) = (list () l)
这就是我们的基本情况。现在我们知道了
(split 1 (cons a b)) = (list (list a) b)
但是我们想了想,我们正在建立左边的第一个参数,建立列表的方法是使用CONS
,所以我们写下来
(split 1 (cons a b)) = (list (cons a ()) b)
然后我们想一想什么是(分割0 l)
,我们可以写下n>=1
:
(split n+1 (cons a b)) = (list (cons a l1) l2) where (split n b) = (list l1 l2)
让我们用Lisp写下来:
(defun split (n list)
(ecase (signum n)
(0 (list nil list))
(1 (if (cdr list)
(destructuring-bind (left right) (split (1- n) (cdr list))
(list (cons (car list) left) right))
(list nil nil)))))
最惯用的解决方案是:
(defun split (n list)
(etypecase n
((eql 0) (list nil list))
(unsigned-integer
(loop repeat n for (x . r) on list
collect x into left
finally (return (list left r))))))
尾部递归解决方案
(defun split (n l &optional (acc-l '()))
(cond ((null l) (list (reverse acc-l) ()))
((>= 0 n) (list (reverse acc-l) l))
(t (split (1- n) (cdr l) (cons (car l) acc-l)))))
改进版
(defun split (n l &optional (acc-l '()))
(cond ((null l) (list (reverse acc-l) ()))
((>= 0 n) (list (reverse acc-l) l))
(t (split (1- n) (cdr l) (cons (car l) acc-l)))))
(在此版本中,确保acc-l
位于开头”()
):
测试它:
(split 3 '(1 2 3 4 5 6 7))
;; returns: ((1 2 3) (4 5 6 7))
(split 0 '(1 2 3 4 5 6 7))
;; returns: (NIL (1 2 3 4 5 6 7))
(split 7 '(1 2 3 4 5 6 7))
;; returns ((1 2 3 4 5 6 7) NIL)
(split 9 '(1 2 3 4 5 6 7))
;; returns ((1 2 3 4 5 6 7) NIL)
(split -3 '(1 2 3 4 5 6 7))
;; returns (NIL (1 2 3 4 5 6 7))
在改进的版本中,通过使用标签
(一种let
,允许定义局部函数,但允许它们自己调用,因此允许递归局部函数),递归函数被更深一层(一种封装)
我是如何找到解决方案的:
不知何故,结果中的第一个列表必须是从l
开始依次考虑一个又一个元素的结果。但是,考虑在现有列表的开头而不是结尾添加元素。
因此,依次考虑列表中的car将导致相反的顺序。
因此,很明显,在最后一步中,当返回第一个列表时,它必须是reverse
d。第二个列表只是上一步的(cdr l)
,因此可以在返回结果时添加到上一步的结果中
因此我认为,最好将第一个列表累加到(acc-l
)-累加器主要是尾部递归函数的参数列表中的最后一个元素,即第一个列表的组件。我称之为acc-l-累加器列表
在编写递归函数时,首先要从琐碎的情况开始cond
部分。如果输入是一个数字和一个列表,那么最简单的情况——以及递归的最后一步——就是当
(等于l'())
-->(空l)
(=n0)
-实际上是(zeropn)
。但后来我将其更改为(>=n0)
,以捕获输入为负数的情况cond
部分在其条件中通常具有null
或zerop
。)
当列表l
为空时,必须返回两个列表,而第二个列表为空列表,第一个列表为反向dacc-l
。
您必须使用(list)
构建它们,因为list
参数在返回前不久会得到评估(与quote
='(…)
相反,在最后一步中无法对结果进行评估。)
当n为零时(稍后:当n为负时),则只需返回l作为第二个列表以及迄今为止第一个列表的累积值,但顺序相反
在所有其他情况下,(t…
,列表l
中的car被cons
分配到迄今为止累积的列表中(对于第一个列表):(cons(car l)acc-l)
,我将其作为累加器列表(acc-l
)分配到split
,列表的其余部分作为此调用中的新列表(cdrl)
和(1-n)
。递归调用中的这种递减对于递归函数定义非常典型
这样,我们就涵盖了递归中一个步骤的所有可能性。
这使得递归如此强大:在一个步骤中征服所有的可能性——然后您定义了如何处理几乎无限多的情况
非尾部递归解决方案
(受Dan Robertson的解决方案启发-谢谢Dan!尤其是他用解构绑定的解决方案,我很喜欢。)
和一个仅具有非常基本功能的解决方案(仅null
,list
,=
,let
,t
,cons
,car
,cdr
,cadr
)
啊解构绑定
-这是非常优雅的解决方法!而且(如果(cdr列表)
部分也很整洁。啊,但我也看到:(ecase(signum n)(0第一个案例处理)(1秒案例处理))
可以简化为(如果(nullp n)第一个案例处理第二个案例处理)
。或者(cond((nullp n)第一个案例处理(t第二个案例处理)
哪一个可读性更好,不是吗?与其使用etypecase
,不如使用(if(zeropn)
?我宁愿使用check type
或assert
单独检查类型。这很懒。我在手机上键入了它,而etypecase
让我将验证和特殊情况结合起来。finally子句是针对副作用进行评估的。如果您想返回那里的内容,需要使用return
或return-从
@Svante是的,你是对的。我怀疑那里也可能有一个错误。(拆分3'(1 2 3 4 5 6 7))
在CLISP中返回(1.2)
。第二部分的内容不正确,我认为。。。
(defun split (n l)
(cond ((null l) (list '() '()))
((>= 0 n) (list '() l))
(t (destructuring-bind (left right) (split (1- n) (cdr l))
(list (cons (car l) left) right)))))
(defun split (n l)
(cond ((null l) (list '() '()))
((>= 0 n) (list '() l))
(t (let ((res (split (1- n) (cdr l))))
(let ((left-list (car res))
(right-list (cadr res)))
(list (cons (car l) left-list) right-list))))))