重新创建一棵扁平的树

时间:2015-01-27 11:23:32

标签: recursion clojure tree nested zipper

我有一张地图矢量,我想以嵌套的方式进行转换。

数据结构如下:

(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 []}])

总结一下 - 我有一个扁平的树状结构,我想再次变成一棵树。我尝试使用拉链实现这一点,但未能处理嵌套的嵌套级别。

3 个答案:

答案 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))))