tags:

views:

945

answers:

5

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

+1  A: 

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
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
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
Oops! Thanks for the tip, Siddhartha!
Carl Smotricz
+3  A: 

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 data)))
Arthur Ulfeldt
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
Are we guaranteed that keys and vals return the corresponding values in the same order? For both sorted maps and hash maps?
Rob Lachlan
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
+8  A: 

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
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
I agree. I didn't know the into function, but it makes perfect sense using it here.
Thomas
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
Is `into` lazy?
nilamo
Nope. Neither are `hash-map` or `zipmap` or `merge` or `reduce`.
Brian Carper
+1  A: 

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
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
+1  A: 

You can use the fmap function from clojure.contrib.generic.functor:

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