Find the value of a specific key in a nested map

In Clojure, how can I find the key value, which can be a deeply nested map structure? For example:

(def m {:a {:b "b" :c "c" :d {:e "e" :f "f"}}}) (find-nested m :f) => "f" 
+8
clojure
source share
4 answers

Clojure offers tree-seq to traverse the depth of any value. This will simplify the logic needed to find your nested key:

 (defn find-nested [mk] (->> (tree-seq map? vals m) (filter map?) (some k))) (find-nested {:a {:b {:c 1}, :d 2}} :c) ;; => 1 

In addition, finding all matches becomes a replacement for some with keep :

 (defn find-all-nested [mk] (->> (tree-seq map? vals m) (filter map?) (keep k))) (find-all-nested {:a {:b {:c 1}, :c 2}} :c) ;; => [2 1] 

Please note that cards with nil values ​​may require special treatment.


Update:. If you look at the code above, you can see that k may actually be a function that offers much more features:

  • to find the string key:

     (find-nested m #(get % "k")) 
  • to find multiple keys:

     (find-nested m #(some % [:a :b])) 
  • find only positive values ​​in integer maps:

     (find-nested m #(when (some-> % :k pos?) (:k %))) 
+12
source share

If you know the nested path, use get-in.

 => (get-in m [:a :d :f]) => "f" 

See here for more details: https://clojuredocs.org/clojure.core/get-in

If you do not know the path in your nested structure, you can write a function that recurses through the nested map, looking for the corresponding specific key, and either returns its value when it finds the first, or returns all the values ​​for: f in the sequence.

+7
source share

If you know the "path", consider using get-in :

 (get-in m [:a :d :f]) ; => "f" 

If the "path" is unknown, you can use something like the following function:

 (defn find-in [mk] (if (map? m) (let [v (mk)] (->> m vals (map #(find-in % k)) ; Search in "child" maps (cons v) ; Add result from current level (filter (complement nil?)) first)))) (find-in m :f) ; "f" (find-in m :d) ; {:e "e", :f "f"} 

Note: this function will find only the first occurrence.

+3
source share

Here is the version that will find the key without knowing the path to it. If there are several matching keys, only one is returned:

 (defn find-key [mk] (loop [m' m] (when (seq m') (if-let [v (get m' k)] v (recur (reduce merge (map (fn [[_ v]] (when (map? v) v)) m'))))))) 

If you need all the values, you can use:

 (defn merge-map-vals [m] (reduce (partial merge-with vector) (map (fn [[_ v]] (when (map? v) v)) m))) (defn find-key [mk] (flatten (nfirst (drop-while first (iterate (fn [[m' acc]] (if (seq m') (if-let [v (get m' k)] [(merge-map-vals m') (conj acc v)] [(merge-map-vals m') acc]) [nil acc])) [m []]))))) 
+2
source share

All Articles