2016-10-11 154 views
1

我有这样的地图的矢量地图:更新嵌套结构

{:tags ["type:something" "gw:somethingelse"], 
:sources [{:tags ["s:my:tags"], 
      :metrics [{:tags ["a tag"]} 
         {:tags ["a noether tag" "aegn"]} 
         {:tags ["eare" "rh"]}]}]} 

注意,可以有多个源,和多个指标。

现在我想通过查看标签的值来更新:metricsid

例:["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"]}]}]} 

我做了一个功能transform能够在标签转换为ID 。例如,(transform "a tag")返回1.

现在,当我尝试添加带有理解的id时,我错过了旧的结构(只有内部的结果返回),并且使用assoc-in我必须先了解索引。

如何优雅地进行这种转换?

+0

在编辑你的问题来平衡括号,我担心我可能做了错误的修复。原始和期望的数据都有一个只有一个元素的“:sources”向量。它是一个矢量,还是应该剥去多余的开放方括号? – Thumbnail

+0

键“:sources”的值是地图的矢量。现在的样子对我来说很好看。 –

回答

8

我会自下而上,为:tags条目创建转换函数,然后为:metrics,然后为:sources

让我们说我们的变换函数产生的IDS只是通过计算标签(仅仅是一个例子,它可以很容易地后来改变):

(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}]}]} 

并基于这种变压器构造方法,我们可以做出一个很好的函数来更新矢量地图向量中的值...结构,任意嵌套层次:

(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}]}]} 

UPDATE

除了本手册的做法,有一个梦幻般的LIB叫specter在那里,这不完全一样的东西(以及更多),但显然更具有普遍性和可用:

(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

更新我的答案与**幽灵** lib例子 – leetwinski

+0

不错!这是一个很好的答案。 –

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"]}]}]} 
+0

不错的方法,但它也可以转换不需要的':tags'值(例如顶级:标签),如果'transform'函数已经为它们返回了一些id值... – leetwinski

+0

可以使用一个简单的函数(或使用clojure。 spec)在进行转换之前检查一致性。例如。 (和标签(不是(或源指标)))) – rmcv

+0

我喜欢这种方法!谢谢。 –