Clojure: map-values and map-keys


This post covers some useful Clojure functions for transforming the keys and values of maps.

map-values

When writing Clojure, I often find I want to transform the values of a map and apply the same function to each value. Clojure makes this easy as the map function breaks a map data structure into key-value tuples that are easy to manipulate. I find this comes up often enough that having a more specialised function is not only more convenient but conveys the code's intention more clearly.

(defn map-values [f m]
  (->> (map (fn [[k v]] [k (f v)]) m)
       (into {})))

(map-values inc {:a 1 :b 2 :c 3})

=> {:a 2 :b 3 :c 4}

(map-values #(str "beta-" %)
 {:sign-up "event" :log-out "event"})

=> {:sign-up "beta-event" :log-out "beta-event"}

This function is specifically for map collection types, but there is a more generic version called fmap available in the clojure/algo.generic library.

UPDATE: As of Clojure 1.11.0 there is now a built in function in clojure.core called update-vals which behaves identically to map-values but takes the arguments in the opposite order (update-vals m f).

map-keys

Another common task I run into is transforming the keys of a map. Useful when you are at the edge of your codebase and want to communicate to another system that might have different conventions; for example when sending up analytic events. The analytic system might camel case its keys or map keys to entirely different words or domain language.

(defn map-keys [f m]
  (->> (map (fn [[k v]] [(f k) v]) m)
       (into {})))

(map-keys #(str "beta-" (name %))
 {:sign-up "event" :log-out "event"})

=> {"beta-sign-up" "event" "beta-log-out" "event"}

(def keys->analytics-event-names
  {:message-sent         "Primary announcement sent"
   :transaction-complete "Item purchased"})

(map-keys keys->analytics-event-names
 {:message-sent         "event"
  :transaction-complete "event"})

=> {"Primary anouncement sent" "event",
    "Item purchased"           "event"}

There's also a core function called clojure.set/rename-keys which does a similar thing to our map-keys function.

(doc clojure.set/rename-keys)

=>
-------------------------
clojure.set/rename-keys
([map kmap])
  Returns the map with the keys in kmap renamed to the vals in kmap

The downside with this function is it takes a key-map not a function, meaning it's only useful for mapping keys. The upside is that it leaves keys that are not in the key-map unchanged.

(def keys->analytics-event-names
  {:message-sent         "Primary announcement sent"
   :transaction-complete "Item purchased"})

(map-keys keys->analytics-event-names
 {:message-sent         "event"
  :transaction-complete "event"
  :missing-key          "event"})

=> {"Primary anouncement sent" "event",
    "Item purchased"           "event"
    nil                        "event"}

(clojure.set/rename-keys
 {:message-sent         "event"
  :transaction-complete "event"
  :missing-key          "event"}
  keys->analytics-event-names)

=> {:missing-key                "event"
    "Primary announcement sent" "event"
    "Item purchased"            "event"}

UPDATE: As of Clojure 1.11.0 there is now a built in function in clojure.core called update-keys which behaves identically to map-keys but takes the arguments in the opposite order (update-keys m f).

I hope these functions come in handy.