Clojure parse nested vectors

307 views Asked by At

I am looking to transform a clojure tree structure into a map with its dependencies

For example, an input like:

[{:value "A"} 
  [{:value "B"} 
    [{:value "C"} {:value "D"}] 
  [{:value "E"} [{:value "F"}]]]]

equivalent to:

:A
  :B
    :C
    :D
  :E
    :F

output:

 {:A [:B :E] :B [:C :D] :C [] :D [] :E [:F] :F}

I have taken a look at tree-seq and zippers but can't figure it out!

2

There are 2 answers

2
Taylor Wood On

Here's a way to build up the desired map while using a zipper to traverse the tree. First let's simplify the input tree to match your output format (maps of :value strings → keywords):

(def tree
  [{:value "A"}
   [{:value "B"} [{:value "C"} {:value "D"}]
    {:value "E"} [{:value "F"}]]])

(def simpler-tree
  (clojure.walk/postwalk
   #(if (map? %) (keyword (:value %)) %)
   tree))
;; [:A [:B [:C :D] :E [:F]]]

Then you can traverse the tree with loop/recur and clojure.zip/next, using two loop bindings: the current position in tree, and the map being built.

(loop [loc (z/vector-zip simpler-tree)
       deps {}]
  (if (z/end? loc)
    deps ;; return map when end is reached
    (recur
     (z/next loc) ;; advance through tree
     (if (z/branch? loc)
       ;; for (non-root) branches, add top-level key with direct descendants
       (if-let [parent (some-> (z/prev loc) z/node)]
         (assoc deps parent (filterv keyword? (z/children loc)))
         deps)
       ;; otherwise add top-level key with no direct descendants
       (assoc deps (z/node loc) [])))))
=> {:A [:B :E], :B [:C :D], :C [], :D [], :E [:F], :F []}
0
Alan Thompson On

This is easy to do using the tupelo.forest library. I reformatted your source data to make it fit into the Hiccup syntax:

(dotest
  (let [relationhip-data-hiccup [:A
                                 [:B
                                  [:C]
                                  [:D]]
                                 [:E
                                  [:F]]]
        expected-result         {:A [:B :E]
                                 :B [:C :D]
                                 :C []
                                 :D []
                                 :E [:F]
                                 :F []} ]
    (with-debug-hid
      (with-forest (new-forest)
        (let [root-hid (tf/add-tree-hiccup relationhip-data-hiccup)
              result   (apply glue (sorted-map)
                         (forv [hid (all-hids)]
                           (let [parent-tag (grab :tag (hid->node hid))
                                 kid-tags   (forv [kid-hid (hid->kids hid)]
                                              (let [kid-tag (grab :tag (hid->node kid-hid))]
                                                kid-tag))]
                             {parent-tag kid-tags})))]
          (is= (format-paths (find-paths root-hid [:A]))
            [[{:tag :A}
              [{:tag :B} [{:tag :C}] [{:tag :D}]]
              [{:tag :E} [{:tag :F}]]]])
          (is= result  expected-result ))))))

API docs are here. The project README (in progress) is here. A video from the 2017 Clojure Conj is here.

You can see the above live code in the project repo.