【问题标题】:Create postorder from tree in LISP在 LISP 中从树创建后序
【发布时间】:2022-01-09 08:13:17
【问题描述】:

我想使用 LISP 以后序遍历树。

如果树是这样表示的:(A(B)(C(D)(E)))

我知道我可以写这个函数

(defun postorder(l)
    (cond
        ((null l) nil)
        (t (APPEND
                (postorder (CADR l))
                (postorder (CADDR l))
                (LIST (CAR l))
            )
        )
    )
)

但是,当树以这种方式表示时 (A 2 B 0 C 2 D 0 E 0) 我被卡住了。 谁能帮我修改我的上述函数以适用于这种树?

【问题讨论】:

  • 你想要的输出是什么?
  • 我承认我不明白这个问题。我不明白格式 (A 2 B 0 C 2 D 0 E 0) 。这最终可能是一件好事。但这也可以解释缺乏答案的原因。
  • 我猜它是一个 content 列表,后跟孩子的数量,然后你必须读入。
  • 一个好的步骤是准确指定该表单的列表如何表示一棵树。如果整数是孩子的个数,你怎么区分(A (B) nil)(A nil (B))

标签: lisp common-lisp


【解决方案1】:

我这样解释这个问题:(A 2 B 0 C 2 D 0 E 0) 应该代表与(A (B) (C (D) (E))) 相同的树。格式是当前节点的内容,然后是孩子数,然后是……其余的。难点在于解析这个,不能只递归到孩子,还需要返回外部解析必须继续的点。

我会通过将该尾部作为第二个值返回来做到这一点。这是一个快速实现:

(defun postorder* (list)                                                  
  (if (endp list)
      nil                                                     
      (destructuring-bind (content n &rest rest) list                     
        (if (zerop n)
            (values (list content) rest)                        
            (loop :repeat n                                               
                  :for (child-po rest)                                    
                    := (multiple-value-list (postorder* (nthcdr 2 list))) 
                      :then (multiple-value-list (postorder* rest))       
                  :append child-po :into po                               
                  :finally (return (values (append po (list content))     
                                           rest)))))))

【讨论】:

    【解决方案2】:

    我们可以将(A 2 B 0 C 2 D 0 E 0) 语法视为令牌流。这很容易通过递归下降来解析。任何语言的递归下降解析器通常从流中读取标记,流是一个有状态的对象,对整个递归都是全局的,在递归函数之间传递,或者可能存储在全局区域中。流解决了将输入位置从一个标记移动到下一个标记的问题。

    在 Lisp 中,列表本身很容易用作流。例如,如果我们有一个变量s 包含(1 2 3),我们可以使用(pop s) 和其他方式从中删除一个项目。在一些与 Lisp 相关的文献中,惰性列表甚至被称为流。

    要在流上编写递归下降解析器,我们可以使递归部分成为局部函数,该函数在范围内具有流变量;然后它可以改变变量以消耗令牌。

    这是一个名为 TXR Lisp 的 Lisp 方言中的函数,它解析 (A 2 B 0 C 2 D 0 E 0) 并将其转换为 (A (B) (C (D) (E)))

    (defun parse (syntax)
      (labels ((rec (parsym n)
                 (cons parsym
                       (collect-each ((i 0..n))
                         (match-case syntax
                           ((@sym 0 . @rest)
                            (set syntax rest)
                            (list sym))
                           ((@sym @(integerp @m) . @rest)
                            (set syntax rest)
                            (rec sym m))
                           (() (error "abrupt end in syntax"))
                           (@else (error "unhandled syntax: ~s" syntax)))))))
        (cadr (rec :root 1))))
    
    
    1> (parse '(A 0))
    (A)
    2> (parse '(A 1))
    ** abrupt end in syntax
    ** during evaluation of form (error "abrupt end in syntax")
    ** ... an expansion of (progn (error "abrupt end in syntax"))
    ** which is located at parse.tl:5
    3> (parse '(A 1 B 0))
    (A (B))
    4> (parse '(A 2 B 0 C 2 D 0 E 0))
    (A (B) (C (D) (E)))
    

    递归解析由一个名为rec 的函数完成。 rec 通过赋值来改变 syntax 变量。 (set syntax rest) 这两种形式使用匹配的语法前缀,将列表指针移动到语法的未解析剩余部分。

    该函数有两个参数:父符号和整数值:该符号有多少子。

    这里有一个技巧来启动解析器:我们对rec 的顶级调用是(rec :root 1)。我们假设我们正在解析的整个语法,例如 (A 2 B 0 ...) 已经嵌入到更大的语法 (:root 1 A 2 B 0) 中,并且我们刚刚解析了 :root 1 部分。因此,我们告诉rec 函数:请解析出1 个参数节点,然后使用该参数创建一个:root 节点。然后从rec 函数(给定示例语法)中出现的实际上是(:root (A (B) (C (D) (E)))),因此我们将cadr 应用于它以删除包装以仅获得(A (B) ...)

    函数的工作原理很简单:它使用模式匹配识别两个可能的前缀:

    • 一个项目,后跟0,以及其余的语法;否则

    • 一个项目,后跟一个非零的整数,然后是其余的。

    所有其他情况都是语法错误。我们不检查该项目是否是符号,所以它不一定是,或者整数是正数。

    该函数在循环中执行此操作:它迭代其参数所指示的次数,以提取节点语法的许多实例。提取的内容被收集到一个列表中,父符号用cons 附加到前面以产生返回值。

    在第一种情况下,当我们有一个后跟零的项目时,没有要为该节点提取的参数,因此没有递归调用。循环的该迭代产生包装在列表中的匹配符号/项目。例如,如果rec 被称为(rec 'Y 1),并且syntax 包含(X 0 whatever ...),则循环将识别X 0 的情况,并收集值(X),将syntax 设置为余数@ 987654351@。因为1,循环只会迭代一次,所以收集的列表是((X))。传入符号 Y 被附加到此以创建 (Y (X))

    在非零情况下,我们有递归。当看到像(Z 2 whatever ...) 这样的语法时,我们只需调用rec,并将提取的符号和数字作为参数:(rec 'Z 2)。这将执行它自己的循环,该循环提取以符号Z 为首的两个参数节点,将该节点返回给我们,并让syntax 变量指向剩余的语法。

    【讨论】:

      猜你喜欢
      • 2015-05-06
      • 1970-01-01
      • 2013-01-24
      • 2016-03-14
      • 1970-01-01
      • 2011-08-15
      • 2016-03-14
      • 2011-07-15
      • 1970-01-01
      相关资源
      最近更新 更多