specter 2024-05-31

hi, I have just discovered specter and am a newbie, requesting some help - I have data which can have arbitrary depth and branches -

{:role0 {:clients {:a 1
                   :b 2}
         :children {:role1 {:clients {:c 3}
                            :children {:role3 {:clients {:d 4}}}}
                    :role2 {:clients {:e 5
                                      :f 6}}}}}
Each level can have :clients and :children keys. I want to select all the client values such that the above returns
[{:a 1} {:b 2} {:c 3} {:d 4} {:e 5} {:f 6}]
I have written the below snippet using specter but it returns the whole data ad a vector -
(def data {:role0 {:clients {:a 1
                             :b 2}
                   :children {:role1 {:clients {:c 3}
                                      :children {:role3
                                                 {:clients {:d 4}}}}
                              :role2 {:clients {:e 5
                                                :f 6}}}}})

(def TREE-VALUES
  (sp/recursive-path [] p
                     (sp/if-path map?
                                 [sp/ALL p]
                                 sp/STAY)))

(sp/select TREE-VALUES data)

I wonder if you should see this as a two step process - one to get the maps second to split them into single vale maps.

true splitting could be done in second step, I am having trouble in extracting the values in the first place

I found this in the specter docs -

(def map-key-walker
  (sp/recursive-path [akey]
                     p
                     [sp/ALL (sp/if-path [sp/FIRST #(= % akey)]
                                         sp/LAST
                                         [sp/LAST p])]))
Based on the example its suppose to return all instances of a key, however for my data it only returned {:a 1 :b 2}

I have tried several options and like you am stuck. Hopefully @nathanmarz will be able to quickly give a solution. I do find quite a bit of my time writing Clojure is simply spent trying to work out things like this.

you can do it like this:

(def NODES
  (recursive-path [] p
    [MAP-VALS
     (stay-then-continue
       :children p)]))
(select [NODES :clients ALL (view (fn [[k v]] {k v}))] data)
;; => [{:a 1} {:b 2} {:c 3} {:d 4} {:e 5} {:f 6}]

👏 1

thanks @nathanmarz, thats neat.

I would love to know how you think through these problems to come up with such a neat solution.

for this one, I can see that as long as I can navigate to the root of every map containing :children and :clients, it's trivial to get to the desired data

and that from those locations I can navigate to all children maps recursively

so that's what NODES encodes

mentally I literally visualize paths as hopping around a data structure

Thank you that is really helpful

it's completely intuitive for me at this point, I think it gets easier with practice