[英]Clojure: update-in, but with wildcards and path tracking
我正在尝试更新由嵌套映射和序列组成的结构中的值,但是由于我想允许使用通配符update-in
因此无法进行update-in
。 我的手动方法使我难看,大的,嵌套for
和into {}
调用。 我最终制作了一个带有结构的函数,类似选择器的序列和一个更新函数。
(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))))))
这使我可以简化对以下内容的更新
(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)))
显然,这具有深度嵌套结构的堆栈溢出问题。 尽管我还没有达到那个目标,但是拥有一个强大的解决方案将是很好的。 谁能提出一个更简单/更快/更优雅的解决方案? 可以用减速器/换能器完成吗?
UPDATE要求更新函数还必须获取要更新的值的完整路径。
update-in
具有与您创建的功能完全相同的签名,并且几乎具有相同的功能。 有两个区别:它不允许在“路径”中使用通配符,并且不会将中间路径传递给更新函数。
添加通配符以进行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)))))
现在,这两行产生相同的结果:
(update-in-* sample [* * :geometry * :buffer] (fn [buf] (inc buf)))
(update-each-in sample [* * :geometry * :buffer] (fn [buf path] (inc buf)))
我对update-in
的更改只是通过检查通配符。 如果遇到通配符,则必须修改该级别的每个子节点。 我使用reduce
来保持对集合的累积更新。
另外,出于健壮性的考虑,我还要说一句:我尝试对通配符使用*
以外的名称。 它可能作为映射中的键出现。
添加路径跟踪以进行update-in
如果要求更新功能接收完整路径,那么我将再修改一次update-in
。 函数签名更改并添加了(conj pk)
,仅此而已。
(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)))))
现在,这两行产生相同的结果:
(update-in-* sample [* * :geometry * :buffer] (fn [path val] (inc val)))
(update-each-in sample [* * :geometry * :buffer] (fn [buf path] (inc buf)))
这比您原来的解决方案好吗? 我不知道。 我喜欢它,因为它是在update-in
之后建模的,并且其他人可能在update-in
比我自己更在意。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.