简体   繁体   English

如何在Clojure中使用尾递归来运行AST

[英]How to walk an AST with tail recursion in Clojure

I have an ANTLR3 AST which I need to traverse using a post-order, depth-first traversal which I have implemented as roughly the following Clojure: 我有一个ANTLR3 AST我需要遍历一个后序,深度优先遍历,我已经实现了大致如下的Clojure:

(defn walk-tree [^CommonTree node]
  (if (zero? (.getChildCount node))
    (read-string (.getText node))
    (execute-node
      (map-action node)
      (map walk-tree (.getChildren node)))))))

I would like to convert this to tail recursion using loop...recur, but I haven't been able to figure out how to effectively use an explicit stack to do this since I need a post-order traversal. 我想使用循环... recur将其转换为尾递归,但我无法弄清楚如何有效地使用显式堆栈来执行此操作,因为我需要进行后序遍历。

Instead of producing a tail recursive solution which traverses the tree and visits each node, you could produce a lazy sequence of the depth first traversal using the tree-seq function and then get the text out of each object in the traversal. 您可以使用tree-seq函数生成深度优先遍历延迟序列,然后在遍历中从每个对象中获取文本,而不是生成遍历树并访问每个节点的尾递归解决方案。 Lazy sequences never blow the stack because they store all the state required to produce the next item in the sequence in the heap. 延迟序列永远不会打击堆栈,因为它们存储了生成堆中序列中下一个项所需的所有状态。 They are very often used instead of recursive solutions like this where loop and recur are more diffacult. 它们经常被用来代替这样的递归解决方案,其中looprecur更加难以实现。

I don't know what your tree looks like though a typical answer would look something like this. 虽然典型的答案看起来像这样,但我不知道你的树是什么样的。 You would need to play with the "Has Children" "list of children" functions 你需要玩“有孩子”的“儿童名单”功能

(map #(.getText %) ;; Has Children?      List of Children    Input Tree
     (tree-seq #(> (.getChildCount #) 0) #(.getChildren %) my-antlr-ast))

If tree-seq does not suit your needs there are other ways to produce a lazy sequence from a tree. 如果tree-seq不适合您的需求,还有其他方法可以从树中生成延迟序列。 Look at the zipper library next. 接下来看看拉链库。

As you mention, the only way to implement this using tail recursion is to switch to using an explicit stack. 如您所述,使用尾递归实现此操作的唯一方法是切换到使用显式堆栈。 One possible approach is to convert the tree structure into a stack structure that is essentially a Reverse Polish notation representation of the tree (using a loop and an intermediate stack to accomplish this). 一种可能的方法是将树结构转换为堆栈结构,该结构本质上是树的反向波兰表示法表示(使用循环和中间堆栈来实现此目的)。 You would then use another loop to traverse the stack and build up your result. 然后,您将使用另一个循环遍历堆栈并构建结果。

Here's a sample program that I wrote to accomplish this, using the Java code at postorder using tail recursion as an inspiration. 这是我为实现这一目的而编写的示例程序, 使用尾部递归作为灵感,使用postorder的Java代码。

(def op-map {'+ +, '- -, '* *, '/ /})

;; Convert the tree to a linear, postfix notation stack
(defn build-traversal [tree]
  (loop [stack [tree] traversal []]
    (if (empty? stack)
      traversal
      (let [e (peek stack)
            s (pop stack)]
        (if (seq? e)
          (recur (into s (rest e)) 
                 (conj traversal {:op (first e) :count (count (rest e))}))
          (recur s (conj traversal {:arg e})))))))

;; Pop the last n items off the stack, returning a vector with the remaining
;; stack and a list of the last n items in the order they were added to
;; the stack
(defn pop-n [stack n]
  (loop [i n s stack t '()]
    (if (= i 0)
      [s t]
      (recur (dec i) (pop s) (conj t (peek s))))))

;; Evaluate the operations in a depth-first manner, using a temporary stack
;; to hold intermediate results.
(defn eval-traversal [traversal]
  (loop [op-stack traversal arg-stack []]
    (if (empty? op-stack)
      (peek arg-stack)
      (let [o (peek op-stack)
            s (pop op-stack)]
        (if-let [a (:arg o)]
          (recur s (conj arg-stack a))
          (let [[args op-args] (pop-n arg-stack (:count o))]
            (recur s (conj args (apply (op-map (:op o)) op-args)))))))))

(defn eval-tree [tree] (-> tree build-traversal eval-traversal))

You can call it as such: 您可以这样称呼它:

user> (def t '(* (+ 1 2) (- 4 1 2) (/ 6 3)))
#'user/t
user> (eval-tree t)
6

I leave it as an exercise to the reader to convert this to work with a Antlr AST structure ;) 我将它作为练习留给读者将其转换为使用Antlr AST结构;)

I'm not skilled up on clojure, but I think I understand what you're looking for. 我不熟悉clojure,但我想我明白你在寻找什么。

Here's some pseudocode. 这是一些伪代码。 The stack here in my pseudocode looks like a stateful object, but it's quite feasible to use an immutable one instead. 我的伪代码中的堆栈看起来像一个有状态对象,但是使用不可变对象是非常可行的。 It uses something like O(depth of tree * max children per node) heap. 它使用类似O(树的深度*每个节点的最大子节点)堆。

walk_tree(TreeNode node) {
    stack = new Stack<Pair<TreeNode, Boolean>>();
    push(Pair(node, True), stack)
    walk_tree_aux(stack);
}
walk_tree_aux(Stack<Pair<TreeNode, Boolean>> stack) { -- this should be tail-recursive
    if stack is empty, return;
    let (topnode, topflag) = pop(stack);
    if (topflag is true) {
        push Pair(topnode, False) onto stack);
        for each child of topnode, in reverse order:
            push(Pair(child, True)) onto stack
        walk_tree_aux(stack);
    } else { -- topflag is false
        process(topnode)
        walk_tree_aux(stack);
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM