问题
I have a clojure function that returns a sequence of 1-key maps. I want to merge these maps into one map; however, if there are maps with the same key, I don't want to overwrite the values, only to combine them into a vector. merge
seems to overwrite, and merge-with
seems to seriously distort the type.
I have:
({:foo "hello"}
{:bar "world"}
{:baz "!!!"}
{:ball {:a "abc", :b "123"}}
{:ball {:a "def", :b "456"}}
{:ball {:a "ghi", :b "789"}})
I'd like:
{:foo "hello"
:bar "world"
:baz "!!!"
:ball [{:a "abc", :b "123"} {:a "def", :b "456"} {:a "ghi", :b "789"}]}
Thanks.
回答1:
(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"}
Note: the use of flatten
works in OP's case but is NOT a general way to merge maps while creating vectors of values belonging to colliding keys.
回答2:
The "vector-safe" variant I could come up with has to iterate over all key-value-pairs twice:
(->> (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"} ...]}
Basically, this groups all values by key and only removes the seq around them if it contains exactly one element.
The fully threaded version (for readability):
(->> (apply concat data)
(group-by key)
(map
(fn [[k vs]]
(if (next vs)
[k (mapv val vs)]
(first vs))))
(into {}))
回答3:
Having a predictable type for each key would save you headaches when you want to read it back later, but if you have no other choice: merge-with
with a custom function would solve it:
(apply merge-with (fn [v1 v2] ((if (vector? v1) conj vector) v1 v2)) data)
来源:https://stackoverflow.com/questions/26639048/merging-maps-without-overriding-keys