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.
(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.
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 {}))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With