Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clojure - walk with path

I am looking for a function similar to those in clojure.walk that have an inner function that takes as argument :

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

Example :

;; not good since it takes `[k v]` as argument instead of `[path v]`, and is not recursive.
user=> (clojure.walk/walk (fn [[k v]] [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 work with arrays too, using the keys 0, 1, 2... (just like in get-in).
  • I don't really care about the outer parameter, if that allows to simplify the code.
like image 371
nha Avatar asked Nov 08 '15 13:11

nha


1 Answers

Currently learning clojure, I tried this as an exercise. I however found it quite tricky to implement it directly as a walk down the tree that applies the inner function as it goes.

To achieve the result you are looking for, I split the task in 2:

  • First transform the nested structure into a dictionary with the path as key, and the value,
  • Then map the inner function over, or reduce with the outer function.

My implementation:

;; Helper function to have vector's 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 [[k v]] (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
like image 64
kimsnj Avatar answered Nov 08 '22 16:11

kimsnj