Steven Mercatante
Steven Mercatante

Reputation: 25295

How best to update this tree?

I've got the following tree:

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

I want to update the people and projects subtrees by adding a :name key-value pair. Assuming I have these maps to perform the lookup:

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

How could I update the original tree so that I end up with the following?

{: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"}]}
    ]
  }
}

I've tried multiple combinations of assoc-in, update-in, get-in and map calls, but haven't been able to figure this out.

Upvotes: 0

Views: 69

Answers (2)

guilespi
guilespi

Reputation: 4702

Not sure if entirely the best approach:

(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))

Just call it with your parameters:

(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}]}}

With nested reduces

  1. Reduce over the people to update corresponding names
  2. For each people, reduce over projects to update corresponding names

The noisesmith solution looks better since doesn't need to find person index or project index for each step.

Naturally you tried to assoc-in or update-in but the problem lies in your tree structure, since the key path to update John name is [:data :people 1 :name], so your assoc-in code would look like:

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

But you need to find John's index in the people vector before you can update it, same things happens with projects inside.

Upvotes: 1

noisesmith
noisesmith

Reputation: 20194

I have used letfn to break down the update into easier to understand units.

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

Upvotes: 1

Related Questions