合并地图而不覆盖键

时间:2014-10-29 19:34:12

标签: clojure

我有一个clojure函数,它返回一系列1键映射。我想将这些地图合并到一个地图中;但是,如果有相同键的地图,我不想覆盖这些值,只是将它们组合成一个矢量。 merge似乎会覆盖,merge-with似乎严重扭曲了这种类型。

我有:

({:foo "hello"}
 {:bar "world"} 
 {:baz "!!!"}
 {:ball {:a "abc", :b "123"}}
 {:ball {:a "def", :b "456"}}
 {:ball {:a "ghi", :b "789"}})

我想:

{:foo "hello"
 :bar "world"
 :baz "!!!"
 :ball [{:a "abc", :b "123"} {:a "def", :b "456"} {:a "ghi", :b "789"}]}

感谢。

3 个答案:

答案 0 :(得分:2)

(def data ...) ;; your list of maps

(apply merge-with (comp flatten vector) data)
;; => {:baz "!!!", :ball ({:b "123", :a "abc"} {:b "456", :a "def"} {:b "789", :a "ghi"}), :bar "world", :foo "hello"}

注意: flatten的使用适用于OP的情况,但不是在创建属于碰撞键的值的向量时合并地图的一般方法。

答案 1 :(得分:1)

"矢量安全"我可以提出的变体必须遍历所有键值对两次:

(->> (for [[k vs] (group-by key (apply concat data))]
       (if (next vs)
         [k (mapv val vs)]
         (first vs)))
     (into {}))
;; => {:foo "hello",
;;     :bar "world",
;;     :baz "!!!",
;;     :ball [{:a "abc", :b "123"} ...]}

基本上,它按键对所有值进行分组,只有当它只包含一个元素时才会删除它们周围的seq。


完全线程版本(为了便于阅读):

(->> (apply concat data)
     (group-by key)
     (map
       (fn [[k vs]]
         (if (next vs)
           [k (mapv val vs)]
           (first vs))))
     (into {}))

答案 2 :(得分:0)

为每个键设置一个可预测的类型,如果你想稍后再读它,可以省去头痛,但是如果你别无选择:带有自定义函数的merge-with可以解决它:

(apply merge-with (fn [v1 v2] ((if (vector? v1) conj vector) v1 v2)) data)