HavocShot
HavocShot

Reputation: 62

How to merge list of maps in clojure

I am given a list maps:

({:a 1 :b ["red" "blue"]} {:a 2 :b ["green"]} {:a 1 :b ["yellow"]} {:a 2 :b ["orange"]})

and I need to combine them to ultimately look like this:

({:a 1 :b ["red" "blue" "yellow"]} {:a 2 :b ["green" "orange"]})

Where the maps are combined based off the value of the key "a". So far, I have this

(->> (sort-by :a)
     (partition-by :a)
     (map (partial apply merge)))

But the merge will overwrite the vector in "b" with the last one giving me

({:a 1 :b ["yellow"]} {:a 2 :b ["orange"]}) 

Upvotes: 2

Views: 866

Answers (2)

amalloy
amalloy

Reputation: 92147

You don't really want to sort or partition anything: that is just CPU time spent on nothing, and at the end you have an awkward data structure (a list of maps) instead of something that would be more convenient, a map keyed by the "important" :a value.

Rather, I would write this as a reduce, where each step uses merge-with on the appropriate subsection of the eventual map:

(defn combine-by [k ms]
  (reduce (fn [acc m]
            (update acc (get m k) 
                    (partial merge-with into) 
                    (dissoc m k)))
          {}, ms))

after which we have

user=> (combine-by :a '({:a 1 :b ["red" "blue"]} 
                        {:a 2 :b ["green"]} 
                        {:a 1 :b ["yellow"]} 
                        {:a 2 :b ["orange"]}))
{1 {:b ["red" "blue" "yellow"]}, 2 {:b ["green" "orange"]}}

which is usefully keyed for easily looking up a map by a specific :a, or if you prefer to get the results back out as a list, you can easily unroll it.

Upvotes: 0

Alejandro C.
Alejandro C.

Reputation: 3801

Instead of merge, use merge-with, i.e.

(->> a (sort-by :a) 
       (partition-by :a) 
       (map (partial 
          apply 
          merge-with (fn [x y] (if (= x y) x (into x y))))))

Outputs

({:a 1, :b ["red" "blue" "yellow"]} {:a 2, :b ["green" "orange"]})

Upvotes: 3

Related Questions