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.
(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