In Clojure, I would like to combine several maps into a single map where mappings with same key are combined into a list.
For example:
{:humor :happy} {:humor :sad} {:humor :happy} {:weather :sunny}
should lead to:
{:weather :sunny, :humor (:happy :sad :happy)}
I thought about:
(merge-with (comp flatten list) data)
But it is not efficient because flatten has O(n) complexity.
Then I came up with:
(defn agg[x y] (if (coll? x) (cons y x) (list y x)))
(merge-with agg data)
But it feels not idiomatic. Any other idea?
In Clojure we can use the merge function to merge multiple maps into a single map. If a key is in multiple maps the value of the key merged last will be used in the resulting map. If we want to influence how the value of a duplicate key is set we can use merge-with .
To merge Maps, use the spread operator (...) to unpack the values of two or more Maps into an array and pass them into the Map() constructor, e.g. new Map([... map1, ... map2]) . The new Map will contain the key-value pairs from all provided Map objects.
One approach would be
(defn merge-lists [& maps]
(reduce (fn [m1 m2]
(reduce (fn [m [k v]]
(update-in m [k] (fnil conj []) v))
m1, m2))
{}
maps))
It's a bit ugly, but that's only because your values aren't already lists. It also forces everything to be a list (so you'd get :weather [:sunny]
rather than :weather :sunny
). Frankly, this is likely to be a million times easier for you to work with anyway.
If you had each value as a vector already, you could simply do (apply merge-with into maps)
. So, another thing you could try would be to convert to vectors first, and then do the simple merge-with
. This probably performs a bit worse than the first approach because of the intermediate data structures.
(defn merge-lists [& maps]
(apply merge-with into
(for [m maps, [k v] m]
{k [v]})))
You could try the following, I think it's pretty efficient
(reduce
(fn [m pair] (let [[[k v]] (seq pair)]
(assoc m k (cons v (m k)))))
{}
data)
=> {:weather (:sunny), :humor (:happy :sad :happy)}
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