code-reviews

2024-06-18T12:33:30.127369Z

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")

Thomas Moerman 2024-06-18T13:12:57.036029Z

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"}

πŸ™ 1
2024-06-18T19:31:05.963629Z

Uh thanks a lot, somehow I missed your response, will take a close look tomorrow!

onionpancakes 2024-06-20T08:03:19.103389Z

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))

πŸ‘ 1
πŸ†’ 3