I want to collect the values for all keys in a collection of maps. The solution I arrived at works, but would love to have some feedback on it, e.g.:
1. I use concat and I donβt know how safe its usage is in here?
2. How idiomatic is this solution?
3. Would you have preferred a different approach?
(defn collect-values-for [ks m]
(let [collect-values (partial collect-values-for ks)]
(cond
(map? m) (concat (keep (fn [[k v]] (when ((set ks) k) v)) m)
(mapcat collect-values (vals m)))
(coll? m) (mapcat collect-values m)
:else [])))
;; Example usage:
(def data [{:id 1 :nested {:mediaId "abc"}}
{:id 2 :nested {:mediaId "def" :inner {:mediaId "ghi"}}}
{:id 3 :list [{:mediaId "jkl"} {:other {:mediaId "mno" :content {:mediaId "pqr" :altId "stu"}}}]}])
(collect-values-for [:mediaId] data)
;; => ("abc" "def" "ghi" "jkl" "mno" "pqr")
(collect-values-for [:mediaId :altId] data)
;; => ("abc" "def" "ghi" "jkl" "mno" "pqr" "stu")using clojure.walk/postwalk would be easier, it removes the need for writing the recursion yourself.
e.g.
(defn collect-values
[ks data]
(let [ks (set ks)
acc (atom #{})]
(w/postwalk (fn [x]
(when (map-entry? x)
(let [[k v] x]
(when (contains? ks k)
(swap! acc conj v)))) x)
data)
@acc))
(collect-values [:mediaId :altId] data)
=> #{"ghi" "pqr" "stu" "abc" "jkl" "mno" "def"}Uh thanks a lot, somehow I missed your response, will take a close look tomorrow!
The solution essentially turns a tree structure into a seq. I would use tree-seq and then filter the resulting seq.
(->> (tree-seq coll? identity data)
(filter map-entry?)
(filter (comp #{:mediaId :altId} key))
(map val))