Lisp中的逆转列表

时间:2015-12-22 19:01:28

标签: lisp common-lisp flags

我正在尝试在Lisp中反转列表,但是我收到错误:“错误:20303FF3处的异常C0000005 [标记0]      {Offset 25 inside#} eax 108 ebx 200925CA ecx 200 edx 2EFDD4D esp 2EFDCC8 ebp 2EFDCE0 esi 628 edi 628“

我的代码如下:

(defun rev (l)
    (cond
        ((null l) '())
        (T (append (rev (cdr l)) (list (car l)))))) 

谁能告诉我我做错了什么?提前谢谢!

4 个答案:

答案 0 :(得分:4)

您编写的代码在逻辑上是正确的,并产生您希望它的结果:

CL-USER> (defun rev (l)
           (cond
             ((null l) '())
             (T (append (rev (cdr l)) (list (car l)))))) 
REV
CL-USER> (rev '(1 2 3 4))
(4 3 2 1)
CL-USER> (rev '())
NIL
CL-USER> (rev '(1 2))
(2 1)

也就是说,在性能方面存在一些问题。 追加函数会生成除最终参数之外的所有参数的副本。例如,当您执行(追加'(1 2)'(ab)'(3 4))时,您将创建一个新的四个缺点单元格,其车辆为1,2,a和湾最后一个的cdr是现有列表(3 4)。那是因为追加的实现是这样的:

(defun append (l1 l2)
  (if (null l1)
      l2
      (cons (first l1)
            (append (rest l1)
                    l2))))

这不完全是Common Lisp的追加,因为Common Lisp的追加可以使用两个以上的参数。它足以证明为什么除了最后一个列表之外的所有列表都被复制了。现在看看在实施 rev 方面意味着什么:

(defun rev (l)
  (cond
    ((null l) '())
    (T (append (rev (cdr l)) (list (car l)))))) 

这意味着当您撤消(1 2 3 4)等列表时,就像您一样:

(append '(4 3 2) '(1))              ; as a result of    (1)
(append (append '(4 3) '(2)) '(1))  ; and so on...      (2)

现在,在第(2)行中,您正在复制列表(4 3)。在第一行中,您正在复制列表(4 3 2),其中包含(4 3)的副本。也就是说,你是复制副本。这对记忆的使用非常浪费。

更常见的方法是使用累加器变量和辅助函数。 (请注意,我使用 endp 休息第一个列表* 而不是 null cdr car 缺点,因为它更清楚我们正在处理列表,而不是任意的cons-tree。它们几乎相同(但存在一些差异)。)

(defun rev-helper (list reversed)
  "A helper function for reversing a list.  Returns a new list
containing the elements of LIST in reverse order, followed by the
elements in REVERSED.  (So, when REVERSED is the empty list, returns
exactly a reversed copy of LIST.)"
  (if (endp list)
      reversed
      (rev-helper (rest list)
                  (list* (first list)
                         reversed))))
CL-USER> (rev-helper '(1 2 3) '(4 5))
(3 2 1 4 5)
CL-USER> (rev-helper '(1 2 3) '())
(3 2 1)

使用此辅助函数,可以轻松定义 rev

(defun rev (list)
  "Returns a new list containing the elements of LIST in reverse
order."
  (rev-helper list '()))
CL-USER> (rev '(1 2 3))
(3 2 1)

那就是说,使用标签来定义本地帮助函数可能更常见,而不是使用外部辅助函数:

(defun rev (list)
  (labels ((rev-helper (list reversed)
             #| ... |#))
    (rev-helper list '())))

或者,由于Common Lisp无法保证优化尾部调用,因此执行循环也很好而且干净:

(defun rev (list)
  (do ((list list (rest list))
       (reversed '() (list* (first list) reversed)))
      ((endp list) reversed)))

答案 1 :(得分:2)

在ANSI Common Lisp中,您可以使用reverse函数反转列表(非破坏性:分配新列表)或nreverse(重新排列现有列表的构建块或数据以生成逆转了一个)。

> (reverse '(1 2 3))
(3 2 1)

不要在引用列表文字上使用nreverse;它是未定义的行为,并且可能以令人惊讶的方式运行,因为它是事实上的自修改代码。

答案 2 :(得分:0)

如果您可以使用append等标准CL库函数,则应使用reverse(作为Kaz suggested)。

否则,如果这是一个练习(h / w与否),你可以试试这个:

(defun rev (l)
  (labels ((r (todo)
             (if todo
                 (multiple-value-bind (res-head res-tail) (r (cdr todo))
                   (if res-head
                       (setf (cdr res-tail) (list (car todo))
                             res-tail (cdr res-tail))
                       (setq res-head (list (car todo))
                             res-tail res-head))
                   (values res-head res-tail))
                 (values nil nil))))
    (values (r l))))

PS。您的具体错误是不可理解的,请与您的供应商联系。

答案 3 :(得分:0)

你可能已经耗尽了堆栈空间;这是在尾部位置之外调用递归函数rev的结果。转换为尾递归函数的方法包括引入一个累加器,变量result如下:

(defun reving (list result)
  (cond ((consp list) (reving (cdr list) (cons (car list) result)))
        ((null list) result)
        (t (cons list result))))

rev函数变为:

(define rev (list) (reving list '()))

示例:

* (reving '(1 2 3) '())
(3 2 1)
* (reving '(1 2 . 3) '())
(3 2 1)

* (reving '1 '())
(1)