在Clojure中,如何找到嵌套地图结构中可能很深的键的值?例如:
(def m {:a {:b "b"
:c "c"
:d {:e "e"
:f "f"}}})
(find-nested m :f)
=> "f"
答案 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 []])))))