Friday 15 May 2015

map - Clojure: update-in, but with wildcards and path tracking -



map - Clojure: update-in, but with wildcards and path tracking -

i'm trying update values in construction consisting of nested maps , sequences, update-in won't work because want allow wildcards. manual approach led me ugly, big, nested for , into {} calls. ended making function takes structure, selector-like sequence, , update function.

class="lang-lisp prettyprint-override">(defn update-each-in ([o [head & tail :as path] f] (update-each-in o path f [])) ([o [head & tail :as path] f current-path] (cond (empty? path) (f o current-path) (identical? * head) (cond (map? o) (into {} (for [[k v] o] [k (update-each-in v tail f (conj current-path k))])) :else (for [[i v] (map-indexed vector o)] (update-each-in v tail f (conj current-path i)))) :else (assoc o head (update-each-in (get o head) tail f (conj current-path head))))))

this allows me simplify updates following

class="lang-lisp prettyprint-override">(def sample {"tr" [{:geometry {:id12 {:buffer 22}}} {:geometry {:id13 {:buffer 33} :id14 {:buffer 55}}} {:geometry {:id13 {:buffer 44}}}] "br" [{:geometry {:id13 {:buffer 22} :id18 {:buffer 11}}} {:geometry {:id13 {:buffer 33}}} {:geometry {:id13 {:buffer 44}}}]}) (update-each-in sample [* * :geometry * :buffer] (fn [buf path] (inc buf)))

obviously has stack overflow problem nested structures; although i'm far hitting one, it'd nice have robust solution. can suggest simpler/faster/more elegant solution? done reducers/transducers?

update it's requirement updating function gets total path value it's updating.

update-in has same signature function created, , same thing. there 2 differences: doesn't allow wildcards in "path," , doesn't pass intermediary paths update function.

adding wildcards update-in

i've adapted source code update-in.

(defn update-in-* [m [k & ks] f & args] (if (identical? k *) (let [idx (if (map? m) (keys m) (range (count m)))] (if ks (reduce #(assoc % %2 (apply update-in-* (get % %2) ks f args)) m idx) (reduce #(assoc % %2 (apply f (get % %2) args)) m idx))) (if ks (assoc m k (apply update-in-* (get m k) ks f args)) (assoc m k (apply f (get m k) args)))))

now these 2 lines produce same result:

(update-in-* sample [* * :geometry * :buffer] (fn [buf] (inc buf))) (update-each-in sample [* * :geometry * :buffer] (fn [buf path] (inc buf)))

the alter made update-in branching on check wildcard. if wildcard encountered, every child-node @ level must modified. used reduce maintain cumulative updates collection.

also, remark, in interests of robustness: i'd seek utilize other * wildcard. perchance occur key in map.

adding path-tracking update-in

if required updating function receive total path, modify update-in 1 more time. function signature changes , (conj p k) gets added, that's it.

(defn update-in-* [m ks f & args] (apply update-in-*-with-path [] m ks f args)) (defn- update-in-*-with-path [p m [k & ks] f & args] (if (identical? k *) (let [idx (if (map? m) (keys m) (range (count m)))] (if ks (reduce #(assoc % %2 (apply update-in-*-with-path (conj p k) (get % %2) ks f args)) m idx) (reduce #(assoc % %2 (apply f (conj p k) (get % %2) args)) m idx))) (if ks (assoc m k (apply update-in-*-with-path (conj p k) (get m k) ks f args)) (assoc m k (apply f (conj p k) (get m k) args)))))

now these 2 lines produce same result:

(update-in-* sample [* * :geometry * :buffer] (fn [path val] (inc val))) (update-each-in sample [* * :geometry * :buffer] (fn [buf path] (inc buf)))

is improve original solution? don't know. because modeled after update-in, , other people have set more careful thought update-in care myself.

map clojure tree

No comments:

Post a Comment