Search code examples
clojurespecter

Select transformed submaps of nested map using specter


Is there a straight-forward way to select a submap containing a transformed submap of each map-value in a nested map using specter?

An example: cr is a nested map,

{:marks {:these :values :are :omitted}, 
 :scores {:to :simplify :the :example}, 
 :results {1 {:total 8, :sums {:p1 5, :p2a 3}, :check true}, 
           2 {:total 8, :sums {:p1 9, :p2b -1}, :check false}}}

and from this I want to extract a map (int -> boolean) containing for each key in :results, the value associated with the :check key,

in this case {1 true 2 false}

I can do it in two steps as

 (:results (spc/transform [:results spc/MAP-VALS] :check cr))

or, in the general case where the desired submap is not at the top level

(spc/select-one [...... :results] (spc/transform .... cr))

Without specter, the transformation can be expressed quite similarly as (and arguably no less clearly) as

(mapmap #(:check %2) (:results cr))

where mapmap is

(defn mapmap
"creates a map, by mapping f over the values in m
f is a function with two arguments, and is passed
the key and the value "
[f m]
    (reduce-kv #(assoc %1 %2 (f %2 %3) ) {} m)
)

I feel like I am is missing something, as I cannot express it in specter as a single navigation. Can this query be expressed in a single select or transform using specter?

Recursive map query using specter seems related, but I don't quite get how recursive paths work or how to use them for the transform.


Solution

  • The missing piece was the transformed navigator:

    (transformed path update-fn)
    
    Navigates to a view of the current value by transforming it with the 
    specified path and update-fn.
    

    Which can be used in the desired one-liner

    (spc/select-one [:results (spc/transformed [spc/MAP-VALS] :check)] cr)
    ==> {1 true, 2 false}