更新地图和矢量的嵌套结构

时间:2016-10-11 10:49:44

标签: clojure

我有一张地图矢量地图,如下所示:

{: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我必须先了解索引。

如何优雅地执行此转换?

2 个答案:

答案 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-datatransform-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"]}]}]}