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

10

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

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

(find-nested m :f)

=> "f"
4个回答

17

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
这是一个好问题。你会如何描述 tree-seq 的一般用途?我感觉听到很多人说不要使用它。它是否仅用于将树转换为序列,如名称所示?还有更好的解释吗? - user3594595
1
我认为我从未在生产环境中使用过它,但我真的看不出有人会反对它,因为该函数相对简单且非常擅长自己的工作 - 其他答案中大多数解决方案实际上都是重新实现 tree-seq 提供的逻辑。 关于摘要:是的,tree-seq 通过按深度优先搜索访问节点的顺序列出节点,将树转换为(惰性的!)序列。 - xsc
我更新了答案,并添加了一些更多的例子,包括查找字符串键。 - xsc
1
我认为当你不知道地图的结构并需要搜索它以找到特定的键时,这是一个很好的解决方案。如果你知道地图的结构,即如果存在节点,则应该使用get-in而不是执行树遍历。这可能是人们说不要使用tree-seq时的意思。然而,当你不知道键可能在地图中的位置时,需要某种形式的遍历和搜索,这时你可以使用tree-seq而不是重新发明轮子。 - Tim X
在Cljs中,“some”无法处理字符串键。例如:(some "k" {"k" 1}) => cljs中的pred.call不是函数 - Petrus Theron
@pate 这已经在之前的评论中提到过了 - 我随后添加了一些示例到答案中,演示了其他查找函数的用法,特别是:(find-nested m #(get % "k")) - xsc

10

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

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

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

如果您不知道嵌套结构中的路径,可以编写一个函数来递归遍历嵌套的映射,查找特定的关键字,并在找到第一个关键字时返回其值,或者在序列中返回所有 :f 的值。


3
如果您知道“路径”,可以考虑使用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"}

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

我不知道路径。我会查看您提供的函数。 - user3594595

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接