views:

243

answers:

2

I'm trying to figure out corecursion in Clojure with nontrivial (i.e. not Fibonacci), but manageable, examples. Apparently it is possible to implement binary tree traversal with corecursion. Wikipedia has an example in Python which I am unable to understand.

How can I implement it in Clojure? Let's say I'm looking for BFS, but it could be any order.

Here's what I have so far:

(defstruct tree :val :left :right)

(def my-tree (struct tree 1 (struct tree 2) (struct tree 3 4 5)))

(def bfs (lazy-cat [my-tree] (map #(:left %) bfs) (map #(:right %) bfs) ))

(println (take 4 bfs))

Unfortunately it seems to be going all the way to the left, ignoring the right branch.

+1  A: 

Here is a direct translation of the bftrav Haskell function from the Wikipedia article. Note that it uses a letrec macro I've just written -- see this Gist for the latest version.

I've changed your definition of my-tree to read thus:

(def my-tree (struct tree 1 (struct tree 2) (struct tree 3 (struct tree 4) (struct tree 5))))

Also, my leaf? function assumes that we're only dealing with proper two-way branches and leaf nodes (so a nil on the :left branch implies a nil on the :right branch); it shouldn't be two difficult to change this to handle single-child "branches":

(defn leaf? [t] (nil? (:left t)))

The code for bftrav is as follows:

(defn bftrav [t]
  (letrec [queue (lazy-seq (cons t (trav 1 queue)))
           trav (fn [l q]
                  (lazy-seq
                    (cond (zero? l) nil
                          (leaf? (first q)) (trav (dec l) (rest q))
                          :else (let [{:keys [left right]} (first q)]
                                  (cons left (cons right (trav (inc l) (rest q))))))))]
    queue))

An example from the REPL:

user> (bftrav my-tree)
({:val 1, :left {:val 2, :left nil, :right nil}, :right {:val 3, :left {:val 4, :left nil, :right nil}, :right {:val 5, :left nil, :right nil}}} {:val 2, :left nil, :right nil} {:val 3, :left {:val 4, :left nil, :right nil}, :right {:val 5, :left nil, :right nil}} {:val 4, :left nil, :right nil} {:val 5, :left nil, :right nil})
user> (count (bftrav my-tree))
5
Michał Marczyk
+5  A: 

Assuming Michal's code does what you want, this also works:

(defn bftrav [& trees]
  (when trees
    (concat trees 
      (->> trees
        (mapcat #(vector (:left %) (:right%)))
        (filter identity)
        (apply bftrav)))))
Alex Taggart
This is so nice... +1. You could replace `#(vector ...)` with `(juxt :left :right)` to make it even prettier. :-)
Michał Marczyk
And I was just saying to myself the other day, "what's the point of juxt?" Excellent!
Alex Taggart
Doesn't it run bftrav over and over for the same nodes in each iteration?
Konrad Garus
Or: What purpose does (filter identity) serve?
Konrad Garus
Nope. It returns a lazy sequence of a tree's sub-trees by recursing on the set of sub-trees in each tier, which is how the breadth-first behavior is achieved. For a balanced tree of seven nodes, bftrav would be called 3 times, one for each level.Now, whether that's what you wanted is a whole separate matter.
Alex Taggart
(filter identity) just filters out the nil values that would be in the sequence when a node didn't have a :left or :right child; I prefer it to (filter #(not (nil? %))) or (filter (complement nil?)). An alternative would be make the function used by mapcat smarter, but I was going for simplicity.
Alex Taggart
It occurs to me that the above should either replace `concat` with `lazy-cat` or wrap the body with `lazy-seq`; otherwise `when` causes the "rest" part of the generated sequence to be forced at each step and the generation is not lazy. With the change, the code remains beautiful *and* becomes fully lazy.
Michał Marczyk
Quite right, though the extra laziness is undone by the call to apply, which will force traversal of the seq returned from each recursion. This is also the problem with the mapcat implementation. See http://stackoverflow.com/questions/3348719#3357201
Alex Taggart
Disregard my comment about apply, I was confused.
Alex Taggart