合并两个复杂的数据结构

时间:2013-06-26 18:26:30

标签: clojure

我无法找到解决以下问题的方法:

假设我有一张地图:

(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带有右向量矢量的向量(如果当然存在相应的键)
  4. 感谢您的帮助:)

2 个答案:

答案 0 :(得分:24)

你一定要看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))

答案 1 :(得分: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"}