在Clojure中,如何找到可能深嵌套在Map结构中的键的值?例如:
(def m {:a {:b "b"
:c "c"
:d {:e "e"
:f "f"}}})
(find-nested m :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 %)))
如果您知道嵌套路径,请使用get-in。
=> (get-in m [:a :d :f])
=> "f"
详见此处:https://clojuredocs.org/clojure.core/get-in
如果您不知道嵌套结构中的路径,可以编写一个函数来递归遍历嵌套的映射,查找特定的关键字,并在找到第一个关键字时返回其值,或者在序列中返回所有 :f 的值。
(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"}
以下是一种能够在不知道路径的情况下查找关键字的版本。如果有多个匹配的关键字,只会返回一个:
(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 []])))))
tree-seq
的一般用途?我感觉听到很多人说不要使用它。它是否仅用于将树转换为序列,如名称所示?还有更好的解释吗? - user3594595tree-seq
提供的逻辑。 关于摘要:是的,tree-seq
通过按深度优先搜索访问节点的顺序列出节点,将树转换为(惰性的!)序列。 - xsc(some "k" {"k" 1})
=>cljs中的pred.call不是函数
。 - Petrus Theron(find-nested m #(get % "k"))
。 - xsc