Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

idiomatically merge maps into a map of value sets with clojure

Tags:

idioms

clojure

I am trying idiomatically merge multiple maps into a single map using clojure.

Input

{:a 1 :b "a"}
{:a 2 :b "b"}
{:a 3 :b "c"}
{:a 4 :b "a"}

Expected

{:a #{1,2,3,4}, :b #{"a" "b" "c"}} 

The values for each key are converted into a set of values in the original maps.

like image 441
Toby Hede Avatar asked Jun 27 '12 11:06

Toby Hede


2 Answers

I'd use merge-with, using a pre-built structure that contains empty sets:

(def data [{:a 1 :b "a"}
           {:a 2 :b "b"}
           {:a 3 :b "c"}
           {:a 4 :b "a"}])

(let [base {:a #{} :b #{}}]
  (apply merge-with conj base data))

=> {:a #{1 2 3 4}, :b #{"a" "b" "c"}}

The trick of using empty sets in the base map is so that conj has a concrete object to work upon, and hence works correctly.

like image 78
mikera Avatar answered Nov 15 '22 08:11

mikera


merge-with can be used for this like:

(def d [{:a 1 :b "a"}
        {:a 2 :b "b"}
        {:a 3 :b "c"}
        {:a 4 :b "a"}])

(def initial (into {} (map #(vector  %1 [])  (keys (apply merge d)))))

(into {} (map (fn [[a b]] [a (set b)]) 
           (apply merge-with (fn [a b] (conj a b)) initial  d)))
like image 26
Ankur Avatar answered Nov 15 '22 10:11

Ankur