151

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

Will Ness
71.6k10 gold badges105 silver badges193 bronze badges
asked Nov 4, 2009 at 21:49

12 Answers 12

174

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)])))
answered Nov 5, 2009 at 2:16
Sign up to request clarification or add additional context in comments.

8 Comments

I like this version because it's super short and obvious if you understand all the functions and such it uses. And if you don't it's an excuse to learn them!
I agree. I didn't know the into function, but it makes perfect sense using it here.
Oh man you hadn't seen into? You are in for a treat. I abuse the hell out of that function every chance I get. So powerful and useful.
I like it, but is there any reason for the arguments order (besides the question) ? I was expecting [f m] as in map for some reason.
I strongly recommend to use (empty m) rather then {}. So it would keep being a particular type of map.
|
101

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}
Andrew Marshall
97.3k20 gold badges228 silver badges217 bronze badges
answered Sep 21, 2010 at 5:44

4 Comments

I know this answer was a bit late looking at the dates, but I reckon it is spot on.
Did this make it into clojure 1.3.0?
the generic.function lib has moved to a separate library: org.clojure/algo.generic "0.1.0" the example should now read: (use '[clojure.algo.generic.functor :only (fmap)]) (fmap inc {:a 1 :b 3 :c 5})
Any idea how to do find fmap in ClojureScript?
43

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))))
answered Nov 4, 2009 at 22:18

10 Comments

It irritates me that I have to supply the keys for it, but it's not a high price to pay. It definitely looks a lot nicer than my original suggestion.
Are we guaranteed that keys and vals return the corresponding values in the same order? For both sorted maps and hash maps?
Rob: yes, keys and vals will use the same order for all maps -- the same order as a seq on the map uses. Since hash, sorted, and array maps are all immutable, there's no chance of the order changing in the mean time.
That does seem to be the case, but is it documented anywhere? At least the docstrings for keys and vals fail to mention this. I would be more comfortable using this if I could point to some official documentation that promises it's going to work.
@Jason Yeah, they added this to the documentation in version 1.6 I think. dev.clojure.org/jira/browse/CLJ-1302
|
29

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}
answered Mar 23, 2022 at 3:56

Comments

26

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

(defn map-kv [m f]
 (reduce-kv #(assoc %1 %2 (f %3)) {} m))
answered Oct 22, 2014 at 18:46

Comments

8

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}
answered Nov 5, 2009 at 5:09

1 Comment

If it isn't clear: the anon function destructures the key and value to k and v and then returns a hash-map mapping k to (f v).
6

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) )
answered Nov 24, 2013 at 5:49

1 Comment

Also check prismatics map-vals function. It's faster using transients github.com/Prismatic/plumbing/blob/…
2

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 Clojure Cheat Sheet .

answered Nov 4, 2009 at 22:12

3 Comments

I thought about going trough sequences as you've done in your example. I also like the name of you're function much more than my own :)
In Clojure, keywords are functions that look themselves up in whatever sequence is passed to them. That's why (:keyword a-map) works. But using the key as a function to look itself up in a map doesn't work if the key is not a keyword. So you might want to change the (% m) above to (m %) which will work no matter what the keys are.
Oops! Thanks for the tip, Siddhartha!
2

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

answered Oct 17, 2014 at 20:10

Comments

2
(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)))
answered Nov 12, 2018 at 4:50

Comments

0

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.

answered Jan 10, 2020 at 10:20

Comments

0

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"}
answered Apr 13, 2020 at 22:07

1 Comment

This returns a seq not a hash-map

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.