1
votes

I am new to Clojure. My question is that how to get a intersection of two hash-maps, e.g.

(def map-1 {"a" 2, "b" 1, "c" 4, "d" 3})
(def map-2 {"a" 3, "b" 6, "e" 5})

As we defined two maps, the expected result would be {"a" 3, "b" 6}, it is in a map with intersected keys and the max value of the key.

Somehow I came up with a solution and implemented it, but it works partially correct.

Basic idea is that find the map which has the smallest amount of items in it, use it as a reference. For each item in the reference map to check whether the other map contains it. If it contains, put the key to output map with its max value (using (max num1 num2))

The following is my sample code:

(defn max-intersect [map1 map2]
  (let [smaller-map (if (< (count map1) (count map2))
                      map1
                      map2)
        longer-map (if (= smaller-map map1)
                     map2
                     map1)]
        (loop [output {}
               reference-map smaller-map]
          (if (empty? reference-map)
            output
            (recur (let [[item-key item-val] (first smaller-map)]
                     (when (contains? longer-map item-key)
                       (assoc output item-key (max item-val (get longer-map item-key)))))
                   (rest reference-map))))))

This is my repl result:

test-intersect.core=> (def map1 {"a" 2, "b" 1, "c" 4, "d" 3})
#'test-intersect.core/map1
test-intersect.core=> (def map2 {"a" 3, "b" 6, "e" 5})
#'test-intersect.core/map2
test-intersect.core=> (max-intersect map1 map2)
{"a" 3}

It may seem complex, I am also waiting for any great and efficient solutions.

Thanks a lot!

3

3 Answers

8
votes

This can be done with merge-with using max as the merging function:

(def map-1 {"a" 2, "b" 1, "c" 4, "d" 3})
(def map-2 {"a" 3, "b" 6, "e" 5})
(merge-with max map-1 map-2)
=> {"a" 3, "b" 6, "c" 4, "d" 3, "e" 5}

merge-with is like merge but it allows you to pass a function to pick the merged value when a key appears in both maps.

To only include keys that appear in both maps, you could then use select-keys with the set intersection of both maps' keys:

(select-keys (merge-with max map-1 map-2)
             (clojure.set/intersection
               (set (keys map-1))
               (set (keys map-2))))
1
votes
(require '[clojure.set :refer [intersection]])

(defn merge-with-max-intersecting
  [m1 m2]
  (let [intersecting (intersection (set (keys m1))
                                   (set (keys m2)))]
    (merge-with max
                (select-keys m1 intersecting)
                (select-keys m2 intersecting))))

(merge-with-max-intersecting {"a" 2, "b" 1, "c" 4, "d" 3}
                             {"a" 3, "b" 6, "e" 5})
;;=> {"a" 3, "b" 6}
1
votes

you can also do it in one pass like this:

(defn merge-maps [m1 m2]
  (reduce-kv (fn [acc k v]
               (if (contains? m1 k)
                 (assoc acc k (max v (m1 k)))
                 acc))
             {}
             m2))

user> (merge-maps map-1 map-2)
;;=> {"a" 3, "b" 6}