Find Value of Specific Key in Nested Map

怎甘沉沦 提交于 2020-12-29 03:50:42

问题


In Clojure, how can I find the value of a key that may be deep in a nested map structure? For example:

(def m {:a {:b "b"
            :c "c"
            :d {:e "e"
                :f "f"}}})

(find-nested m :f)

=> "f"

回答1:


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

(defn find-nested
  [m k]
  (->> (tree-seq map? vals m)
       (filter map?)
       (some k)))

(find-nested {:a {:b {:c 1}, :d 2}} :c)
;; => 1

Also, finding all matches becomes a matter of replacing some with keep:

(defn find-all-nested
  [m k]
  (->> (tree-seq map? vals m)
       (filter map?)
       (keep k)))

(find-all-nested {:a {:b {:c 1}, :c 2}} :c)
;; => [2 1]

Note that maps with nil values might require some special treatment.


Update: If you look at the code above, you can see that k can actually be a function which offers a lot more possibilities:

  • to find a string key:

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

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

    (find-nested m #(when (some-> % :k pos?) (:k %)))
    



回答2:


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

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

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

If you don't know the path in your nested structure you could write a function that recurses through the nested map looking for the particular key in question and either returns its value when it finds the first one or returns all the values for :f in a seq.




回答3:


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 next function:

(defn find-in [m k]
  (if (map? m)
    (let [v (m k)]
      (->> 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: given function will find only the first occurrence.




回答4:


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

(defn find-key [m k]
  (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 require all 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 [m k]
  (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 []])))))


来源:https://stackoverflow.com/questions/28091305/find-value-of-specific-key-in-nested-map

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!