3
votes

How can you apply a function to leaf nodes of a (nested) map? For example, let's have this map:

{:a 0
 :b {:c 1}
 :d [{:e 2} {:f 3}]}

Let's say we want to increment all leaf nodes in this map and produce the following result:

{:a 1
 :b {:c 2}
 :d [{:e 3} {:f 4}]}

Currently, I'm thinking of using the map-zipper from this answer and editing the map via the clojure.zip functions. However, I'm not sure how to iterate through the zipper and how to identify leaf nodes. What functions should I look at? Is there a simpler solution without zippers?

Could something like the following work (provided there is a leaf-node? predicate testing if a location in zipper is a leaf node)?

(require '[clojure.zip :as zip])

(defn inc-leaf-nodes
  [loc]
  (if (zip/end? loc)
    (zip/node loc)
    (recur (zip/next (if (leaf-node? loc)
                       (zip/edit loc inc)
                       loc)))))
1

1 Answers

4
votes

A leaf-node? function could be implemented given an appropriate definition of a leaf, but there are at least two issues with the proposed inc-leaf-nodes:

  • the zipper implementation you linked to uses map entries as nodes, so you cannot simply pass inc to zip/edit – you would have to wrap it in a helper function that would apply it in the value position;

  • that zipper also doesn't treat non-map values as branches, so it would not descend into the vector [{:e 3} {:f 4}] – you would need a different zipper to overcome this problem.

Assuming that a leaf is something that is not a map, set or a sequential collection and the mapping function is to be applied only in the value position in maps, you could define a map-leaves function like so:

(defn map-leaves [f x]
  (cond
    (map? x) (persistent!
               (reduce-kv (fn [out k v]
                            (assoc! out k (map-leaves f v)))
                 (transient {})
                 x))
    (set? x) (into #{} (map #(map-leaves f %)) x)
    (sequential? x) (into [] (map #(map-leaves f %)) x)
    :else (f x)))

At the REPL:

(map-leaves inc {:a 0 :b {:c 1 :d [{:e 2} {:f 3}]}})
;= {:a 1, :b {:c 2, :d [{:e 3} {:f 4}]}}

(map-leaves inc {:a 0 :b {:c 1 :d [{:e 2} {:f 3} {:g #{1 2 3}}]}})
;= {:a 1, :b {:c 2, :d [{:e 3} {:f 4} {:g #{4 3 2}}]}}