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
  • 70,110
  • 9
  • 98
  • 181
Thomas
  • 2,769
  • 5
  • 21
  • 21

12 Answers12

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)])))
Brian Carper
  • 71,150
  • 28
  • 166
  • 168
  • 2
    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! – Runevault Nov 05 '09 at 03:14
  • I agree. I didn't know the into function, but it makes perfect sense using it here. – Thomas Nov 05 '09 at 05:40
  • 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. – Runevault Nov 05 '09 at 05:48
  • Nope. Neither are `hash-map` or `zipmap` or `merge` or `reduce`. – Brian Carper Nov 06 '09 at 02:44
  • btw, the [source code for `fmap`](https://github.com/clojure/algo.generic/blob/aa028f7467d193a5cfad0626b324f4c682b91c15/src/main/clojure/clojure/algo/generic/functor.clj#L19) (see Arthur Edelstein's answer), when used on a Clojure map, is essentially the same as this answer. – Mars Jan 07 '16 at 20:21
  • 3
    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. – nha Aug 21 '16 at 22:48
  • 4
    I strongly recommend to use (empty m) rather then {}. So it would keep being a particular type of map. – nickik Dec 30 '19 at 02:56
  • I haven't read the reduce one but this looks so cool. – vaibhav chaturvedy Jul 28 '22 at 16:31
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
  • 95,083
  • 20
  • 220
  • 214
Arthur Edelstein
  • 1,567
  • 1
  • 10
  • 11
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))))
Arthur Ulfeldt
  • 90,827
  • 27
  • 201
  • 284
  • 1
    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. – Thomas Nov 04 '09 at 23:31
  • 2
    Are we guaranteed that keys and vals return the corresponding values in the same order? For both sorted maps and hash maps? – Rob Lachlan Nov 05 '09 at 07:22
  • 5
    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. – Chouser Nov 05 '09 at 14:25
  • 2
    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. – Jouni K. Seppänen Mar 29 '11 at 10:40
  • The function example should have `(vals m)` – AnnanFay Jan 17 '12 at 00:26
  • @JouniK.Seppänen, I share your concern and in fact it is documented in vals and keys that the order is the same as seq. Doc for `vals`: "Returns a sequence of the map's values, in the same order as (seq map)." Doc for `keys`: "Returns a sequence of the map's keys, in the same order as (seq map)." The links where I got those are https://clojuredocs.org/clojure.core/vals and https://clojuredocs.org/clojure.core/keys, respectively. – Jason Feb 09 '16 at 15:36
  • 2
    @Jason Yeah, they added this to the documentation in version 1.6 I think. http://dev.clojure.org/jira/browse/CLJ-1302 – Jouni K. Seppänen Feb 10 '16 at 06:59
  • Typo in the first code snippet: one paren after `do-stuff %)` should be moved to the end of the line. – Ahmed Fasih Jan 05 '17 at 00:14
  • Thanks @AhmedFasih that mistake has been sitting there for more then 7 years! (And you are free to edit these things directly as well) – Arthur Ulfeldt Jan 05 '17 at 00:37
  • Thank you, got it! My thinking for not making the edit directly was this: it’d go to peer review, and most would feel uncomfortable reviewing such a subtle change in an admittedly unusual language—some might suspect I was vandalizing… So I pinged you if you hadn’t responded, I’d probably have risked an edit ! – Ahmed Fasih Jan 05 '17 at 00:43
25

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

(defn map-kv [m f]
  (reduce-kv #(assoc %1 %2 (f %3)) {} m))
roboli
  • 1,418
  • 20
  • 24
22

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}
Rory O'Kane
  • 29,210
  • 11
  • 96
  • 131
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}
Siddhartha Reddy
  • 6,130
  • 1
  • 33
  • 20
  • 2
    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)*. – Siddhartha Reddy Nov 05 '09 at 05:24
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) )
Rory O'Kane
  • 29,210
  • 11
  • 96
  • 131
  • 1
    Also check prismatics map-vals function. It's faster using transients https://github.com/Prismatic/plumbing/blob/4b01888d23cd99e02a32c61ee6ff23b22edcfcfd/src/plumbing/core.clj#L33 – ClojureMostly Feb 09 '14 at 21:36
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 .

Carl Smotricz
  • 66,391
  • 18
  • 125
  • 167
  • 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 :) – Thomas Nov 04 '09 at 23:29
  • 2
    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. – Siddhartha Reddy Nov 05 '09 at 05:21
  • Oops! Thanks for the tip, Siddhartha! – Carl Smotricz Nov 05 '09 at 05:33
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)))
Didier A.
  • 4,609
  • 2
  • 43
  • 45
1

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

olange
  • 165
  • 2
  • 7
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.

mzuther
  • 1,158
  • 1
  • 13
  • 24
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"}
Jack Pendleton
  • 171
  • 1
  • 6