2013-06-26 3 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. если вал ключа является вектором, то 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"}