使用常见的lisp展平列表

时间:2014-09-16 10:24:11

标签: linked-list lisp common-lisp flatten on-lisp

我正在阅读保罗格雷厄姆的“论Lisp”一书。在第4章“效用函数”中,他给出了在列表上运行的小函数的示例,这在编写较大的程序时很有用。

其中一个是flatten。给定任意级别的嵌套列表作为参数,flatten将删除所有嵌套元素并将它们放在顶层。

以下是我实施flatten的尝试:

(defun flatten (lst)
  (labels ((rflatten (lst1 acc)
                     (dolist (el lst1)
                       (if (listp el)
                         (rflatten el acc)
                         (push el acc)))
                     acc))
    (reverse (rflatten lst nil))))

但上述功能并未正确压缩列表。

; returns (1) instead of (1 2)
(print (flatten '(1 (2))))
(1)

使用(1 (2))调用该函数会返回(1)而不是(1 2)

我无法找到实施flatten的问题。这是我使用的方式 labels?或者它是我使用dolist宏的方式? dolist宏始终返回nil。但这并不重要,因为我使用累加器acc来存储展平列表。

3 个答案:

答案 0 :(得分:3)

push更改范围内的符号绑定。因此递归(rflatten el acc)拥有它自己的acc,这就是那里的结果,但是你不会对返回的结果做任何事情而且它不会改变被调用者{{ 1}}。

也许acc可以解决这个问题:

(setf acc (rflatten el acc))

答案 1 :(得分:3)

你实际上非常接近。正如Sylwester所提到的那样,问题是(push el acc)只会修改 el 的本地绑定(其中每次调用都会有一个新的绑定> rflatten 。正如Rainer所提到的,它并不是传统意义上的累加器,所以我不打算称之为 acc ,但结果。由于您已经定义了本地功能,因此没有理由不在更广泛的范围内定义结果

(defun flatten (lst)
  (let ((result '()))
    (labels ((rflatten (lst1)
               (dolist (el lst1)
                 (if (listp el)
                   (rflatten el)
                   (push el result)))))
      (rflatten lst)
      (nreverse result))))

实际上有几种方法可以清理它。第一个是风格和品味问题,但我使用& aux 变量来绑定结果,所以

(defun flatten (lst &aux (result '()))
  ...)

接下来是dolist可以采用第三个参数,一个表单来评估返回值。这通常用于" push来创建一个列表,然后反转返回值"成语,例如,

(let ((result '()))
  (dolist (x list (nreverse result))
    ...
    (push ... result)))

你不想在每个 dolist 之后反转,但你仍然可以从 dolist <返回 结果 / strong>,因此来自 rflatten 。然后,您只需使用 rflatten 的结果调用 nreverse

(defun flatten (lst &aux (result '()))
  (labels ((rflatten (lst1)
             (dolist (el lst1 result)
               (if (listp el)
                 (rflatten el)
                 (push el result)))))
      (nreverse (rflatten lst))))

答案 2 :(得分:1)

一个非递归代码,它按cons comments建立结果,并从code user:Sylwester开始:

(defun flatten (lst &optional back acc)
  (loop
     (cond 
        ((consp lst) (psetq lst (cdr lst)              ; parallel assignment
                           back (cons (car lst) back)))
        (back
                  (if (consp (car back))  
                    (psetq lst (cdar back)
                          back (cons (caar back) (cdr back)))
                    (psetq acc (if (car back) (cons (car back) acc) acc)
                          back (cdr back))))
        (t     
               (return acc)))))                        ; the result

它不漂亮,但seems to work。并行赋值PSETQ用于模拟尾递归调用帧更新,而不必担心精确排序。

实现与

编码良好的过程相同的过程
(defun flatten2 (l z)
    (cond
        ((endp l) z)
        ((listp (car l)) (flatten2 (car l) (flatten2 (cdr l) z)))
        ((atom (car l)) (cons (car l) (flatten2 (cdr l) z)))))

(defun flatten (l)
   (flatten2 l nil))

将隐式堆栈操作表示为变量之间的列表结构操作。