Mapping a function on the values of a map in Clojure

DictionaryClojureMap Function

Dictionary Problem Overview


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 clojure api, but I have been unable to find it.

Here's an example implementation of what I'm looking for

(defn map-function-on-map-vals [m f]
  (reduce (fn [altered-map [k v]] (assoc altered-map k (f v))) {} m))
(println (map-function-on-map-vals {:a "test" :b "testing"} #(.toUpperCase %)))
{:b TESTING, :a TEST}

Does anybody know if map-function-on-map-vals already exists? I would think it did (probably with a nicer name too).

Dictionary Solutions


Solution 1 - Dictionary

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)])))

Solution 2 - Dictionary

You can use the clojure.algo.generic.functor/fmap:

user=> (use '[clojure.algo.generic.functor :only (fmap)])
nil
user=> (fmap inc {:a 1 :b 3 :c 5})
{:a 2, :b 4, :c 6}

Solution 3 - Dictionary

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))))

Solution 4 - Dictionary

Taken from the Clojure Cookbook, there is reduce-kv:

(defn map-kv [m f]
  (reduce-kv #(assoc %1 %2 (f %3)) {} m))

Solution 5 - Dictionary

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}

Solution 6 - Dictionary

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) )

Usage

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}

Alternative implementations

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) )

Parellel versions – 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) )

Solution 7 - Dictionary

I'm a Clojure n00b, so there may well be much more elegant solutions. Here's mine:

(def example {:a 1 :b 2 :c 3 :d 4})
(def func #(* % %))

(prn example)

(defn remap [m f]
  (apply hash-map (mapcat #(list % (f (% m))) (keys m))))

(prn (remap example func))

The anon func makes a little 2-list from each key and its f'ed value. Mapcat runs this function over the sequence of the map's keys and concatenates the whole works into one big list. "apply hash-map" creates a new map from that sequence. The (% m) may look a little weird, it's idiomatic Clojure for applying a key to a map to look up the associated value.

Most highly recommended reading: The http://clojure.org/cheatsheet">Clojure Cheat Sheet .

Solution 8 - Dictionary

(defn map-vals
  "Map f over every value of m.
   Returns a map with the same keys as m, where each of its values is now the result of applying f to them one by one.
   f is a function of one arg, which will be called which each value of m, and should return the new value.
   Faster then map-vals-transient on small maps (8 elements and under)"
  [f m]
  (reduce-kv (fn [m k v]
               (assoc m k (f v)))
             {} m))

(defn map-vals-transient
  "Map f over every value of m.
   Returns a map with the same keys as m, where each of its values is now the result of applying f to them one by one.
   f is a function of one arg, which will be called which each value of m, and should return the new value.
   Faster then map-vals on big maps (9 elements or more)"
  [f m]
  (persistent! (reduce-kv (fn [m k v]
                            (assoc! m k (f v)))
                          (transient {}) m)))

Solution 9 - Dictionary

Clojure 1.11 added a function for this to clojure.core, the standard library. Clojure 1.11 was released on 2022-03-22.

update-vals

(update-vals m f) applies the function to every value in the map. It returns a new map {k (f v) ...}.

Usage
(update-vals {:a 1 :b 2} str)
;;        => {:a "1", :b "2"}

See also: update-keys

(update-keys m f) applies the function to every key in the map. It returns a new map {(f k) v ...}. All keys resulting from (f k) must be unique.

This function, too, was added in Clojure 1.11.

Usage
(update-keys {:a 1 :b 2} str)
;;        => {":a" 1, ":b" 2}

Solution 10 - Dictionary

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).

Solution 11 - Dictionary

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.

Solution 12 - Dictionary

Clojure 1.7 (released June 30, 2015) provides an elegant solution for this with update:

(defn map-function-on-map-vals [m f]
    (->> (map #(update % 1 f) m)
         (into {})))

(map-function-on-map-vals {:a "test" :b "testing"} #(.toUpperCase %))
;; => {:a "TEST", :b "TESTING"}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionThomasView Question on Stackoverflow
Solution 1 - DictionaryBrian CarperView Answer on Stackoverflow
Solution 2 - DictionaryArthur EdelsteinView Answer on Stackoverflow
Solution 3 - DictionaryArthur UlfeldtView Answer on Stackoverflow
Solution 4 - DictionaryroboliView Answer on Stackoverflow
Solution 5 - DictionarySiddhartha ReddyView Answer on Stackoverflow
Solution 6 - DictionaryRory O'KaneView Answer on Stackoverflow
Solution 7 - DictionaryCarl SmotriczView Answer on Stackoverflow
Solution 8 - DictionaryDidier A.View Answer on Stackoverflow
Solution 9 - DictionaryRory O'KaneView Answer on Stackoverflow
Solution 10 - DictionaryolangeView Answer on Stackoverflow
Solution 11 - DictionarymzutherView Answer on Stackoverflow
Solution 12 - DictionaryJack PendletonView Answer on Stackoverflow