我有一张地图矢量,我想以嵌套的方式进行转换。
数据结构如下:
(def data
[{:id 1 :name "a" :parent 0}
{:id 2 :name "b" :parent 0}
{:id 3 :name "c" :parent 0}
{:id 4 :name "a_1" :parent 1}
{:id 5 :name "a_2" :parent 1}
{:id 6 :name "b_1" :parent 2}
{:id 7 :name "a_1_1" :parent 4}])
每个地图都有:id
,其他一些键和值对于此讨论并不重要,而:parent
键表示元素是否属于另一个元素。如果:parent
为0,则它是顶级元素。
我想嵌套这个扁平列表,以便属于父元素的每个元素都存储在父映射中的键:nodes
下,如下所示:
(def nested
[{:id 1 :name "a" :parent 0 :nodes
[{:id 4 :name "a_1" :parent 1 :nodes []}
{:id 5 :name "a_2" :parent 1 :nodes
[{:id 7 :name "a_1_1" :parent 4 :nodes []}]}]}
{:id 2 :name "b" :parent 0 :nodes
[{:id 6 :name "b_1" :parent 2}]}
{:id 3 :name "c" :parent 0 :nodes []}])
总结一下 - 我有一个扁平的树状结构,我想再次变成一棵树。我尝试使用拉链实现这一点,但未能处理嵌套的嵌套级别。
答案 0 :(得分:3)
最简单的方法是通过在每个步骤执行完整扫描来递归地构建它:
(defn tree
([flat-nodes]
(tree flat-nodes 0))
([flat-nodes parent-id]
(for [node flat-nodes
:when (= (:parent node) parent-id)]
(assoc node
:nodes (tree flat-nodes (:id node))))))
然后
=> (tree data)
({:parent 0, :name "a", :nodes
({:parent 1, :name "a_1", :nodes
({:parent 4, :name "a_1_1", :nodes (), :id 7}), :id 4}
{:parent 1, :name "a_2", :nodes (), :id 5}), :id 1}
{:parent 0, :name "b", :nodes
({:parent 2, :name "b_1", :nodes (), :id 6}), :id 2}
{:parent 0, :name "c", :nodes (), :id 3})
(defn tree [flat-nodes]
(let [children (group-by :parent flat-nodes)
nodes (fn nodes [parent-id]
(map #(assoc % :nodes (nodes (:id %)))
(children parent-id)))]
(nodes 0)))
答案 1 :(得分:1)
这样的树必须从下到上构建,所以我们需要一个将seq节点分成叶子和内部节点的函数:
(defn split-leaves
[nodes]
(let [parent-id? (set (map :parent nodes))]
(group-by
(comp #(if % :inner :leaves) parent-id? :id)
nodes)))
下一步是将所有叶子贴在父母身上:
(defn attach-leaves
[inner leaves]
(let [leaves-by-parent (group-by :parent leaves)]
(map
(fn [{:keys [id] :as node}]
(update-in node [:nodes] concat (leaves-by-parent id)))
inner)))
必须重复这两个步骤,直到只留下叶子:
(defn generate
[nodes root-id]
(loop [nodes (conj nodes {:id root-id})]
(let [{:keys [leaves inner]} (split-leaves nodes)]
(if (seq inner)
(recur (attach-leaves inner leaves))
(some #(when (= (:id %) root-id) (:nodes %)) leaves)))))
请注意,我们必须添加和删除虚拟根节点才能使其正常工作,因为您的原始节点集不包含一个节点(这就是函数需要根节点ID的原因)。 / p>
(generate data 0)
;; => ({:parent 0, :name "c", :id 3}
;; {:parent 0, :name "b",
;; :nodes ({:parent 2, :name "b_1", :id 6}),
;; :id 2}
;; {:parent 0, :name "a",
;; :nodes ({:parent 1, :name "a_2", :id 5}
;; {:parent 1, :name "a_1",
;; :nodes ({:parent 4, :name "a_1_1", :id 7}),
;; :id 4}),
;; :id 1})
答案 2 :(得分:0)
另一种选择是将您的子父关系转换为邻接列表,然后遍历非循环有向图。
(defn adjacency-list [coll]
(reduce (fn [r {p :parent c :id}]
(-> r
(update-in [:counts p] #(or % 0))
(update-in [:counts c] #(if % (inc %) 1))
(update-in [:adjacency p] #(if % (conj % c) [c]))))
{}
coll))
(defn get-data [k]
(first (filter #(= (:id %) k) data)) )
(defn traverse [m al roots]
(reduce (fn [r k]
(conj r
(assoc (get-data k)
:nodes (if-let [v (get al k)]
(traverse [] al v)
[]))))
m
roots))
(clojure.pprint/pprint
(let [{:keys [adjacency]} (adjacency-list data)]
(traverse [] adjacency (get adjacency 0))))