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

Find Value of Specific Key in Nested Map

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

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

(find-nested m :f)

=> "f"

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

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

详情请看这里:https://clojuredocs.org/clojure.core/get-in

如果您不知道嵌套结构中的路径,您可以编写一个函数,在嵌套映射中递归查找有问题的特定键,并在找到第一个键时 returns 它的值或 returns 序列中 :f 的所有值。

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

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

如果您知道 "path",请考虑使用 get-in:

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

如果 "path" 未知,您可以使用类似下一个函数的功能:

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

注意:给定的函数只会找到第一个匹配项。

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 %)))