How to get all values ​​for a given key in a nested structure in clojure

(def threads {:values [{:_id "t1" :u {:uid 1} :members {:values [{:uid 1} {:uid 2}]} :messages {:values [{:_id "m1" :u {:uid 1}} {:_id "m2" :u {:uid 2}}]}} {:_id "t2" :u {:uid 12} :members {:values [{:uid 11} {:uid 12}]} :messages {:values [{:_id "m3" :u {:uid 13}} {:_id "m4" :u {:uid 12}}]}}]}) 

You need to find out all the values ​​for the key: uid In this case, the response should return [1 2 11 12 13] without using any global bindings. Requires a solution scale for any level of nested structure.

thanks

+7
source share
1 answer

This can be done using tree-seq and a filter, or with subsequent processing. I am very interested in both ratings:

trail tree:

 user> (map :uid (filter #(if (and (map? %) (:uid %)) true false) (tree-seq #(or (map? %) (vector? %)) identity threads))) (1 2 1 1 2 13 12 12 11 12) 

Which looks better when used ->> (and with installation and vec to remove duplicates)

 user> (->> (tree-seq #(or (map? %) (vector? %)) identity threads) (filter #(if (and (map? %) (:uid %)) true false)) (map :uid) set vec) [1 2 11 12 13] 

or using postwalk:

 user> (let [results (atom [])] (clojure.walk/postwalk #(do (if-let [uid (:uid %)] (swap! results conj uid)) %) threads) @results) [1 2 1 1 2 13 12 12 11 12] 

This scans the structure using a function that, if the structure contains a key with the name: uid, adds it to the local atom. Then at the end we return the accumulated contents of the atom. This is slightly different from your example because it accumulates duplicates. If you want to eliminate them effectively, use a vector as a set instead, and then turn it into a vector once at the end (your example has a result in a vector)

 user> (let [results (atom #{})] (clojure.walk/postwalk #(do (if-let [uid (:uid %)] (swap! results conj uid)) %) threads) (vec @results)) [1 2 11 12 13] 
+9
source

All Articles