Update a tree

Hi,
I need to update a tree of unknow breadth and depth.
As I learn best through examples, do you have examples, links, docs, … on how to ‘update’ complex data structures in clojure? Is Specter the way to go?

Say we have the following nested structure (breadth and depth not known in advance), containing a relative file path (:path , in this example) in respect with the parent. If that relative path is empty or missing, then the full path is the same as the parent’s full path

[{:level 0, :properties {:path "~/"}}
   [{:level 1, :properties {}}
    [{:level 2, :properties {:path "1100"}}]
    [{:level 2, :properties {:path "1200"}}
     [{:level 3}
      [{:level 4, :properties {}}]
      [{:level 4, :properties {:path "1201"}}]]]
    [{:level 2, :properties {:path "1300"}}
     [{:level 3, :properties {:path "1310"}}]
     [{:level 3, :properties {:path "1320"}}]]]
   [{:level 1, :properties {:path "999"}}]]

I’d like to return the same data, but with a new property :full that is a concatenation of the relative path with all the paths of all the parents up to the root. (And if the relative path of the parent is empty, then use the full path of the parent’s parent)

[{:level 0, :properties {:path "~/" :full "~/"}}
   [{:level 1, :properties {:full "~/"}}
    [{:level 2, :properties {:path "1100" :full "~/1100/"}}]
    [{:level 2, :properties {:path "1200" :full "~/1200/"}}
     [{:level 3, :properties {:full "~/1200/"}}
      [{:level 4, :properties {:full "~/1200/"}}]
      [{:level 4, :properties {:path "1201" :full "~/1200/1201/"}}]]]
    [{:level 2, :properties {:path "1300" :full "~/1300/"}}
     [{:level 3, :properties {:path "1310" :full "~/1300/1310/"}}]
     [{:level 3, :properties {:path "1320" :full "~/1300/1320/"}}]]]
   [{:level 1, :properties {:path "999" :full "~/999/"}}]]

Thanks in advance for any response that could help me!

I tried to solve this without any tree library- but I had to change :path of level 0 to "~":

(defn change [[root & args] & {:keys [path] :or {path ""}}]
  (let [root-path (get-in root [:properties :path])
        new-path (if root-path (str path root-path "/") path)]
    (apply vector
           (assoc-in root [:properties :full] new-path)
           (when (seq args)
             (mapv #(change % :path new-path) args)))))

Example:

(change [{:level 0, :properties {:path "~"}}
         [{:level 1, :properties {}}
          [{:level 2, :properties {:path "1100"}}]
          [{:level 2, :properties {:path "1200"}}
           [{:level 3}
            [{:level 4, :properties {}}]
            [{:level 4, :properties {:path "1201"}}]]]
          [{:level 2, :properties {:path "1300"}}
           [{:level 3, :properties {:path "1310"}}]
           [{:level 3, :properties {:path "1320"}}]]]
         [{:level 1, :properties {:path "999"}}]])
1 Like

Looks good to me! I find it quite natural that no path segment has a path separator in it (I would maybe represent paths as vectors of strings anyway).

All I have is some minor stylistic fine tuning.

I would maybe choose different names: node and children instead of root and args, because that’s what it is at all levels, and the function fill-in-full-paths because it is quite specific (you could call it change if you could generalize it to arbitrary changes maybe).

Instead of the apply - vector - when - seq - mapv thing you can use into with map as a transducer.

(defn fill-in-full-paths [[node & children] & {:keys [path] :or {path ""}}]
  (let [path-segment (-> node :properties :path)
        full-path (cond-> path
                    path-segment (str "/" path-segment))]
    (into [(assoc-in node [:properties :full] full-path)]
          (map #(fill-in-full-paths % :path full-path)
          children)))
2 Likes

Thanks for your time and the solution !

Thanks Harleqin !