How to find the path of keys to a value in a nested array map in Clojure?

左心房为你撑大大i 提交于 2020-05-15 05:00:05

问题


Supposing I had:

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

How would I find the path of keys to a given value "qux" such that

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

would return "qux"?

In other words, a function that takes a-map and "qux" and returns [:biz :baz].

I would then be able to use the returned path like this:

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

and get "qux".

The paths I need are going to be far more nested than this simple example.

I am wanting to find the path to a given value in html that has been parsed into an array map using hickory. I want to do this without having to try to mentally navigate down through dozens of nested key/values. I'm open to other strategies.


回答1:


you can employ zipper for that: like this, for example:

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]

or the same, but in a more 'declarative' style:

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

update

you can also use the classic recursive approach:

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



回答2:


There are 2 ways you can solve this with the Tupelo library. The first uses the function walk-with-parents-readonly. When you find the node you want, you save off all of the parent nodes, which could be processed to give the information you want:

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

You can also use the tupelo.forest library, which is designed for processing HTML and other tree-like structures

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

Please see also the example for extracting a permalink from the XKCD comic webpage.



来源:https://stackoverflow.com/questions/61338077/how-to-find-the-path-of-keys-to-a-value-in-a-nested-array-map-in-clojure

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!