Find Value of Specific Key in Nested Map

后端 未结 4 695
一向
一向 2021-02-08 09:40

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 {         


        
相关标签:
4条回答
  • 2021-02-08 10:11

    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.

    0 讨论(0)
  • 2021-02-08 10:21

    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 []])))))
    
    0 讨论(0)
  • 2021-02-08 10:29

    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.

    0 讨论(0)
  • 2021-02-08 10:31

    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 %)))
      
    0 讨论(0)
提交回复
热议问题