This is probably easier if you do it in two passes: one to assemble the
get-in path down to a value, and another to stringify that path.
(def input {:name {:first "Rich" :last "Hickey"} :number [1 415 123 4567]})
;=> #'user/input
(def expected {"$.number[0]" 1, "$.number[1]" 415, "$.number[2]" 123,
"$.number[3]" 4567, "$.name.first" "Rich", "$.name.last" "Hickey"})
;=> #'user/expected
(defn keypaths
([xs] (keypaths [] xs))
([path-prefix xs]
(reduce-kv
(fn [kp+v k v]
(let [path (conj path-prefix k)]
(if (associative? v) ;; You may need to change this.
(into kp+v (keypaths path v))
(conj kp+v [path v]))))
[]
xs)))
;=> #'user/keypaths
(defn keypath->str [path]
(let [str-part
(fn [x] (cond
(or (string? x) (char? x)) (str \. x)
(instance? clojure.lang.Named x) (str \. (name x))
:else (str \[ x \])))
str-path ^String (apply str (map str-part path))]
(if (.startsWith str-path ".")
(subs str-path 1)
str-path)))
;=> #'user/keypath->str
(keypaths input)
;=> [[[:number 0] 1] [[:number 1] 415] [[:number 2] 123] [[:number 3] 4567]
[[:name :first] "Rich"] [[:name :last] "Hickey"]]
(keypaths ["$"] input)
;=> [[["$" :number 0] 1] [["$" :number 1] 415] [["$" :number 2] 123] [["$" :
number 3] 4567] [["$" :name :first] "Rich"] [["$" :name :last] "Hickey"]]
(->> (keypaths ["$"] input)
(map (fn [[path v]] [(keypath->str path) v]))
(into {}))
;=> {"$.number[0]" 1, "$.number[1]" 415, "$.number[2]" 123, "$.number[3]"
4567, "$.name.first" "Rich", "$.name.last" "Hickey"}
(= *1 expected)
;=> true
So that increases readability. What about performance?
I'm not entirely sure what you are trying to optimize. You mention a
whitelist of keys. Is there something related to that you want to optimize?
Maybe you don't need to stringify keys at all? Perhaps you need to
represent your whitelist as a set of vectors (each of which is a keypath),
then filter the un-stringified keypaths from maps by that whitelist:
(def whitelist #{[:number 0] [:number 1] [:number 2] [:name :first] [:name
:last]})
;=> #'user/whitelist
(->> (keypaths input)
(filter (comp whitelist first)))
;=> ([[:number 0] 1] [[:number 1] 415] [[:number 2] 123] [[:name :first]
"Rich"] [[:name :last] "Hickey"])
If the problem is that you have extremely large maps to flatten, you can
try folding reducers. There are two opportunities for parallelism:
constructing the paths, and stringifying the paths. Stringifying the paths
is more obvious:
(require '[clojure.core.reducers :as r])
;=> nil
(defn r-flatten-assoc [m]
(->> (keypaths [\$] m)
(r/map (fn [[path v]] [(keypath->str path) v]))))
;=> #'breeze.mast.repl/r-flatten-assoc
(->> (r-flatten-assoc input) (r/foldcat) (into {}) (= expected)
;=> true
But paralleling construction of paths is a bit harder. The following
example only folds over maps; to fold over the vectors with index without
lazy seqs or realizing maps requires a custom reducer. (I also use
transients for good measure.)
(defn r-keypaths
([xs] (r-keypaths [] xs))
([path-prefix xs]
(if (map? xs)
(->> xs
(r/mapcat (fn [k v]
(let [path (conj path-prefix k)]
(if (associative? v)
(r-keypaths path v)
[[path v]]))))
(r/foldcat))
(->> (reduce-kv
(fn [kp+v k v]
(let [path (conj path-prefix k)]
(if (associative? v)
(reduce conj! kp+v (r-keypaths path v))
(conj! kp+v [path v]))))
(transient [])
xs)
(persistent!)))))
On Tuesday, February 17, 2015 at 10:18:58 AM UTC-6, Mark Watson wrote:
>
> A slightly cleaner version:
>
> (defn- flatten-keys* [a ks m]
>
> (cond
>
> ;; Is a map?
>
> (map? m) (reduce into
>
> (map (fn [[k v]]
>
> (flatten-keys* a (str ks "." (name k)) v))
>
> (seq m)))
>
> ;; Is an arr/vec/seq?
>
> (and (sequential? m)
>
> (not (instance? clojure.lang.MapEntry m))) (reduce into
>
> (map-indexed (fn
> [idx itm]
>
>
> (flatten-keys* a
>
>
> (str ks "[" idx "]")
>
>
> itm))
>
> (seq
> m)))
>
> ;; Is not a collection
>
> :else (assoc a ks m)))
>
> (defn flatten-keys [m] (flatten-keys* {} "$" m))
>
>
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to [email protected]
Note that posts from new members are moderated - please be patient with your
first post.
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
---
You received this message because you are subscribed to the Google Groups
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/d/optout.