I want to transform one map of values to another map with the same keys but with a function applied to the values. I would think there was a function for doing this in the c
map-map
, map-map-keys
, and map-map-values
I know of no existing function in Clojure for this, but here’s an implementation of that function as map-map-values
that you are free to copy. It comes with two closely related functions, map-map
and map-map-keys
, which are also missing from the standard library:
(defn map-map
"Returns a new map with each key-value pair in `m` transformed by `f`. `f` takes the arguments `[key value]` and should return a value castable to a map entry, such as `{transformed-key transformed-value}`."
[f m]
(into (empty m) (map #(apply f %) m)) )
(defn map-map-keys [f m]
(map-map (fn [key value] {(f key) value}) m) )
(defn map-map-values [f m]
(map-map (fn [key value] {key (f value)}) m) )
You can call map-map-values
like this:
(map-map-values str {:a 1 :b 2})
;; => {:a "1", :b "2"}
And the other two functions like this:
(map-map-keys str {:a 1 :b 2})
;; => {":a" 1, ":b" 2}
(map-map (fn [k v] {v k}) {:a 1 :b 2})
;; => {1 :a, 2 :b}
If you only want map-map-keys
or map-map-values
, without the more general map-map
function, you can use these implementations, which don’t rely on map-map
:
(defn map-map-keys [f m]
(into (empty m)
(for [[key value] m]
{(f key) value} )))
(defn map-map-values [f m]
(into (empty m)
(for [[key value] m]
{key (f value)} )))
Also, here’s an alternative implementation of map-map
that is based on clojure.walk/walk instead of into, if you prefer this phrasing:
(defn map-map [f m]
(clojure.walk/walk #(apply f %) identity m) )
pmap-map
, etc.There are also parallel versions of these functions if you need them. They simply use pmap instead of map.
(defn pmap-map [f m]
(into (empty m) (pmap #(apply f %) m)) )
(defn pmap-map-keys [f m]
(pmap-map (fn [key value] {(f key) value}) m) )
(defn pmap-map-values [f m]
(pmap-map (fn [key value] {key (f value)}) m) )
Here is a fairly typical way to transform a map.
zipmap
takes a list of keys and a list of values and "does the right thing" producing a new Clojure map. You could also put the map
around the keys to change them, or both.
(zipmap (keys data) (map #(do-stuff %) (vals data)))
or to wrap it up in your function:
(defn map-function-on-map-vals [m f]
(zipmap (keys m) (map f (vals m))))
I like your reduce
version. With a very slight variation, it can also retain the type of records structures:
(defn map-function-on-map-vals [m f]
(reduce (fn [altered-map [k v]] (assoc altered-map k (f v))) m m))
The {}
was replaced by m
. With that change, records remain records:
(defrecord Person [firstname lastname])
(def p (map->Person {}))
(class p) '=> Person
(class (map-function-on-map-vals p
(fn [v] (str v)))) '=> Person
By starting with {}
, the record loses its recordiness, which one might want to retain, if you desire the record capabilities (compact memory representation for instance).
I like your reduce
version just fine. I think it's idiomatic. Here's a version using list comprehension anyways.
(defn foo [m f]
(into {} (for [[k v] m] [k (f v)])))
I'm wondering why nobody has mentioned the specter library yet. It has been written to make this kind of transform easy to code (and, even more important, the written code easy to understand), while still being very performant:
(require '[com.rpl.specter :as specter])
(defn map-vals [m f]
(specter/transform
[specter/ALL specter/LAST]
f m))
(map-vals {:a "test" :b "testing"}
#(.toUpperCase %))
Writing such a function in pure Clojure is simple, but the code gets much trickier once you move on to highly nested code composed of different data structures. And this is where specter comes in.
I recommend watching this episode on Clojure TV which explains the motivation behind and details of specter.
Here's a fairly idiomatic way to do this:
(defn map-function-on-map-vals [m f]
(apply merge
(map (fn [[k v]] {k (f v)})
m)))
Example:
user> (map-function-on-map-vals {1 1, 2 2, 3 3} inc))
{3 4, 2 3, 1 2}