Lisp - Split递归

时间:2018-05-22 18:35:33

标签: recursion lisp common-lisp

我试图制作一个递归函数,根据想要的元素数量将列表拆分为两个列表。

  

例如:

     
    

(分3'(1 3 5 7 9))((1 3 5)(7 9))

         

(分裂7'(1 3 5 7 9))((1 3 5 7 9)NIL)

         

(分裂0'(1 3 5 7 9))(无(1 3 5 7 9))

  

我的代码是这样的:

(defun split (e L) 
  (cond ((eql e 0) '(() L))
        ((> e 0) (cons (car L) (car (split (- e 1) (cdr L))))))))

我找不到加入第一个列表元素并返回第二个列表的方法。

3 个答案:

答案 0 :(得分:3)

记住: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)))))))

答案 1 :(得分:3)

尾递归解决方案

(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位于开头'()):

(defun split (n l)
  (labels ((inner-split (n l &optional (acc-l '()))
             (cond ((null l) (list (reverse acc-l) ()))
                   ((= 0 n)  (list (reverse acc-l) l))
                   (t (inner-split (1- n) (cdr l) (cons (car l) acc-l))))))
    (inner-split n 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))

在改进版本中,通过使用labels(允许定义局部函数的let种类,将递归函数放置得更深一层(一种封装))以允许他们自己调用的方式 - 所以它允许递归本地函数。)

我是如何找到解决方案的:

很明显,结果中的第一个列表必须是从l的开头以连续的顺序逐个出现一个元素。但是,consing在其开头而不是结束时向现有列表添加元素。 因此,连续出售列表中的汽车将导致逆序。 因此,很明显,在最后一步中,当返回第一个列表时,它必须是reverse d。第二个列表只是最后一步的(cdr l),因此可以在返回结果时将其添加到最后一步的结果中。

所以我想,最好将第一个列表累积到(acc-l)中 - 累加器主要是尾递归函数的参数列表中的最后一个元素,即第一个列表的组件。我叫它acc-l - accumulator-list。

在编写递归函数时,可以用普通的情况开始cond部分。如果输入是一个数字和一个列表,那么最简单的情况 - 以及递归的最后一步,就是这种情况,

  1. 列表为空(equal l '()) ---> (null l)
  2. 且数字为零----> (= n 0) - 实际上(zerop n)。但后来我将其更改为(>= n 0)以捕获负数作为输入的情况。
  3. (因此,递归cond部分通常会在其条件中nullzerop。)

    当列表l为空时,则必须返回两个列表 - 而第二个列表是空列表,第一个列表是 - 不直观地 - reverse d {{1} }。 您必须使用acc-l构建它们,因为(list )参数在返回之前不久会被评估(与list = quote相反,其中结果无法在最后评估步骤。)

    当n为零(以及之后:当n为负数)时,除了将l作为第二个列表返回l以及为第一个列表累积的内容之前没有任何事情要做 - 但顺序相反。

    在所有其他情况'(...)中,列表(t ...)的汽车l已列入到目前为止累积的列表(第一个列表):cons我将此作为累加器列表((cons (car l) acc-l))提供给acc-l,将其余列表作为此调用split(cdr l)中的新列表。递归调用中的这种递减对于递归函数定义非常典型。

    由此,我们已经涵盖了递归中一步的所有可能性。 这使得递归变得如此强大:在一步中征服所有可能性 - 然后你已经定义了如何处理几乎无限多的案例。

    非尾递归解决方案 (灵感来自Dan Robertson的解决方案 - 谢谢你Dan!特别是他的解决方案(1- n)我很喜欢。)

    destructuring-bind

    只有非常基本功能的解决方案(仅限(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))))) nulllist>=lettconscarcdr

    cadr

答案 2 :(得分:2)

好吧,让我们尝试推导出我们应该做的递归。

(split 0 l) = (list () l)

这就是我们的基本案例。现在我们知道

(split 1 (cons a b)) = (list (list a) b)

但是我们想了一下,我们正在构建左边的第一个参数,并且建立列表的方式是CONS,所以我们写下来

(split 1 (cons a b)) = (list (cons a ()) b)

然后我们会思考一下,我们会考虑(split 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))))))