在嵌套映射中查找特定键的值

时间:2015-01-22 14:35:11

标签: clojure

在Clojure中,如何找到嵌套地图结构中可能很深的键的值?例如:

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

(find-nested m :f)

=> "f"

4 个答案:

答案 0 :(得分:16)

Clojure提供tree-seq来对任何值进行深度优先遍历。这将简化查找嵌套密钥所需的逻辑:

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

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

此外,找到所有匹配项就可以将some替换为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]

请注意,nil值的地图可能需要一些特殊处理。


更新:如果您查看上面的代码,您会发现k实际上可以提供更多可能性的功能:

  • 查找字符串键:

    (find-nested m #(get % "k"))
    
  • 找到多个键:

    (find-nested m #(some % [:a :b]))
    
  • 在整数映射中只找到正值:

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

答案 1 :(得分:8)

如果您知道嵌套路径,请使用get-in。

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

详情请见此处:https://clojuredocs.org/clojure.core/get-in

如果您不知道嵌套结构中的路径,您可以编写一个函数,通过嵌套映射来查找有问题的特定键,并在找到第一个键时返回其值或返回所有键。值为:seq。中的f

答案 2 :(得分:3)

如果您知道“路径”,请考虑使用get-in

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

如果“路径”未知,您可以使用下一个功能:

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

注意:给定的函数只会找到第一次出现。

答案 3 :(得分:2)

这是一个在不知道路径的情况下找到密钥的版本。如果有多个匹配的键,则只返回一个:

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

如果您需要所有可以使用的值:

(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 []])))))