Recursion 使用LISP递归地将列表一分为二

Recursion 使用LISP递归地将列表一分为二,recursion,lisp,elisp,sequence,Recursion,Lisp,Elisp,Sequence,使用LISP,我需要创建一个函数,将一个列表拆分为两个列表。第一个列表由第1、3、5、7等元素组成,第二个列表由第2、4、6等元素组成 输出示例: (SPLIT-LIST ( )) => (NIL NIL) (SPLIT-LIST '(A B C D 1 2 3 4 5)) => ((A C 1 3 5) (B D 2 4)) (SPLIT-LIST '(B C D 1 2 3 4 5)) => ((B D 2 4) (C 1 3 5)) (SPLIT-LIST '(A)

使用LISP,我需要创建一个函数,将一个列表拆分为两个列表。第一个列表由第1、3、5、7等元素组成,第二个列表由第2、4、6等元素组成

输出示例:

(SPLIT-LIST ( )) => (NIL NIL)

(SPLIT-LIST '(A B C D 1 2 3 4 5)) => ((A C 1 3 5) (B D 2 4))

(SPLIT-LIST '(B C D 1 2 3 4 5)) => ((B D 2 4) (C 1 3 5))

(SPLIT-LIST '(A)) => ((A) NIL)
函数需要是递归的。

这是到目前为止我的代码

(defun SPLIT-LIST (L)
  (cond
    ((null L) NIL)
    ((= 1 (length L)) (list (car L)))
    (t (cons (cons (car L) (SPLIT-LIST (cddr L))) (cadr L)))))
);cond
);defun
稍后我将尝试使用flatte,这样我就可以得到两个列表,但是现在,我似乎无法正确地获得序列

我的代码:

> (SPLIT-LIST '(1 2 3 4))
((1 (3) . 4) . 2)
我只是不能让代码打印出12324而不是12342

> (SPLIT-LIST '(1 2 3 4 5 6))
((1 (3 (5) . 6) . 4) . 2)
我无法按正确的顺序打印预期输出的后半部分。

以下是我得到的:

(取消拆分列表(lst)
(如果是lst
(如果(cddr lst)
(let((l(拆分列表(cddr lst)))
(名单
(副驾驶(第一节车厢)(第一节车厢))
(反对党(第一选区)(第一选区)))
`((,(汽车lst)),(cdr lst)))
"(无无))
在阅读了SICP之后,我很少对递归感到困惑。
我强烈推荐它。

以下是我的观点,使用内部函数:

(defun split-list (lst)
  (labels ((sub (lst lst1 lst2 flip)
             (if lst
               (if flip
                 (sub (cdr lst) (cons (car lst) lst1) lst2 (not flip))
                 (sub (cdr lst) lst1 (cons (car lst) lst2) (not flip)))
               (list (reverse lst1) (reverse lst2)))))
    (sub lst nil nil t)))
你的代码 我们通常通过缩进来读取Lisp代码,而不是用大写字母来编写。因为我们是通过缩进来阅读的,所以我们不需要在它们自己的行中放置结束符(或者任何真正的结束符)。那么,正确格式化的代码是:

(defun split-list (l)
  (cond
    ((null l) '())
    ((= 1 (length l)) (list (car l)))
    (t (cons (cons (car l)
                   (split-list (cddr l)))
             (cadr l)))))
正确处理基本情况
拆分列表
始终应返回两个列表的列表。我们应该先讨论这些基本情况。当
l
为空时,左列表或右列表中没有任何内容,因此我们只需返回
'(())
。然后第一个条件变成:

((null l) '(() ())) ; or ((endp l) '(() ()))
从你的第二个例子来看,我想你希望第二个和第三个例子是:(I)如果只剩下一个元素,它必须是奇数的,并且属于左边的结果;(ii)否则,至少剩下两个元素,我们可以在每个元素中添加一个。那么第二个条件应该是

((= 1 (length l)) (list (car l) '()))
在每一步检查
l
的长度实际上有点贵。您只关心是否只剩下一个元素。您已经知道
l
不是空的(从第一个案例开始)
,因此您可以检查
l
的其余部分是否为空列表。我发现当使用cons单元格作为列表时,使用
first
rest`更具可读性,因此我将第二条写为:

((endp (rest l)) (list (list (first l)) '()))
处理递归案例 现在,您的最后一个案例是至少有两个元素。这意味着
l
看起来像
(xy.zs)
。您需要做的是在
zs
上调用
splitlist
,以获得
(奇数zs偶数zs)
形式的一些结果,然后将其拆分并构造
((x.奇数zs)(y.偶数zs))
。看起来是这样的:

(t (let ((split-rest (split-list (rest (rest l)))))
     (list (list* (first l) (first split-rest))
           (list* (second l) (second split-rest)))))
(defun split-list (list &optional (odds '()) (evens '()))
  (if (endp list)
      (list (nreverse odds)
            (nreverse evens))
      (split-list (rest list)
                  evens
                  (list* (first list) odds))))
事实上,有一些方法可以解决这个问题。我们可以使用解构绑定同时拉出
奇数zs
偶数zs
。因为这是cond的最后一个子句,如果没有body forms,子句将返回测试的值,所以我们不需要初始的
t
。最后一条可以是:

((destructuring-bind (odd-zs even-zs)            ; *
       (split-list (rest (rest l)))
     (list (list* (first l) odd-zs)
           (list* (second l) even-zs))))))
*我省略了
t
测试,因为如果
cond
子句没有主体形式,则返回测试值。这在这里很管用

总而言之,我们已经将您的代码修改为

(defun split-list (l)
  (cond
    ((endp l) '(() ()))
    ((endp (rest l)) (list (list (first l)) '()))
    ((destructuring-bind (odd-zs even-zs) 
         (split-list (rest (rest l)))
       (list (list* (first l) odd-zs)
             (list* (second l) even-zs))))))

其他办法 我认为值得探索一些尾部递归的方法,因为支持尾部调用优化的实现可以将它们转换为循环。公共Lisp中的尾部递归函数通常也很容易转换为
do
循环,这些循环更可能作为迭代实现。在这些解决方案中,我们将以相反的方式建立结果列表,然后在返回结果列表时将其反转

一次递归一个元素 如果左右切片可以互换 如果两个列表中的哪一个是第一个并不重要,您可以使用如下内容:

(t (let ((split-rest (split-list (rest (rest l)))))
     (list (list* (first l) (first split-rest))
           (list* (second l) (second split-rest)))))
(defun split-list (list &optional (odds '()) (evens '()))
  (if (endp list)
      (list (nreverse odds)
            (nreverse evens))
      (split-list (rest list)
                  evens
                  (list* (first list) odds))))
这实际上可以使用
do
循环非常简洁地编写,但这通常被视为是迭代的,而不是递归的:

(defun split-list (list)
  (do ((list list (rest list))
       (odds '() evens)
       (evens '() (list* (first list) odds)))
      ((endp list) (list (nreverse odds) (nreverse evens)))))
如果他们不能互换 如果您总是需要包含原始列表的第一个元素的列表作为第一个元素,那么您需要多一点逻辑。一种可能性是:

(defun split-list (list &optional (odds '()) (evens '()) (swap nil))
  (if (endp list)
      (if swap 
          (list (nreverse evens)
                (nreverse odds))
          (list (nreverse odds)
                (nreverse evens)))
      (split-list (rest list)
                  evens
                  (list* (first list) odds)
                  (not swap))))
我认为
(如果交换…)
实际上有点难看。我们可以使用
cond
,这样我们可以得到多个表单(或者
if
progn
),并在返回之前交换
赔率
偶数
的值。我认为这实际上更容易阅读,但如果你的目标是一个纯粹的递归解决方案(学术作业?),那么你可能也在避免变异,因此
rotatef
将不可用,并且在
时使用
仅仅获得一些副作用可能会受到反对

(defun split-list (list &optional (odds '()) (evens '()) (swap nil))
  (cond
    ((endp list)
     (when swap (rotatef odds evens))
     (list (nreverse odds) (nreverse evens)))
    ((split-list (rest list)
                 evens
                 (list* (first list) odds)
                 (not swap)))))
这也有助于
do

(defun split-list (list)
  (do ((list list (rest list))
       (odds '() evens)
       (evens '() (list* (first list) odds))
       (swap nil (not swap)))
      ((endp list) 
       (when swap (rotatef odds evens))
       (list (nreverse odds) (nreverse evens)))))
一次递归两个元素 另一种更直接的方法是通过
cddr
(即
(rest(rest…)
)沿着列表递归,并在每个递归的左、右子列表中添加元素。但是,我们需要注意的是,当输入中有奇数个元素时,不要意外地将一个额外的
nil
添加到
right
列表中

(defun split-list (list &optional (left '()) (right '()))
  (if (endp list)
      (list (nreverse left)
            (nreverse right))
      (split-list (rest (rest list))
                  (list* (first list) left)
                  (if (endp (rest list))
                      right
                      (list* (second list) right)))))
同样,一个
do
版本:

(defun split-list (list)
  (do ((list list (rest (rest list)))
       (left '() (list* (first list) left))
       (right '() (if (endp (rest list)) right (list* (second list) right))))
      ((endp list) (list (nreverse left) (nreverse right)))))
作为常见的Lisp循环:

(defun split-list (list)

  "splits a list into ((0th, 2nd, ...) (1st, 3rd, ...))"

  (loop for l = list then (rest (rest l))

        until (null l)                     ; nothing to split

        collect (first l) into l1          ; first split result

        unless (null (rest l))
        collect (second l) into l2         ; second split result

        finally (return (list l1 l2))))

内部尾部递归函数以自上而下的方式构建列表(无反转,
循环
代码可能编译成等价的东西),并使用head-sentinel技巧(为简单起见)


在Lisp中定义展平很有趣。但我从来没有用过它。
因此,如果您认为“我可以使用flatte来解决这个问题”,可能是因为您试图解决错误的问题。

请注意,您的代码没有实现前两个
cond
子句(基本情况)
(defun split-list (L)
    (if (endp L)
    '(nil nil)
    (let ((X (split-list (cdr L))))
        (list (cons (car L) (cadr X)) (car X))
)))
(defun split-list (list)

  "splits a list into ((0th, 2nd, ...) (1st, 3rd, ...))"

  (loop for l = list then (rest (rest l))

        until (null l)                     ; nothing to split

        collect (first l) into l1          ; first split result

        unless (null (rest l))
        collect (second l) into l2         ; second split result

        finally (return (list l1 l2))))
(defun split-list (lst &aux (lst1 (list 1)) (lst2 (list 2)))
  (labels ((sub (lst p1 p2)
             (if lst 
                 (progn (rplacd p1 (list (car lst))) 
                        (sub (cdr lst) p2 (cdr p1)))
                 (list (cdr lst1) (cdr lst2)))))
    (sub lst lst1 lst2)))
(defun split-list (L)
    (if (endp L)
    '(nil nil)
    (let ((X (split-list (cdr L))))
        (list (cons (car L) (cadr X)) (car X))
)))