如何在Clojure中使用尾部递归遍历AST

如何在Clojure中使用尾部递归遍历AST,clojure,functional-programming,antlr,antlr3,tail-recursion,Clojure,Functional Programming,Antlr,Antlr3,Tail Recursion,我有一个ANTLR3 AST,我需要使用post顺序、深度优先遍历来遍历它,我大致实现了以下Clojure: (defn walk-tree [^CommonTree node] (if (zero? (.getChildCount node)) (read-string (.getText node)) (execute-node (map-action node) (map walk-tree (.getChildren node)))))))

我有一个ANTLR3 AST,我需要使用post顺序、深度优先遍历来遍历它,我大致实现了以下Clojure:

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

我想使用loop…recur将其转换为尾部递归,但我还没有弄清楚如何有效地使用显式堆栈来实现这一点,因为我需要一个后序遍历。

正如您所提到的,使用尾部递归实现这一点的唯一方法是切换到使用显式堆栈。一种可能的方法是将树结构转换为堆栈结构,该结构本质上是树的反向波兰表示法(使用循环和中间堆栈来实现)。然后使用另一个循环遍历堆栈并建立结果

下面是我编写的一个示例程序,以实现这一点,它的灵感来自于

(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))
您可以这样称呼它:

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

我将其作为练习留给读者,让他们将其转换为使用Antlr AST结构;)

您可以使用
tree seq
函数生成深度优先遍历的延迟序列,然后从遍历中的每个对象中获取文本,而不是生成遍历树并访问每个节点的尾部递归解决方案。惰性序列从不破坏堆栈,因为它们存储了在堆中生成序列中的下一项所需的所有状态。它们经常被用来代替像这样的递归解决方案,
loop
recur
更为困难

我不知道你的树是什么样子,虽然一个典型的答案是这样的。您需要使用“Has Children”“list of Children”函数

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

如果tree seq不适合您的需要,那么还有其他方法可以从树生成惰性序列。看看下面的拉链库

我对clojure不在行,但我想我知道你在找什么

这里有一些伪代码。在我的伪代码中,这里的堆栈看起来像一个有状态对象,但使用不可变对象是完全可行的。它使用类似于O(树的深度*每个节点的最大子节点数)的堆

walk_树(树节点){
堆栈=新堆栈();
推送(成对(节点,真),堆栈)
步行树辅助(堆栈);
}
walk_tree_aux(Stack Stack){--这应该是尾部递归的
如果堆栈为空,则返回;
let(topnode,topflag)=pop(堆栈);
如果(topflag为真){
将对(topnode,False)推到堆栈上;
对于topnode的每个子级,按相反顺序:
将(对(子,真))推到堆栈上
步行树辅助(堆栈);
}否则{--topflag为false
进程(topnode)
步行树辅助(堆栈);
}
}

虽然我希望我能同时接受这个答案和下面使用堆栈的答案,但这确实是一个更好的解决方案,即使这不是我想要的。谢谢你让我用更好的方式来思考这个问题!FWIW,tree-seq提供了一个前序遍历,而不是后序遍历。老实说,我认为这里最简单的解决方案是
clojure.walk/postwark
,如果可能的话,我会使用它。如果确实存在炸毁堆栈的危险,那么拉链可以工作,尽管使用它们进行真正的后序遍历可能会很棘手。不管拉链是否适合解决这个特殊问题,它都是您的武器库中的绝佳工具:)
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);
    }
}