【问题标题】:Flatten Nests Function in Lisp - need help understandingLisp中的Flatten Nests Function - 需要帮助理解
【发布时间】:2012-05-14 22:59:57
【问题描述】:

我一直在尝试找到一种方法将嵌套列表压缩为可以返回原始列表的数字,但我遇到了一些麻烦。

我一直在查看此处给出的 flatten 功能(它是如此广泛可用):

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

我知道这个例子是递归的,但它是如何工作的?它检查元素是null还是原子,但是如果元素落入这些条件它会做什么?

【问题讨论】:

    标签: lisp common-lisp


    【解决方案1】:

    在我的日子里,我们写了(mapcan #'g l),而不是(loop for a in l appending (g a))。相当于(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)),即参数列表中只有 atoms。该列表中的每个原子都包含在一个列表中——成为一个单例列表,如(1) (2)等。然后它们都被附加在一起,还给我们……原来的名单:

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

    因此,展平原子列表是一种恒等运算(在 Common LISP 中,即#'identity)。现在想象展平一个列表,其中包含一些 atoms 以及 atomslist。同样,列表的每个元素都被flatten 转换,然后它们都被附加在一起。正如我们刚刚看到的那样,原子列表保持不变。 原子被包含在一个列表中。因此,追加将返回嵌套列表中 两个 级别上的所有原子,现在已展平:

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

    以此类推,还有更多级别的嵌套。

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

    【讨论】:

    • APPLY 不是一个好主意,因为它不适用于任意长的列表。
    • @RainerJoswig 只是用它来说明。
    • 有了代码如何执行的其他答案以及 Will Ness 对该程序背后逻辑的解释(否则我不会得到),我现在理解了这个概念!
    • @OpenLearner:CALL-ARGUMENTS-LIMIT 可以低至 50。使用REDUCE 或其他...
    • @RainerJoswig 所以如果我看到这个:CL-USER> CALL-ARGUMENTS-LIMIT 536870911 我想apply 很好,reduce 没必要?
    【解决方案2】:
    1. 如果您正在查看的元素是 nil - 它是列表的末尾,则返回 nil。

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

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

    这很短,但不是最有效的算法,因为 append 需要一直到列表的末尾才能将一个部分放在另一部分的尾部。下面稍微复杂一些,但看起来更高效:

    (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. 如果有第一个元素,将其加入列表,如果有第二个元素 - 保留它,否则,选择下一个元素并重复。

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

    【讨论】:

    • 即使盯着你高效的扁平化功能看了很长时间......我还是不明白。我是 lisp 的新手,但我改天再来看这段代码并理解你的概念。谢谢!
    • 您的代码是线性递归的,而不是树递归的;但是在没有TCO % cons 的实现上(有吗?...)它仍然是一个成熟的递归。另外,它也消耗了很多,重新创建了它的输入结构。可以一步解决这两个问题。 Here's one example 怎么样。 :)
    • 我认为mapcan 不会执行任何额外的遍历,我希望(loop for a in l nconcing (g a)) 也不会执行任何操作。两者的最大递归深度是嵌套深度,但是对于您的版本,它将循环替换为递归,它将是展平列表的长度。即使不重用旧结构(它有它的位置,只是应该清楚地标有例如 nflatten 名称),将 TCO%cons 代码(例如您的代码)重写为 loop 也有好处递归,例如使用do 构造,以自上而下的方式构建结果列表(以避免reverse)。
    • 您现在答案中的代码是尾递归模缺点。它可以通过应用标准技术转换为循环,以自上而下的方式构造结果列表,同时保持其结束指针。 loopnconcing 可以做同样的事情,所以它只会重新遍历对 flatten 的最新递归调用的结果(部分解决方案)。要获得完整的解决方案,您的代码可以转换为循环,或者可以重写 flatten 以返回 last 单元格。
    • pastebin.com/smPKTSQN 我使用 CLISP 2.38 进行了测试。 mapcan 是最快的,您的代码(“线性记录”)在 1.3 倍时排名第二,在 1.4 倍时自上而下循环,然后在 1.6 倍时 nconcing,最后附加,慢 2.5 倍。 nconcing 显然做得更好,运行速度比附加快 1.5 倍。很有趣,看看你的结果会怎样。你在测试什么?请按原样测试此代码,以便我们进行比较。顺便说一句,随着数据大小的增加,“线性记录”和“附加”的 cmpxties 比其他三个更差。
    【解决方案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)
    

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

    ))))
    

    【讨论】:

    猜你喜欢
    • 2021-01-13
    • 2013-04-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-18
    • 2021-06-22
    • 1970-01-01
    相关资源
    最近更新 更多