Search code examples
clojurezipper

How to move zipper to left/right node in clojure?


I'm writing a tree ( business process decision tree ) in clojure data structure .

(require clojure.zip :as z)

(z/vector-zip 
    [ :billed?  
        [:yes  
            [:check-bank-account] 
            [:check-cash] ] 
        [:send-out-email] ])

when code walk on the first node, it will read the keyword and perform certain actions, the result will be True or False, then I would like it walk into left ( True) or right (False) node .

When my code starts with root node, and call some function associated with :billed? it return a True, how could clojure walk into :yes node, or :send-out-email node ? I thought there is only z/down while, left or rightis only for siblings not for directions of children.

Thank you very much for your time and appreciate any thoughts


Solution

  • Zippers walk through a data structure while keeping track of the current position. To reach different nodes, you have to apply a sequence of moves to the same zipper. When the zipper is created your position is directly above the tree:

    (z/node tree)
    => [:billed? [:yes [:check-bank-account] [:check-cash]] [:send-out-email]]
    

    So you can descend into the tree with z/down, and use z/node to get the current node from the zipper's location:

    (-> tree
        z/down
        z/node)
    => :billed?
    

    If you're walking the tree from the top towards some node, you probably only need z/down and z/right, since descending into a child vector will place you at the leftmost child. It's easier to imagine this if you lay out the vector in a flat line and imagine z/right simply moving a cursor to the next element, and z/down moving the cursor to inside a vector.

    (-> tree
        z/down
        z/right
        z/node)
    => [:yes [:check-bank-account] [:check-cash]]
    
    (-> tree
        z/down
        z/right
        z/right
        z/node)
    => [:send-out-email]
    

    Here's an example of you could walk this tree by evaluating the keys against a map of facts:

    (def tree
      (z/vector-zip
        [:billed?
         [:wire-funds?
          [:check-bank-account]
          [:check-cash]]
         [:send-out-email]]))
    
    (defn facts->action [facts]
      (loop [curr (z/down tree)]
        (let [node (z/node curr)]
          (if-let [fact (find facts node)]
            (if (val fact)
              (recur (-> curr z/right z/down)) ;; descend "left"
              (recur (-> curr z/right z/right z/down))) ;; descend "right"
            node))))
    
    (facts->action {:billed? false})
    => :send-out-email
    (facts->action {:billed? true :wire-funds? true})
    => :check-bank-account
    (facts->action {:billed? true :wire-funds? false})
    => :check-cash