specter

murtaza52 2024-05-31T08:57:11.294199Z

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)

mmer 2024-05-31T09:28:10.185499Z

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.

murtaza52 2024-05-31T09:37:12.449969Z

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

murtaza52 2024-05-31T09:43:14.672249Z

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}

mmer 2024-05-31T11:51:36.833369Z

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.

nathanmarz 2024-05-31T15:38:10.894019Z

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
murtaza52 2024-05-31T19:04:22.514129Z

thanks @nathanmarz, thats neat.

mmer 2024-05-31T19:06:04.247739Z

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

nathanmarz 2024-05-31T19:50:47.565689Z

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

nathanmarz 2024-05-31T19:51:09.501239Z

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

nathanmarz 2024-05-31T19:51:24.035379Z

so that's what NODES encodes

nathanmarz 2024-05-31T19:52:13.429319Z

mentally I literally visualize paths as hopping around a data structure

mmer 2024-05-31T19:52:16.400139Z

Thank you that is really helpful

nathanmarz 2024-05-31T19:52:35.933129Z

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