2014-12-07 47 views
0

我有下面的樹:如何最好地更新這棵樹?

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

我想通過添加:name鍵值對更新peopleprojects子樹。 假設我有這些地圖進行查找:

(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調用多種組合,但一直沒能想出解決辦法。

回答

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 
+0

謝謝。除了解決我的問題,這帶來了'letfn'我的注意:) – 2014-12-07 23:03:45

+0

我不明白什麼傳遞'(項目(:ID%))'作爲'assoc' lambda的值。 「項目」不是一張地圖嗎?在這種情況下它如何被用作函數? – 2014-12-07 23:17:21

+0

哦,我剛剛意識到'(projects(:id%))'是'(get projects(:id%))'的簡寫。 – 2014-12-08 00:06:33

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. 對於每個人來說,減少過度的項目更新對應的名稱

的noisesmith解決方案看起來更好,因爲並不需要找到每個步驟人物索引或項目索引。

當然你試圖assoc-inupdate-in但問題就出在你的樹結構中,由於關鍵路徑更新約翰的名字是[:data :people 1 :name],所以你assoc-in代碼看起來像:

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

但你需要在更新它之前,先在人員向量中找到John的索引,同樣的事情發生在內部的項目中。

+0

謝謝。我最終使用了noisesmith的解決方案,但是您對索引問題的解釋很有幫助。 – 2014-12-07 22:59:11