在树结构中,如何将键/值与每个分支合并,其中值是分支值和所有父分支值的总和?
例如,从跟随树结构开始:
[{ :v 1
:_ [{ :v 2 }
{ :v 3
:_ [{ :v 5 }]}
{ :v 4 }]}]
如何将其重新创建为:
[{ :v 1
:sum 1
:_ [{ :v 2
:sum 3 }
{ :v 3
:sum 4
:_ [{ :v 5
:sum 9 }]}
{ :v 4
:sum 5 }]}]
我一直在尝试使用walk
。我认为这可能是正确的做法。但是,到目前为止,我还没有管理它。
答案 0 :(得分:4)
我认为这种递归函数可以解决问题。
(defn sums [{v :v children :_} sum]
{:v v
:sum (+ sum v)
:_ (mapv #(sums % (+ sum v)) children)})
按以下方式评估时:
(def root
[{:v 1
:_ [{:v 2}
{:v 3
:_ [{:v 5}]}
{:v 4}]}])
[(sums (first root) 0)]
结果:
[{:v 1,
:sum 1,
:_ [{:v 2,
:sum 3,
:_ []}
{:v 3,
:sum 4,
:_ [{:v 5,
:sum 9,
:_ []}]}
{:v 4,
:sum 5,
:_ []}]}]
或者这是您树格式的相同sums
函数的更友好版本。
(defn sums [root]
(letfn [(f [{v :v children :_} sum]
{:v v
:sum (+ sum v)
:_ (mapv #(f % (+ sum v)) children)})]
[(f (first root) 0)]))
(sums root)
;= same result as before
答案 1 :(得分:2)
为了便于比较,这是一个使用clojure.walk
的版本。我认为这是一种定制的递归函数比使用walk更干净的情况。自定义函数允许您将中间结果(以父项的总和的形式)从父对象传递给子作为函数参数,而传递给walk的函数除了被遍历的表单之外没有额外的参数,所以在遍历树时,您必须在数据本身中记录中间结果。
(require '[clojure.walk :as walk])
(defn sums
[x]
(walk/prewalk (fn [m]
(if (map? m)
(let [v (or (:v m) 0)
s (+ v (or (:sum m) 0))
m (assoc m :sum s)]
(if (seq (:_ m))
(update-in m [:_]
(partial map (fn [c] (assoc c :sum s))))
m))
m))
x))
答案 2 :(得分:1)
可以编写拉链解决方案
(require '[clojure.zip :as z])
(def root
[{:v 1
:_ [{:v 2}
{:v 3
:_ [{:v 5}]}
{:v 4}]}])
(def zipper (partial z/zipper :_ :_ (fn [n ch] (assoc n :_ (vec ch)))))
(loop [node (zipper (first root))]
(if (z/end? node)
(z/root node)
(recur
(z/next
(z/edit node
#(assoc % :sum ((fnil + 0) %2 (:v %)))
(some-> node z/up z/node :sum))))))
;=> {:v 1,
:_ [{:v 2, :sum 3}
{:v 3,
:_ [{:v 5, :sum 9}],
:sum 4}
{:v 4, :sum 5}],
:sum 1}
如果需要,可以将其回收到矢量中。注意," root"当包含在向量中时,定义的树结构的其余部分不匹配,这就是为什么我们有一个"(第一个根)"。