eav db
eav db

Reputation: 595

Clojure pre/post-walk with path from root

What I know

I'm familiar with clojure.walk/prewalk and clojure.walk/postwalk

What I want

I want something like clojure.walk/prewalk and clojure.walk/postwalk -- but I also want the path required to get to the node -- whereas prewalk/postwalk only gives me the nodes, w/o the actual path.

Example

So if we had a structure

[ {:cat "Garfield", :dog "DogeCoin"} [:a :b {:site "so"}]]

then I want my function called with args:

[] [ {:cat "Garfield", :dog "DogeCoin"} [:a :b {:site "so"}]]

[0] {:cat "Garfield", :dog "DogeCoin"}

[1] [:a :b {:site "so"}]

[0 :cat] "Garfield"

...

Question:

Is there a builtin for the above? where the processing function receives both the node and the path (from the root node) to the node)?

Thanks!

Possible Solution

(based on what fl00r suggested)

(defn pathwalk [f cur-path node]
  (let [f1 #(pathwalk f (conj cur-path %1) %2)]
    (f cur-path node)
    (cond
      (map? node) (map #(apply f1 %) node)
      (or (vector? node) (list? node)) (keep-indexed f1 node))))

Upvotes: 5

Views: 590

Answers (1)

rmcv
rmcv

Reputation: 1976

I suppose you also want 'pathwalk' to return something from function f similar to clojure.walk/prewalk and not rely on side-effect? E.g.

(prewalk #(if (= :a %) :c %) [:a :b])
=>
[:c :b]

If yes, then you can do this:

(defn pathwalk [f path e]
  (let [e' (f path e)]
    (cond
      (map? e')  (->> e'
                      (map (fn [[k x]] [k (pathwalk f (conj path k) x)]))
                      (into (empty e')))
      (coll? e') (->> e'
                      (map-indexed (fn [i x] (pathwalk f (conj path i) x)))
                      (into (empty e')))
      :else      e')))

Here is a test run:

(pathwalk #(do
             (println %1 %2)
             (if (= :a %2)
               :c
               %2))
          []
          [ {:cat "Garfield", :dog "DogeCoin"} [:a :b {:site "so"}]])

it will print:

[] [{:cat Garfield, :dog DogeCoin} [:a :b {:site so}]]
[0] {:cat Garfield, :dog DogeCoin}
[0 :cat] Garfield
[0 :dog] DogeCoin
[1] [:a :b {:site so}]
[1 0] :a
[1 1] :b
[1 2] {:site so}
[1 2 :site] so

and below data will be returned from the function:

[{:cat "Garfield", :dog "DogeCoin"} [:c :b {:site "so"}]]

Upvotes: 2

Related Questions