Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In clojure, how to merge several maps combining mappings with same key into a list?

Tags:

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?

like image 904
viebel Avatar asked Feb 23 '12 07:02

viebel


People also ask

How do I merge two maps in Clojure?

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 .

How do I merge arrays in maps?

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.


2 Answers

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]})))
like image 139
amalloy Avatar answered Oct 13 '22 23:10

amalloy


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)}
like image 24
mikera Avatar answered Oct 14 '22 00:10

mikera