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 clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
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 clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to