2013-06-26 58 views
9

我無法找到解決如下問題:合併兩個複雜的數據結構

可以說我有一張地圖:

(def defaults { 
    :name "John" 
    :surname "Doe" 
    :info {:date-of-birth "01-01-1980" 
      :registered [{:type "newsletter" :name "breaking news" }]} 
}) 

然後我通過一個類似結構的地圖,我想合相載體和覆蓋鍵的其餘部分:

(def new { 
    :name "Peter" 
    :info {:date-of-birth "11-01-1986" 
      :registered [{:type "alert" :name "mobile-alert" }]} 
}) 

而且我想這樣的結果:

{:name "Peter" 
    :surname "Doe" 
    :info {:date-of-birth "11-01-1986" 
      :registered [{:type "newsletter" :name "breaking news" } 
          {:type "alert"  :name "mobile-alert" }]}} 

現在我可以用靜態的語法像這樣做很容易:

(reduce conj (get-in defaults [:info :registered]) (get-in new [:info :registered])) 

(有可能是一個更好的辦法...),但我希望更多的動態功能具有以下屬性:

  1. 保持所有鍵從兩個圖,在不知道結構
  2. 更新與所述值的任何鍵從右側地圖
  3. 如果一個鍵的val爲一個矢量,然後conj用正確的地圖矢量矢量(如果當然相應的鍵存在)

感謝您的幫助提前:)

回答

20

你絕對應該看看merge-with函數。這是可能的實現:

(defn deep-merge [a b] 
    (merge-with (fn [x y] 
       (cond (map? y) (deep-merge x y) 
         (vector? y) (concat x y) 
         :else y)) 
       a b)) 
+0

+1完全忘了'merge-with'。 –

+0

完美地工作。我懷疑這與合併有關,但我無法弄清楚......乾杯! –

0

下面是這種功能的可能的實現。這至少是一個起點,您可能需要一些額外的驗證,具體取決於數據的可能結構(例如,如果重寫映射的值是一個向量,但默認映射中的值甚至不是集合呢?)。

(declare merge-maps) 

(defn merge-map [x [k v]] 
    (cond (vector? v) 
      (assoc x k (vec (reduce conj (x k) v))) 
     (map? v) 
      (assoc x k (merge-maps (x k) v)) 
     :esle 
      (assoc x k v))) 

(defn merge-maps [x y] 
    (reduce merge-map x y)) 

(merge-maps defaults new) 

;= {:info {:date-of-birth "11-01-1986", 
;=   :registered [{:name "breaking news", :type "newsletter"} 
;=      {:name "mobile-alert", :type "alert"}]}, 
;= :name "Peter", 
;= :surname "Doe"}