【问题标题】:How to find the path of keys to a value in a nested array map in Clojure?如何在 Clojure 的嵌套数组映射中找到键的路径?
【发布时间】:2020-04-21 07:26:44
【问题描述】:

假设我有:

(def a-map {:foo "bar" :biz {:baz "qux"}})

我如何找到给定值“qux”的键路径,以便

(get-in a-map <the resulting path>) 

会返回“qux”吗?

换句话说,一个接受 a-map 和 "qux" 并返回 [:biz :baz] 的函数。

然后我就可以像这样使用返回的路径:

 (get-in a-map [:biz :baz])

然后得到“qux”。

我需要的路径将比这个简单的例子更嵌套。

我想在 html 中找到给定值的路径,该值已使用山核桃解析为数组映射。我想这样做,而不必尝试在精神上向下浏览数十个嵌套的键/值。我对其他策略持开放态度。

【问题讨论】:

    标签: clojure clojurescript


    【解决方案1】:

    你可以为此使用zipper:像这样,例如:

    user> (require '[clojure.zip :as z])
    nil
    
    user> 
    (loop [curr (z/zipper coll? seq nil a-map)]
      (cond (z/end? curr) nil
            (-> curr z/node (= "qux")) (->> curr
                                            z/path
                                            (filter map-entry?)
                                            (mapv first))
            :else (recur (z/next curr))))
    ;;=> [:biz :baz]
    

    或相同,但采用更“声明性”的风格:

    (some->> a-map
             (z/zipper coll? seq nil)
             (iterate z/next)
             (take-while (complement z/end?))
             (filter #(= (z/node %) "qux"))
             first
             z/path
             (filter map-entry?)
             (mapv first))
    

    更新

    您也可以使用经典的递归方法:

    (defn get-path [endpoint data]
      (cond (= endpoint data) []
            (map? data) (some (fn [[k v]]
                                (when-let [p (get-path endpoint v)]
                                  (cons k p)))
                              data)))
    
    user> (get-path "qux" a-map)
    ;;=> (:biz :baz)
    

    【讨论】:

    • 一旦找到endpoint,您的经典递归方法将花费二次时间将答案构造为向量。您可以在线性时间内将其构建为列表。可能微不足道。
    • @Thumbnail 确实如此。为了可读性,我有意识地这样做了。 (避免对 list->vector 转换多一层间接性)
    • 我想你可能有。但是没有要求 key 的路径 是一个向量。而相关的标准函数get-inassoc-in 接受任意键序列。我承认这个结构不太可能很深,而且深度肯定不能超过递归的限制。
    • 哇。这些年来,我绝对确定他们只接受向量。那么显然应该是(cons k p)!已更新。
    【解决方案2】:

    使用the Tupelo library 可以通过两种方式解决此问题。第一个使用函数walk-with-parents-readonly。当你找到你想要的节点时,你会保存所有的父节点,这些父节点可以被处理以提供你想要的信息:

    (ns tst.demo.core
      (:use demo.core tupelo.core tupelo.test)
      (:require [tupelo.forest :as tf]))
    
    (dotest
      (let [result (atom nil)
            data   {:foo "bar" :biz {:baz "qux"}}]
        (walk-with-parents-readonly data
          {:enter (fn [parents item]
                    (when (= item "qux")
                      (reset! result parents)))})
        (is= @result
          [{:foo "bar", :biz {:baz "qux"}}
           [:biz {:baz "qux"}]
           {:type :map-val, :value {:baz "qux"}}
           {:baz "qux"}
           [:baz "qux"]
           {:type :map-val, :value "qux"}]))
    

    你也可以使用the tupelo.forest library,它是为处理HTML和其他树状结构而设计的

      (tf/with-forest (tf/new-forest)
        (let [hiccup      [:foo
                           [:bar
                            [:baz "qux"]]]
              root-hid    (tf/add-tree-hiccup hiccup)
              path-raw    (only (tf/find-paths root-hid [:** :baz]))
              path-pretty (tf/format-path path-raw) ]
          (is= path-pretty
            [{:tag :foo}
             [{:tag :bar}
              [{:tag :baz, :value "qux"}]]]) )))
    

    另请参阅extracting a permalink from the XKCD comic webpage 的示例。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-04-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多