I've got a tree like so:
[:root
[:a [:b [:c [:g]]]]
[:d [:e [:f [:g]]]]]
How can I get the edges, ie:
[[:root :a] [:root :d] [:a :b] [:b :c] [:c :g] [:d :e] [:e :f] [:f :g]]
I've got a tree like so:
[:root
[:a [:b [:c [:g]]]]
[:d [:e [:f [:g]]]]]
How can I get the edges, ie:
[[:root :a] [:root :d] [:a :b] [:b :c] [:c :g] [:d :e] [:e :f] [:f :g]]
This is what I've come up with before I checked your answer. Seems a bit more idiomatic unless I'm missing something.
(defn vec->edges [v-tree]
(->> v-tree
(tree-seq vector? next)
(mapcat (fn [[a & children]]
(map (fn [[b]] [a b]) children)))))
This approach uses a basic loop (no need for extra libraries or recursion):
(defn get-edges [tree]
(loop [subtrees [tree]
output []]
(if (empty? subtrees)
output
(let [[[root & first-subtrees] & subtrees] subtrees]
(recur (into subtrees first-subtrees)
(into output (map #(-> [root (first %)])) first-subtrees))))))
Testing it on the example data:
(get-edges [:root
[:a [:b [:c [:g]]]]
[:d [:e [:f [:g]]]]])
;; => [[:root :a] [:root :d] [:d :e] [:e :f] [:f :g] [:a :b] [:b :c] [:c :g]]
Here is another approach based on lazy sequences:
(defn get-edges2 [tree]
(->> [tree]
(iterate #(into (rest %) (rest (first %))))
(take-while seq)
(mapcat (fn [subtrees]
(let [[[root & sub] & _] subtrees]
(map #(-> [root (first %)]) sub))))))
I really like the way you post your question Scott Klarenbach, it's really synthetic.
I proposed a solution in raw clojure. The tricky part was the location of the recursive call and how to handle the results of these recursive calls.
(def data
[:root
[:a [:b [:c [:g]]]]
[:d [:e [:f [:g]]]]])
(defn get-edges [collection]
(let [root (first collection)
branches (rest collection)]
(if (empty? branches)
[]
(let [edges
(mapv (fn [branch] [root (first branch)]) branches)
sub-edges
(->> branches
(mapcat (fn [branch] (get-edges branch)))
vec)]
(if (empty? sub-edges)
edges
(vec (concat edges sub-edges)))))))
(get-edges data)
;; => [[:root :a] [:root :d] [:a :b] [:b :c] [:c :g] [:d :e] [:e :f] [:f :g]]
There is no simple built-in way to do this. The easiest way is to roll your own recursion.
Here is a solution with unique hiccup tags (no duplicates):
(ns tst.demo.core
(:use tupelo.core tupelo.test)
(:require
[schema.core :as s]))
(def result (atom []))
(s/defn walk-with-path-impl
[path :- [s/Any] ; parent path is a vector
user-fn
data :- [s/Any]] ; each data item is a hiccup vector
(let [tag (xfirst data)
path-next (append path tag)]
(user-fn path data)
(doseq [item (xrest data)]
(walk-with-path-impl path-next user-fn item))))
(defn walk-with-path!
[user-fn data]
(walk-with-path-impl [] user-fn data))
(s/defn print-edge
[path :- [s/Any] ; parent path is a vector
data :- [s/Any]] ; each data item is a hiccup vector
(when (not-empty? path)
(let [parent-node (xlast path)
tag (xfirst data)
edge [parent-node tag]]
(swap! result append edge))))
The unit test shows the input data & result
(dotest
(let [tree [:root
[:a
[:b
[:c
[:z]]]]
[:d
[:e
[:f
[:g]]]]]]
(reset! result [])
(walk-with-path! print-edge tree)
(is= @result
[[:root :a] [:a :b] [:b :c] [:c :z]
[:root :d] [:d :e] [:e :f] [:f :g]])))
Here are the docs for convenience functions like xfirst, append, etc