Come accennato, l'unico modo per implementare questo utilizzando la ricorsione in coda è passare all'utilizzo di uno stack esplicito. Un possibile approccio consiste nel convertire la struttura ad albero in una struttura di stack che è essenzialmente una rappresentazione di notazione polacca inversa dell'albero (utilizzando un ciclo e una pila intermedia per ottenere ciò). Dovresti quindi usare un altro ciclo per attraversare lo stack e creare il tuo risultato.
Ecco un programma di esempio che ho scritto per realizzare questo, utilizzando il codice Java a postorder using tail recursion come ispirazione.
(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))
Si può chiamare come tale:
user> (def t '(* (+ 1 2) (- 4 1 2) (/ 6 3)))
#'user/t
user> (eval-tree t)
6
lascio come esercizio al lettore per convertire questo lavoro con una struttura Antlr AST;)
Anche se mi piacerebbe poter accettare sia questa risposta sia la risposta sotto che utilizza uno stack, questa è davvero la soluzione migliore anche se non è esattamente quello che avevo in mente. Grazie per avermi rivolto a un modo migliore di pensare a questo problema! –
FWIW, tree-seq fornisce un attraversamento pre-ordine, non un post-ordine. Onestamente, penso che la soluzione più semplice qui sia 'clojure.walk/postwalk' e la userei se possibile. Se c'è davvero il pericolo di far esplodere lo stack, le cerniere potrebbero funzionare, anche se può essere complicato fare un vero traversamento post-ordine con loro. Indipendentemente dalla loro idoneità per questo particolare problema, le chiusure lampo sono uno strumento meraviglioso da avere nel tuo arsenale :) – Alex