关于Clojure:Clojure-递归半平面嵌套地图

Clojure - Recursively Semi-Flatten Nested Map

在Clojure中,如何旋转这样的嵌套地图:

1
2
3
4
5
6
7
8
9
10
(def parent {:id"parent-1"
             :value"Hi dude!"
             :children [{:id"child-11"
                         :value"How is life?"
                         :children [{:id"child-111"
                                     :value"Some value"
                                     :children []}]}
                        {:id"child-12"
                         :value"Does it work?"
                         :children []}]})

此:

1
2
3
4
5
6
[
[{:id"parent-1", :value"Hi dude!"}]
[{:id"parent-1", :value"Hi dude!"} {:id"child-11", :value"How is life?"}]
[{:id"parent-1", :value"Hi dude!"} {:id"child-11", :value"How is life?"} {:id"child-111", :value"Some value"}]
[{:id"parent-1", :value"Hi dude!"} {:id"child-12", :value"Does it work?"}]
]

我正在尝试非常棘手的递归尝试,现在我的大脑已经筋疲力尽了。

到目前为止,我所掌握的是下面的内容。它确实获取了正确的数据,但是却将数据放入了一些多余的嵌套向量中。

该如何解决?
在Clojure中是否有一种很好的惯用方式来做到这一点?

谢谢。

1
2
3
4
5
6
7
(defn do-flatten [node parent-tree]
  (let [node-res (conj parent-tree (dissoc node :children))
        child-res (mapv #(do-flatten % node-res) (:children node))
        end-res (if (empty? child-res) [node-res] [node-res child-res])]
    end-res))

(do-flatten parent [])

哪个生产:

1
2
3
4
5
6
7
8
9
10
11
[
[{:id"parent-1", :value"Hi dude!"}]
[[
[{:id"parent-1", :value"Hi dude!"} {:id"child-11", :value"How is life?"}]
[[
[{:id"parent-1", :value"Hi dude!"} {:id"child-11", :value"How is life?"} {:id"child-111", :value"Some value"}]
]]]
[
[{:id"parent-1", :value"Hi dude!"} {:id"child-12", :value"Does it work?"}]
]]
]


我不知道这是否是惯用的,但似乎可行。

1
2
3
4
5
6
7
(defn do-flatten
  ([node]
   (do-flatten node []))
  ([node parents]
   (let [path (conj parents (dissoc node :children))]
     (vec (concat [path] (mapcat #(do-flatten % path)
                                 (:children node)))))))

您可以在调用[]时忽略它。


我倾向于使用一些局部状态来简化逻辑:

1
2
3
4
5
6
7
8
9
10
(defn do-flatten
  ([node]
   (let [acc (atom [])]
     (do-flatten node [] acc)
     @acc))
  ([node base acc]
   (let [new-base (into base (self node))]
     (swap! acc conj new-base)
     (doall
      (map #(do-flatten % new-base acc) (:children node))))))

也许某些功能纯粹主义者会不喜欢它,当然,您可以用纯粹的功能方式完成整个事情。我的感觉是,这是一个临时的,完全是局部的状态(因此不会引起该州臭名昭著的各种问题),所以如果它可以提高可读性(我认为确实如此),我很高兴使用它。


另一个选择是使用拉链:

1
2
3
4
5
6
7
8
9
10
11
(require '[clojure.zip :as z])

(defn paths [p]
  (loop [curr (z/zipper map? :children nil p)
         res []]
    (cond (z/end? curr) res
          (z/branch? curr) (recur (z/next curr)
                                  (conj res
                                        (mapv #(select-keys % [:id :value])
                                              (conj (z/path curr) (z/node curr)))))
          :else (recur (z/next curr) res))))