mapmap

144 views
Skip to first unread message

C. Florian Ebeling

unread,
Dec 17, 2009, 9:37:58 AM12/17/09
to clo...@googlegroups.com
I was just wondering how a #'map for maps could be done most
succinctly. Came up with this:

(defn mapmap
"Map values of map m using function f."
[f m]
(reduce (fn [m [k v]]
(assoc m k (f v))) {} m))

But there is probably a more straightforward way. What do you use?

Florian

--
Florian Ebeling
florian...@gmail.com

Sean Devlin

unread,
Dec 17, 2009, 9:44:33 AM12/17/09
to Clojure
1. I'd call it map-vals (as opposed to map-keys)

2. Slight change in the body

(defn map-vals [f coll]
(into {} (map (juxt key (comp f val)) coll))

There is talk of getting something like this into contrib. Stay
tuned :)

Sean

On Dec 17, 9:37 am, "C. Florian Ebeling" <florian.ebel...@gmail.com>
wrote:

> florian.ebel...@gmail.com

Laurent PETIT

unread,
Dec 17, 2009, 9:46:22 AM12/17/09
to clo...@googlegroups.com
AFAIK, such a function is still not in clojure core because it's not clear to Rich whether f should have one argument (the value), or 2 arguments (key + value).

Your version seems good, you can maybe improve it a little bit by using transients (not tested):

(persistent!
  (reduce (fn [m [k v]]
           (assoc! m k (f v))) (transient {}) m))

HTH,

--
Laurent

2009/12/17 C. Florian Ebeling <florian...@gmail.com>
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@googlegroups.com
Note that posts from new members are moderated - please be patient with your first post.
To unsubscribe from this group, send email to
clojure+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

C. Florian Ebeling

unread,
Dec 17, 2009, 10:24:32 AM12/17/09
to clo...@googlegroups.com
> AFAIK, such a function is still not in clojure core because it's not clear
> to Rich whether f should have one argument (the value), or 2 arguments (key
> + value).

That is a good question. But I thought if the key makes a difference,
then one could use standard map and rely on map's collection nature,
using into {} afterwards.

My reasoning was that map does not care about the index of an element
of collection either. So the key should not count in. But there are
valid reasons for the opposite decision as well, I guess.

Stuart Sierra

unread,
Dec 17, 2009, 11:17:54 AM12/17/09
to Clojure
On Dec 17, 9:37 am, "C. Florian Ebeling" <florian.ebel...@gmail.com>
wrote:
> (defn mapmap
>   "Map values of map m using function f."
>   [f m]
>   (reduce (fn [m [k v]]
>             (assoc m k (f v))) {} m))
>
> But there is probably a more straightforward way. What do you use?


I do that exactly, but I never felt the need to define a new function
for it. The advantage of "reduce" is that you can change the keys
too, and pick which keys end up in the final map.

-SS

Christophe Grand

unread,
Dec 17, 2009, 12:00:59 PM12/17/09
to Clojure
Hi Laurent,

If you want to use transients, since the keyset is unchanged, it
should perform slightly better when using (transient m) instead of
(transient {}) as the seed to reduce.

Christophe

On Dec 17, 3:46 pm, Laurent PETIT <laurent.pe...@gmail.com> wrote:
> AFAIK, such a function is still not in clojure core because it's not clear
> to Rich whether f should have one argument (the value), or 2 arguments (key
> + value).
>
> Your version seems good, you can maybe improve it a little bit by using
> transients (not tested):
>
> (persistent!
>   (reduce (fn [m [k v]]
>            (assoc! m k (f v))) (transient {}) m))
>
> HTH,
>
> --
> Laurent
>

> 2009/12/17 C. Florian Ebeling <florian.ebel...@gmail.com>


>
> > I was just wondering how a #'map for maps could be done most
> > succinctly. Came up with this:
>
> > (defn mapmap
> >  "Map values of map m using function f."
> >  [f m]
> >  (reduce (fn [m [k v]]
> >            (assoc m k (f v))) {} m))
>
> > But there is probably a more straightforward way. What do you use?
>
> > Florian
>
> > --
> > Florian Ebeling

> > florian.ebel...@gmail.com


>
> > --
> > You received this message because you are subscribed to the Google
> > Groups "Clojure" group.
> > To post to this group, send email to clo...@googlegroups.com
> > Note that posts from new members are moderated - please be patient with
> > your first post.
> > To unsubscribe from this group, send email to

> > clojure+u...@googlegroups.com<clojure%2Bunsu...@googlegroups.com>

Konrad Hinsen

unread,
Dec 17, 2009, 12:16:49 PM12/17/09
to clo...@googlegroups.com
On 17 Dec 2009, at 15:44, Sean Devlin wrote:

> 1. I'd call it map-vals (as opposed to map-keys)
>
> 2. Slight change in the body
>
> (defn map-vals [f coll]
> (into {} (map (juxt key (comp f val)) coll))
>
> There is talk of getting something like this into contrib. Stay
> tuned :)

There's already clojure.contrib.generic.functor/fmap, which is a
multimethod whose implementation for maps does just that. Here's the
code:

(defmethod fmap clojure.lang.IPersistentMap
[f m]
(into (empty m) (for [[k v] m] [k (f v)])))

Konrad.

Erik Price

unread,
Dec 17, 2009, 12:39:53 PM12/17/09
to clo...@googlegroups.com
On Thu, Dec 17, 2009 at 12:16 PM, Konrad Hinsen
<konrad...@fastmail.net> wrote:
>
> On 17 Dec 2009, at 15:44, Sean Devlin wrote:

> > (defn map-vals [f coll]
> >  (into {} (map (juxt key (comp f val)) coll))

vs:

> (defmethod fmap clojure.lang.IPersistentMap
>   [f m]
>   (into (empty m) (for [[k v] m] [k (f v)])))

Are there any guidelines as to when it's appropriate to use the map
function vs a list comprehension?

e

Sean Devlin

unread,
Dec 17, 2009, 2:26:24 PM12/17/09
to Clojure
Konrad,
I am working on a different proposal on the dev list:

http://groups.google.com/group/clojure-dev/browse_thread/thread/9a518c853bfbba8b#

This is a more in depth version of these functions for maps. I'd love
to have you contribute to the discussion here.

Sean

Sean Devlin

unread,
Dec 17, 2009, 2:28:34 PM12/17/09
to Clojure
Depends if you need a closure. There are times I do 2D mappings, and
I have to write something like

(map (partail map #(do-work %)) coll).

Or I have to compose my mapping operations.

for is awesome when you need some of the other built-ins, like :while

Sean

On Dec 17, 12:39 pm, Erik Price <erikpr...@gmail.com> wrote:
> On Thu, Dec 17, 2009 at 12:16 PM, Konrad Hinsen
>

Laurent PETIT

unread,
Dec 17, 2009, 2:35:23 PM12/17/09
to clo...@googlegroups.com
Good to know, thank you !

2009/12/17 Christophe Grand <chris...@cgrand.net>

Avital Oliver

unread,
Dec 17, 2009, 4:11:23 PM12/17/09
to clo...@googlegroups.com
Erik,

My experience is that you should only use filter/map if you either have a pre-existing function or you want to use a _very_ short closure (using #()). For any other case the code becomes unmanageable and then I feel that using for is the wiser choice.

I hope that they are just as efficient, but I haven't really tested it. I feel like even if it isn't right now they can be brought up to the same speed.

--
http://wave.theWE.net
http://twitter.com/theWE_




e

Alex Osborne

unread,
Dec 17, 2009, 9:46:20 PM12/17/09
to clo...@googlegroups.com
Avital Oliver <avi...@thewe.net> writes:

> My experience is that you should only use filter/map if you either have a
> pre-existing function or you want to use a _very_ short closure (using #()).
> For any other case the code becomes unmanageable and then I feel that using for
> is the wiser choice.

I agree, but then again I've been criticized for writing "too bulky"
functions so perhaps it's better to extract the conditions and the body
as separate functions where possible. I guess the other advantage of map
is that is more easily replaced with 'pmap'.

> I hope that they are just as efficient, but I haven't really tested it. I feel
> like even if it isn't right now they can be brought up to the same speed.

In theory 'for' is slightly more efficient than a filter/map combo as it
avoids one lazy sequence, but for almost all purposes the difference is
negligible and not worth bothering over.

Konrad Hinsen

unread,
Dec 18, 2009, 3:50:25 AM12/18/09
to clo...@googlegroups.com
On 17 Dec 2009, at 20:26, Sean Devlin wrote:

> Konrad,
> I am working on a different proposal on the dev list:
>
> http://groups.google.com/group/clojure-dev/browse_thread/thread/9a518c853bfbba8b#
>
> This is a more in depth version of these functions for maps. I'd love
> to have you contribute to the discussion here.

I vaguely remember that discussion, but at the time I was too busy
with other things to look at it. Now I did, but I don't think I have
much to contribute. The way maps are used in the code examples of that
thread just doesn't occur in the applications I have for Clojure.

Just one general remark: I don't think it is a good idea to structure
libraries around Clojure datatypes, as it is implied by a library name
such as map-utils. Clojure's built-in data types can implement many
different data abstractions, and I'd prefer to structure libraries
around those abstractions rather than around the concrete
representations. BTW, this is also the reason why I like multimethods
and protocols, as very often an abstraction can have multiple
practically useful implementations.

Let's take maps as an example. A map can represent a table, or a small
database. A map can also represent a function defined on a finite set
of arguments. The operations used on maps are likely to be different
for those two use-cases, and it is even quite possible that the same
operation should have a different name for each application. So I'd
rather see a library table-utils and another library discrete-function-
utils. The latter should also provide implementations of its functions
for vectors, which can represent functions defined on a finite range
of integers.

The discussion in the thread you cite turns mostly around using maps
to represent tables. As I said, I don't have much use for this, so my
only recommendation is to call it table-utils rather than map-utils
and to envisage altenative representations, such as on-disk databases.

Konrad.

Sean Devlin

unread,
Dec 18, 2009, 9:03:51 AM12/18/09
to Clojure
Konrad,
Yeah, there are discussions of two libs there. The idea is that most
of the fn in this thread will be used for table-utils.

The last entry is the most relevant to a map-utils library. Check out
the visitor stuff:

http://groups.google.com/group/clojure-dev/msg/6c1bbce17cafdf52

The idea is to take your generic functor application idea and put it
on steroids. I'm going to be writing about this a lot more once 1.1
is out the door. A whole lot.

Sean

On Dec 18, 3:50 am, Konrad Hinsen <konrad.hin...@fastmail.net> wrote:
> On 17 Dec 2009, at 20:26, Sean Devlin wrote:
>
> > Konrad,
> > I am working on a different proposal on the dev list:
>

> >http://groups.google.com/group/clojure-dev/browse_thread/thread/9a518...

Konrad Hinsen

unread,
Dec 20, 2009, 5:31:18 AM12/20/09
to clo...@googlegroups.com
On 18 Dec 2009, at 15:03, Sean Devlin wrote:

> The last entry is the most relevant to a map-utils library. Check out
> the visitor stuff:
>
> http://groups.google.com/group/clojure-dev/msg/6c1bbce17cafdf52
>
> The idea is to take your generic functor application idea and put it
> on steroids. I'm going to be writing about this a lot more once 1.1
> is out the door. A whole lot.

The functor abstraction is certainly not my idea, I just wrote the
Clojure implementation, which is almost trivial.

Functors have their origin in mathematics (category theory, see http://en.wikipedia.org/wiki/Functor)
, although the link to functors in functional programming is not as
evident or straightforward as some make it seem to be. Functors are a
well-established and much-used abstraction in Haskell and part of its
standard library. Haskell purists would probably complain that my
Clojure version has nothing to do with the "real thing" because
Clojure doesn't have static typing, but the practical utility of
functors is the same in Clojure as it is in Haskell.

Your visitor approach looks a bit like the data structure walkers in
clojure.walk. Could both be unified into a single concept and library?

Konrad.

lambdatronic

unread,
Dec 28, 2009, 9:13:07 PM12/28/09
to Clojure
I've been using a number of similar functions in my own coding. This
was my approach.

(defn seq2map
"Constructs a map from a sequence by applying keyvalfn to each
element of the sequence. keyvalfn should return a pair [key val]
to be added to the map for each input sequence element."
[aseq keyvalfn]
(loop [aseq aseq
amap {}]
(if (empty? aseq)
amap
(let [[key val] (keyvalfn (first aseq))]
(recur (rest aseq)
(assoc amap key val))))))

(defn seq2redundant-map
"Constructs a map from a sequence by applying keyvalfn to each
element of the sequence. keyvalfn should return a pair [key val]
to be added to the map for each input sequence element. If key is
already in the map, its current value will be combined with the new
val using (mergefn curval val)."
[aseq keyvalfn mergefn]
(loop [aseq aseq
amap {}]
(if (empty? aseq)
amap
(let [[key val] (keyvalfn (first aseq))]
(recur (rest aseq)
(update-in amap [key] mergefn val))))))

(defn maphash
"Creates a new map by applying keyfn to every key of in-map and
valfn to every corresponding val."
[keyfn valfn in-map]
(seq2map (seq in-map) (fn [[key val]] [(keyfn key) (valfn val)])))

~Gary

Reply all
Reply to author
Forward
0 new messages