Clojure - walk the path

I am looking for a function similar to the clojure.walk function, which has an inner function that takes as an argument:

  • not a key and value, as is the case with the clojure.walk / walk function
  • but the key vector needed to access the value from the top-level data structure.
  • recursively traverses all data

Example:

 ;; not good since it takes `[kv]` as argument instead of `[path v]`, and is not recursive. user=> (clojure.walk/walk (fn [[kv]] [k (* 10 v)]) identity {:a 1 :b {:c 2}}) ;; {:a 10, :c 30, :b 20} ;; it should receive as arguments instead : [[:a] 1] [[:b :c] 2] 

Note:

  • It should also work with arrays using the keys 0, 1, 2 ... (as in get-in ).
  • I really don't need the outer parameter if this allows me to simplify the code.
+5
source share
3 answers

Currently learning clojure, I tried this as an exercise. However, it seemed to me quite difficult to implement it just like a walk through a tree, which applies an internal function as it passes.

To achieve the result you are looking for, I divided the task into 2:

  • First, convert the nested structure to a dictionary with a path key, and the value
  • Then display the internal function or decrease it with the external function.

My implementation:

 ;; Helper function to have vector indexes work like for get-in (defn- to-indexed-seqs [coll] (if (map? coll) coll (map vector (range) coll))) ;; Flattening the tree to a dict of (path, value) pairs that I can map over ;; user> (flatten-path [] {:a {:k1 1 :k2 2} :b [1 2 3]}) ;; {[:a :k1] 1, [:a :k2] 2, [:b 0] 1, [:b 1] 2, [:b 2] 3} (defn- flatten-path [path step] (if (coll? step) (->> step to-indexed-seqs (map (fn [[kv]] (flatten-path (conj path k) v))) (into {})) [path step])) ;; Some final glue (defn path-walk [f coll] (->> coll (flatten-path []) (map #(apply f %)))) ;; user> (println (clojure.string/join "\n" (path-walk #(str %1 " - " %2) {:a {:k1 1 :k2 2} :b [1 2 3]}))) ;; [:a :k1] - 1 ;; [:a :k2] - 2 ;; [:b 0] - 1 ;; [:b 1] - 2 ;; [:b 2] - 3 
+7
source

Turns out Stuart Halloway posted a gist that might be useful (it uses a protocol, which makes it extensible)

 (ns user) (def app "Intenal Helper" (fnil conj [])) (defprotocol PathSeq (path-seq* [form path] "Helper for path-seq")) (extend-protocol PathSeq java.util.List (path-seq* [form path] (->> (map-indexed (fn [idx item] (path-seq* item (app path idx))) form) (mapcat identity))) java.util.Map (path-seq* [form path] (->> (map (fn [[kv]] (path-seq* v (app path k))) form) (mapcat identity))) java.util.Set (path-seq* [form path] (->> (map (fn [v] (path-seq* v (app path v))) form) (mapcat identity))) java.lang.Object (path-seq* [form path] [[form path]]) nil (path-seq* [_ path] [[nil path]])) (defn path-seq "Returns a sequence of paths into a form, and the elements found at those paths. Each item in the sequence is a map with :path and :form keys. Paths are built based on collection type: lists by position, maps by key, and sets by value, eg (path-seq [:a [:b :c] {:d :e} #{:f}]) ({:path [0], :form :a} {:path [1 0], :form :b} {:path [1 1], :form :c} {:path [2 :d], :form :e} {:path [3 :f], :form :f}) " [form] (map #(let [[form path] %] {:path path :form form}) (path-seq* form nil))) (comment (path-seq [:a [:b :c] {:d :e} #{:f}]) ;; finding nils hiding in data structures: (->> (path-seq [:a [:b nil] {:d :e} #{:f}]) (filter (comp nil? :form))) ;; finding a nil hiding in a Datomic transaction (->> (path-seq {:db/id 100 :friends [{:firstName "John"} {:firstName nil}]}) (filter (comp nil? :form))) ) 

Note: in my case, I could also use Specter , so if you are reading this, you can check it out as well.

0
source

There is also https://github.com/levand/contextual/

 (def node (:b (first (root :a)))) (= node {:c 1}) ;; => true (c/context node) ;; => [:a 0 :b] 
0
source

Source: https://habr.com/ru/post/1235470/


All Articles