2014-12-07 3 views
0

У меня есть следующее дерево:Как лучше всего обновить это дерево?

{: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-in, update-in, get-in и map звонки, но не был в состоянии понять это.

ответ

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 :) :) –

+0

Я не понимаю, что проходит' (projects (: id%)) 'как значение для lambda' assoc'. Разве «проекты» не являются картой? Как его можно использовать как функцию в этом случае? –

+0

О, я просто понял, что '(projects (: id%))' является сокращением для '(get projects (: id%))'. –

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-in или update-in, но проблема заключается в вашей древовидной структуре, так как ключевой путь для обновления имени Джон [:data :people 1 :name], так что ваш assoc-in код будет выглядеть следующим образом:

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

Но вам нужно найдите индекс Джона в векторе людей, прежде чем вы сможете его обновить, то же самое происходит с проектами внутри.

+0

Спасибо. Я закончил использование решения шумоподавителя, но ваше объяснение проблемы с индексом было полезным. –