Dark theme | Light theme

February 23, 2021

Clojure Goodness: Merge Maps With Function To Set Value Duplicate Keys

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. We specify as first argument the function that will be used when the same key is available in multiple maps. The function must accept two arguments, where the the first argument is the value of the key in the first map and the second argument the value of the same key in the following map. The result is assigned to the key in the resulting map. If we pass more than two maps to the merge-with the function will be called multiple times for a key if it is part of more than two maps.

In the following example we use Clojure core functions and a custom function to merge multiples maps, so we can alter the value for duplicate keys:

(ns mrhaki.core.merge-with
  (:require [clojure.test :refer [is]]))

;; Merge maps and use the function specified as first argument
;; to calculate the value for keys that are present
;; in multiple maps.
(is (= {:a 60 :b 3 :c 44 :d 100}
       (merge-with * {:a 2 :b 3 :c 4} {:a 10 :c 11} {:a 3 :d 100})))

;; Works for all maps and independent of type that is used for keys.
;; We can use any function for merge-with.
(def languages (merge-with (comp vec flatten conj) {"Clojure" [:dynamic :functional]}
                           {"Java" [:jvm]}
                           {"Groovy" [:jvm]}
                           {"Clojure" [:jvm]}
                           {"Groovy" [:dynamic]}))

(is (= {"Clojure" [:dynamic :functional :jvm]
        "Java"    [:jvm]
        "Groovy"  [:jvm :dynamic]}

;; Sample map with small inventory.
(def inventory {"pencil" {:count 10 :price 0.25}
                "pen"    {:count 23 :price 0.4}})
;; Sample basket with items.
(def basket {"pencil" {:count 5} "pen" {:count 2}})

;; Function to subtract the :count value for a basket item
;; from the :count value for the same inventory item.
(defn item-sold
  [inventory-item basket-item]
  (update-in inventory-item [:count] - (:count basket-item)))

(is (= {"pencil" {:count 5 :price 0.25}
        "pen"    {:count 21 :price 0.4}}
       (merge-with item-sold inventory basket)))

Written with Clojure 1.10.1.