在Lisp中展平嵌套函数 - 需要帮助理解

时间:2012-05-05 19:08:21

标签: lisp common-lisp

我一直试图找到一种方法将嵌套列表压缩成原始列表中的数字,但是我遇到了一些麻烦。

我一直在看这里给出的展平函数(广泛可用):

(defun flatten (l)
  (cond
    ((null l) nil)
    ((atom l) (list l))
    (t (loop for a in l appending (flatten a)))))

我理解这个例子是递归,但它是如何工作的?它检查元素是否为null或原子,但如果元素属于这些条件,它会怎么做?

3 个答案:

答案 0 :(得分:3)

在我的一天而不是(loop for a in l appending (g a))我们写了(mapcan #'g l)。这相当于(apply #'append (mapcar #'g l)),或多或少:

(defun flatten (l) (if l (if (atom l) (list l) (mapcan #'flatten l))))

那么在这种情况下它意味着什么?想象一下,你打电话给(flatten (list 1 2 3 4 5))。列表中的每个原子都被包含在一个列表中 - 变成单个列表,如(1) (2)等。然后它们全部被附加在一起,给我们回复......原始列表:

( 1 2 3 4 5 )

( (1) (2) (3) (4) (5) )

( 1 2 3 4 5 )

如此扁平化原子列表是id操作(在Common LISP中,即#'identity)。现在想象一下扁平化一个包含原子的列表,以及一个原子列表。同样,列表的每个元素都由flatten转换,然后它们全部附加在一起。正如我们刚刚看到的那样,原子列表保持不变。每个原子都列在一个列表中。因此,附加将返回嵌套列表中两个级别上的所有原子,现在变平:

( 11 12 (1 2 3 4) 13 )

( (11) (12) (1 2 3 4) (13) )

( 11 12 1 2 3 4 13 )

依此类推,以及更多级别的嵌套。

NIL作为列表中的元素会造成问题。 NIL是一个空列表,空列表中不包含任何内容,因此不应该提供任何内容。但NIL也是一个原子。所以我们为它做了一个特例,将它包含在一个单例列表中 - 保持原样,所以当追加时,它就会消失。

答案 1 :(得分:3)

(defun flatten (l)

上面定义了一个函数FLATTEN,其中一个参数名为L

  (cond

如果

    ((null l) nil)

参数L的值是空列表,返回空列表。

    ((atom l) (list l))

或者如果参数L是一个原子(即不是列表),则返回一个以原子作为唯一项目的列表。

    (t 

或者我们有一个非空列表

       (loop for a in l

然后遍历列表中的所有项目,即L

的值
        appending (flatten a)

并附加展平每个列表项的结果。

))))

答案 2 :(得分:2)

  1. 如果您正在查看的元素是nil - 它是列表的末尾,则返回nil。

  2. 如果您正在查看的元素不是列表,则返回包含该元素的列表(我实际上并不确定这是正确的事情,因为给定一个不是列表的原子,它会返回一个包含原子的列表,而不是原子本身。

  3. 否则(如果它是一个列表),循环遍历它的所有元素并附加所有展平的子树(通过调用flatten展平),然后返回它们。

  4. 这很简短,但不是最有效的算法,因为追加需要一直到列表的末尾,以便在另一部分的尾部构成一个部分。下面有点复杂,但看起来更有效的版本:

    (defun flatten (x &optional y)
      (cond
        ((null x)
         (cond
           ((null y) nil)
           ((listp (car y))
            (flatten (car y) (cdr y)))
           (t (cons (car y) (flatten (cdr y))))))
        ((listp (car x))
         (flatten (car x) (if y (list (cdr x) y) (cdr x))))
        (t (cons (car x) (flatten (cdr x) y)))))
    

    此函数背后的算法执行如下操作:

    1. 如果我们既没有第一个元素,也没有其他元素 - 我们做了所有事情,所以只需返回nil(列表的末尾)。

    2. 如果没有第一个元素 - 将第二个元素拆分为第一个元素,其余元素则重复。

    3. 如果有第一个元素,请将其纳入列表,如果有第二个元素 - 保留它,否则,选择下一个元素并重复。

    4. 编辑:我改变了#3,因为解释真的很模糊/可能是错误的。