Meet my little friend mapmap

3

I seem to chronically run into cases where I want to create a map in Clojure but don’t have just the right tool at hand. One nice function that I’ve found to be helpful is my little friend mapmap. mapmap is probably not a very good name – I’m open to suggestions. Maybe mapmerge would be better. It’s kind of a weird mix of zipmap, merge, and mapcat.

Here’s the code:

(defn mapmap
  "Apply kf and vf to a sequence, s, and produce a map of (kf %) to (vf %)."
  ([vf s]
     (mapmap identity vf s))
  ([kf vf s]
     (zipmap (map kf s)
              (map vf s))))

This function takes a sequence, and in the three-arg form applies a key function to the sequence, a value function to the values, and zips them together as a map. In the two-arg form, identity is used as the key function so the keys are the original sequence.

Two-arg example:

user> (mapmap #(* 10 %) [1 2 3 4 5])
{5 50, 4 40, 3 30, 2 20, 1 10}
user> (mapmap born-in [ "Chuck Berry" "Ted Nugent" ])
{"Ted Nugent" "Detroit", "Chuck Berry" "St. Louis"}

Three-arg example:

user> (mapmap #(+ 1 %) #(* 10 %) [1 2 3 4])
{5 40, 4 30, 3 20, 2 10}

And I’ve found it useful to use mapmap over a map as a sequence of map entries, for example to take a map and manipulate either the keys and/or the values of the map.

user> (mapmap #(subs (key %) 0 2)
                      (comp name val)
                      { "abcd" :x, "foobar" :y, "nachos" :z })
{"na" "z", "fo" "y", "ab" "x"}

Or if for example you wanted to swap the keys and values in a map:

user> (mapmap val key { :a 1 :b 2 :c 3 })
{3 :c 2 :b 1 :a}

What do you think?

Comments

3 Responses to “Meet my little friend mapmap”
  1. Jürgen Hötzel says:

    Good post! Inspired me to a generic “Map-mapping” function:

    http://gist.github.com/596728

  2. Alex says:

    Just happened across an example of where mapmap could be used inside clojure core in the protocol implementation:

    (defn- emit-impl [[p fs]]
    [p (zipmap (map #(-> % first keyword) fs)
    (map #(cons 'fn (drop 1 %)) fs))])

    The zipmap over two maps on the same sequence makes mapmap a perfect choice here:

    (defn- emit-impl [[p fs]]
    [p (mapmap #(-> % first keyword)
    #(cons 'fn (drop 1 %))
    fs)])

  3. What do you think about making mapmap two different functions to improve the naming and usage?

    (defn map-keys
    “transform a map by applying a function to its keys
    and \”dragging along\” its values. the transformation
    function must be one-to-one for the keys of the map
    and its range or else the resulting map would have more than
    one value for a key”
    [f m]
    (zipmap (map f (keys m)) (vals m)))

    (defn map-vals
    “transform a map by applying a function to its values, keeping the
    keys the same.”
    [f m]
    (zipmap (keys m) (map f (vals m))))

    It also immediately suggests these functions.

    (defn filter-keys
    “makes a new map which contains only pairs for which
    the filter function on the key of the pair returns true.
    Preserves whatever meta-data the original map had.”
    [fun m]
    (select-keys m (filter fun (keys m))))

    (defn filter-vals
    “makes a new map which contains only pairs for which
    the filter function on the value of the pair returns true.
    Preserves whatever meta-data the original map had.”
    [fun m]
    (into {} (filter (comp fun val) m)))

Speak Your Mind

Tell us what you're thinking...
and oh, if you want a pic to show with your comment, go get a gravatar!