Question

This is really a followup to question Update hierarchical / tree structure in Clojure

I need to be able to change an Atom containing a map with lists of submaps. I would like to use fx assoc-in to do this, but i'm not sure how best to optain the path to the element i wich to change

My data structure:

(def x (atom {:name "A" 
          :id 1 
          :children [{:name "B" 
                      :id 2 
                      :children []} 
                     {:name "C" 
                      :id 3 
                      :children [{:name "D" 
                                  :id 4 
                                  :children []}]}]}))

How do i make a function to find the path go a given id, fx give me path to map which contain #(= (:id %) 3):

(find-path 3 @x) ; => [0 :children 1]

So i can do this to get a new map:

(assoc-in @x [(conj (find-path...) :name)] "Jim")

Or update the Atom like this:

(swap! x assoc-in [(conj (find-path...) :name)] "Bob")
Was it helpful?

Solution 2

There's a fundamental issue here is that your update process is now a two steps process: find-path and update-in. However you are working with atoms so the path returned by find-path may be incorrect by the time update-in gets to see the value of the atom.

So you should combine them in a single fn so as to not have one deref and one swap!.

(defn update-by-id [x id f & args]
  (apply update-in x (find-path x id) f args))

Now you can use a single swap!:

(swap! x update-by-id 3 assoc :name "Bob")

However if you constantly update and access through ids you should also evaluate switching to another (flatter) representation for this piece of data.

OTHER TIPS

Create a zipper with zipper, then iterate through each location with next and once you find the location that holds the node with the id you are looking for, start moving up building the path along the way.

The following is a function I've used to do the last step, which is move up to the root and build the path along the way.

(defn path-from-root
  [loc]
  (loop [path []
         loc loc]
    (if-let [parent (zip/up loc)]
      (recur (into [:children (-> loc zip/lefts count)] path)
             parent)
      path)))
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top