我有一张地图矢量地图,如下所示:
{:tags ["type:something" "gw:somethingelse"],
:sources [{:tags ["s:my:tags"],
:metrics [{:tags ["a tag"]}
{:tags ["a noether tag" "aegn"]}
{:tags ["eare" "rh"]}]}]}
请注意,可以有多个来源和多个指标。
现在,我想通过查看代码的值来更新:metrics
id
。
示例:如果["a tag"]
与例如id 1匹配,["a noether tag" "aegn"]
与id 2匹配,我希望更新后的结构如下所示:
{:tags ["type:something" "gw:somethingelse"],
:sources [{:tags ["s:my:tags"],
:metrics [{:tags ["a tag"]
:id 1}
{:tags ["a noether tag" "aegn"]
:id 2}
{:tags ["eare" "rh"]}]}]}
我创建了一个可以将标签转换为id的函数transform
。例如,(transform "a tag")
返回1.
现在,当我尝试添加具有for-comprehension的id时,我会错过旧结构(只返回内部结构)和assoc-in
我必须先了解索引。
如何优雅地执行此转换?
答案 0 :(得分:8)
我会自下而上,为:tags
条目创建转换函数,然后为:metrics
创建转换函数,然后为:sources
创建转换函数。
让我们说我们的转换函数只是通过计算标签来生成id(仅用于说明,以后可以轻松更改):
(defn transform [tags] (count tags))
user> (transform ["asd" "dsf"])
;;=> 2
然后应用转换度量条目:
(defn transform-metric [{:keys [tags] :as m}]
(assoc m :id (transform tags)))
user> (transform-metric {:tags ["a noether tag" "aegn"]})
;;=> {:tags ["a noether tag" "aegn"], :id 2}
现在使用transform-metric
更新源条目:
(defn transform-source [s]
(update s :metrics #(mapv transform-metric %)))
user> (transform-source {:tags ["s:my:tags"],
:metrics [{:tags ["a tag"]}
{:tags ["a noether tag" "aegn"]}
{:tags ["eare" "rh"]}]})
;;=> {:tags ["s:my:tags"],
;; :metrics [{:tags ["a tag"], :id 1}
;; {:tags ["a noether tag" "aegn"], :id 2}
;; {:tags ["eare" "rh"], :id 2}]}
,最后一步是转换整个数据:
(defn transform-data [d]
(update d :sources #(mapv transform-source %)))
user> (transform-data data)
;;=> {:tags ["type:something" "gw:somethingelse"],
;; :sources [{:tags ["s:my:tags"],
;; :metrics [{:tags ["a tag"], :id 1}
;; {:tags ["a noether tag" "aegn"], :id 2}
;; {:tags ["eare" "rh"], :id 2}]}]}
所以,我们在这里完成了。
现在注意transform-data
和transform-source
几乎完全相同,因此我们可以创建一个生成此类更新函数的实用程序函数:
(defn make-vec-updater [field transformer]
(fn [data] (update data field (partial mapv transformer))))
使用此函数,我们可以定义如下的深度转换:
(def transformer
(make-vec-updater
:sources
(make-vec-updater
:metrics
(fn [{:keys [tags] :as m}]
(assoc m :id (transform tags))))))
user> (transformer data)
;;=> {:tags ["type:something" "gw:somethingelse"],
;; :sources [{:tags ["s:my:tags"],
;; :metrics [{:tags ["a tag"], :id 1}
;; {:tags ["a noether tag" "aegn"], :id 2}
;; {:tags ["eare" "rh"], :id 2}]}]}
并且基于这种变换器构造方法,我们可以创建一个很好的函数来更新vector-of-maps-of-vectors-of-vectors ...结构中的值,具有任意嵌套级别:
(defn update-in-v [data ks f]
((reduce #(make-vec-updater %2 %1) f (reverse ks)) data))
user> (update-in-v data [:sources :metrics]
(fn [{:keys [tags] :as m}]
(assoc m :id (transform tags))))
;;=> {:tags ["type:something" "gw:somethingelse"],
;; :sources [{:tags ["s:my:tags"],
;; :metrics [{:tags ["a tag"], :id 1}
;; {:tags ["a noether tag" "aegn"], :id 2}
;; {:tags ["eare" "rh"], :id 2}]}]}
<强>更新强>
除了这种手动方法之外,还有一个名为specter的奇妙的lib,它完全相同(甚至更多),但显然更通用和可用:
(require '[com.rpl.specter :as sp])
(sp/transform [:sources sp/ALL :metrics sp/ALL]
(fn [{:keys [tags] :as m}]
(assoc m :id (transform tags)))
data)
;;=> {:tags ["type:something" "gw:somethingelse"],
;; :sources [{:tags ["s:my:tags"],
;; :metrics [{:tags ["a tag"], :id 1}
;; {:tags ["a noether tag" "aegn"], :id 2}
;; {:tags ["eare" "rh"], :id 2}]}]}
答案 1 :(得分:2)
(use 'clojure.walk)
(def transform {["a tag"] 1
["a noether tag" "aegn"] 2})
(postwalk #(if-let [id (transform (:tags %))]
(assoc % :id id)
%)
data)
输出:
{:tags ["type:something" "gw:somethingelse"],
:sources
[{:tags ["s:my:tags"],
:metrics
[{:tags ["a tag"], :id 1}
{:tags ["a noether tag" "aegn"], :id 2}
{:tags ["eare" "rh"]}]}]}