2016-10-11 152 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

我喜歡這種方法!謝謝。 –