至于你提到,要实现这个用尾递归的唯一方法是切换到使用一个明确的堆栈。一种可能的方法是将树结构转换为基本上是树的逆波兰表示法的堆栈结构(使用循环和中间堆栈来完成此操作)。然后,您将使用另一个循环来遍历堆栈并建立结果。
下面是我写的做到这一点,使用Java代码在postorder using tail recursion作为灵感的示例程序。
(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结构工作;)
尽管我希望我能接受这个答案和下面使用堆栈的答案,但这确实是更好的解决方案,即使它不是我所想的。感谢您让我更好地思考这个问题! –
FWIW,tree-seq给出了预购遍历,而不是后序。老实说,我认为这里最简单的解决方案是'clojure.walk/postwalk',如果可能的话我会使用它。如果真的存在堆栈的危险,那么拉链就可以工作,尽管对它们进行真正的后序遍历可能会非常棘手。无论他们是否适合这个特殊问题,拉链都是你的军火库中的绝佳工具:) – Alex