如何最好地更新这棵树?

时间:2014-12-07 20:54:16

标签: clojure

我有以下树:

{:start_date "2014-12-07"
  :data {
    :people [
      {:id 1
       :projects [{:id 1} {:id 2}]}
      {:id 2
       :projects [{:id 1} {:id 3}]}
    ]
  }
}

我想通过添加people键值对来更新projects:name子树。 假设我有这些地图来执行查找:

(def people {1 "Susan" 2 "John")
(def projects {1 "Foo" 2 "Bar" 3 "Qux")

我如何更新原始树以便最终得到以下内容?

{:start_date "2014-12-07"
  :data {
    :people [
      {:id 1
       :name "Susan"
       :projects [{:id 1 :name "Foo"} {:id 2 :name "Bar"}]}
      {:id 2
       :name "John"
       :projects [{:id 1 :name "Foo"} {:id 3 :name "Qux"}]}
    ]
  }
}

我尝试了assoc-inupdate-inget-inmap来电的多种组合,但未能弄清楚这一点。

2 个答案:

答案 0 :(得分:1)

我已经使用letfn将更新分解为更容易理解的单位。

user> (def tree {:start_date "2014-12-07"
                 :data {:people [{:id 1
                                  :projects [{:id 1} {:id 2}]}
                                 {:id 2
                                  :projects [{:id 1} {:id 3}]}]}})
#'user/tree
user> (def people {1 "Susan" 2 "John"})
#'user/people
user> (def projects {1 "Foo" 2 "Bar" 3 "Qux"})
#'user/projects
user> 
(defn integrate-tree
  [tree people projects]
  ;; letfn is like let, but it creates fn, and allows forward references
  (letfn [(update-person [person]
             ;; -> is the "thread first" macro, the result of each expression
             ;; becomes the first arg to the next
             (-> person
                 (assoc :name (people (:id person)))
                 (update-in [:projects] update-projects)))
          (update-projects [all-projects]
            (mapv
             #(assoc % :name (projects (:id %)))
             all-projects))]
    (update-in tree [:data :people] #(mapv update-person %))))
#'user/integrate-tree
user> (pprint (integrate-tree tree people projects))
{:start_date "2014-12-07",
 :data
 {:people
  [{:projects [{:name "Foo", :id 1} {:name "Bar", :id 2}],
    :name "Susan",
    :id 1}
   {:projects [{:name "Foo", :id 1} {:name "Qux", :id 3}],
    :name "John",
    :id 2}]}}
nil

答案 1 :(得分:1)

不确定是否完全是最佳方法:

(defn update-names
  [tree people projects]
  (reduce
   (fn [t [id name]]
     (let [person-idx (ffirst (filter #(= (:id (second %)) id)
                                      (map-indexed vector (:people (:data t)))))
           temp (assoc-in t [:data :people person-idx :name] name)]
       (reduce
        (fn [t [id name]]
          (let [project-idx (ffirst (filter #(= (:id (second %)) id)
                                            (map-indexed vector (get-in t [:data :people person-idx :projects]))))]
            (if project-idx
              (assoc-in t [:data :people person-idx :projects project-idx :name] name)
              t)))
        temp
        projects)))
   tree
   people))

只需使用您的参数调用它:

(clojure.pprint/pprint (update-names tree people projects))
{:start_date "2014-12-07",
 :data
 {:people
  [{:projects [{:name "Foo", :id 1} {:name "Bar", :id 2}],
   :name "Susan",
   :id 1}
   {:projects [{:name "Foo", :id 1} {:name "Qux", :id 3}],
    :name "John",
    :id 2}]}}

使用嵌套缩减

  1. 减少人们更新相应的名称
  2. 对于每个人,减少项目以更新相应的名称
  3. noisesmith解决方案看起来更好,因为不需要为每个步骤找到人员索引或项目索引。

    当然,您尝试assoc-inupdate-in,但问题在于您的树结构,因为更新John名称的关键路径是[:data :people 1 :name],因此您的assoc-in代码看起来像是:

    (assoc-in tree [:data :people 1 :name] "John")
    

    但是你需要在人物矢量中找到John的索引才能更新它,内部项目也会发生相同的事情。