constructing maps

59 views
Skip to first unread message

Nathan Hawkins

unread,
May 4, 2009, 8:23:18 AM5/4/09
to clo...@googlegroups.com
Possibly I'm going about this wrong. I'm trying to understand how best
to construct maps from sequences, by applying a function which returns a
key / value pair.

Something like this:

(ns test (:use clojure.contrib.str-utils))

(def test-str "foo=1;bar=2;baz=3")

(defn split-kv [text]
(let [[k v] (re-split #"=" text )]
{k v}))

(defn split-pairs [text]
(re-split #";" text))

(map split-kv (split-pairs test-str))

-> ({"foo" "1"} {"bar" "2"} {"baz" "3"})


Doesn't really do what I had in mind. And yeah, I figured out that I can
convert that to a hash-map in various ways, but I had expected map to be
able to do this. So I wrote this:


(defn map-assoc
"Returns a map consisting of f applied to coll, where f is a function
returning a key and value. f can return either a sequence with two
values or a single item map."
[f coll]
(loop [map {}
s (seq coll)]
(if s
(let [item (f (first s))]
(recur (if (associative? item)
(conj map item)
(assoc map (first item) (second item)))
(next s)))
map)))


This seems a little bit more like what I expected:

(map-assoc split-kv (split-pairs test-str))

-> {"baz" "3", "bar" "2", "foo" "1"}


Am I overlooking some already existing function hidden away someplace
that does this?

Konrad Hinsen

unread,
May 4, 2009, 9:03:08 AM5/4/09
to clo...@googlegroups.com
On May 4, 2009, at 14:23, Nathan Hawkins wrote:

> This seems a little bit more like what I expected:
>
> (map-assoc split-kv (split-pairs test-str))
>
> -> {"baz" "3", "bar" "2", "foo" "1"}
>
>
> Am I overlooking some already existing function hidden away someplace
> that does this?

Here's one way to do what you want:


(ns test (:use clojure.contrib.str-utils))

(def test-str "foo=1;bar=2;baz=3")

(defn split-kv [text]
(vec (re-split #"=" text)))

(defn split-pairs [text]
(re-split #";" text))

(apply conj {} (map split-kv (split-pairs test-str)))


Konrad.

Drew Raines

unread,
May 4, 2009, 9:05:00 AM5/4/09
to clo...@googlegroups.com
Nathan Hawkins wrote:

> Possibly I'm going about this wrong. I'm trying to understand how
> best to construct maps from sequences, by applying a function which
> returns a key / value pair.

[...]

> Am I overlooking some already existing function hidden away someplace
> that does this?

Here's a more succinct way:

user> (let [test-str "foo=1;bar=2;baz=3"]
(reduce conj {}
(map #(apply hash-map (seq (.split % "=")))
(.split test-str ";"))))

{"baz" "3", "bar" "2", "foo" "1"}

-Drew

Drew Raines

unread,
May 4, 2009, 9:16:14 AM5/4/09
to Clojure
On May 4, 8:05 am, Drew Raines <aarai...@gmail.com> wrote:

> user> (let [test-str "foo=1;bar=2;baz=3"]
>         (reduce conj {}
>            (map #(apply hash-map (seq (.split % "=")))
>                (.split test-str ";"))))

Whoops, that (seq) is a debugging artifact. You can remove that:

(let [test-str "foo=1;bar=2;baz=3"]
(reduce conj {}
(map #(apply hash-map (.split % "="))
(.split test-str ";"))))

-Drew

mikel

unread,
May 4, 2009, 9:35:34 AM5/4/09
to Clojure
user> (apply hash-map (clojure.contrib.str-utils/re-split #"[=;]"
"foo=1;bar=2;baz=3"))
{"foo" "1", "bar" "2", "baz" "3"}

This solution makes a lot of assumptions about the input data, of
course.

Nathan Hawkins

unread,
May 4, 2009, 9:47:14 AM5/4/09
to clo...@googlegroups.com

Ok, my example seems to have misled. You're missing the point a little
bit:

1. I was trying to avoid the (reduce conj {} ...), by having the map
function do it. Why even build a list that's only going to get thrown
away when I want a hash-map at the end?

2. The functions used to split the strings were not important, only an
example. It could just as easily be a function to extract fields from a
java object.


To some extent, I guess I'm thinking in terms of Common Lisp, where I'd
build an a-list with mapcar and cons.

Nathan

Laurent PETIT

unread,
May 4, 2009, 9:50:58 AM5/4/09
to clo...@googlegroups.com
2009/5/4 Nathan Hawkins <uts...@gmail.com>:

>
> Possibly I'm going about this wrong. I'm trying to understand how best
> to construct maps from sequences, by applying a function which returns a
> key / value pair.
>
> Something like this:
>
> (ns test (:use clojure.contrib.str-utils))
>
> (def test-str "foo=1;bar=2;baz=3")
>
> (defn split-kv [text]
>  (let [[k v] (re-split #"=" text )]
>   {k v}))
>
> (defn split-pairs [text]
>  (re-split #";" text))
>
> (map split-kv (split-pairs test-str))
>
> -> ({"foo" "1"} {"bar" "2"} {"baz" "3"})
>
>
> Doesn't really do what I had in mind. And yeah, I figured out that I can
> convert that to a hash-map in various ways, but I had expected map to be
> able to do this.

Hi, no, maybe you make a confusion with the word "map" as in
"hash-map" and as in "mapping". The latter is the meaning for the map
function: mapping a sequence to another sequence (and the result will
always be a sequence).

Christopher Taylor

unread,
May 4, 2009, 10:07:06 AM5/4/09
to clo...@googlegroups.com
Hi Nathan,

On 04.05.2009, at 15:47, Nathan Hawkins wrote:

>
> On Mon, 4 May 2009 06:16:14 -0700 (PDT)
> Drew Raines <aara...@gmail.com> wrote:
>>
>> Whoops, that (seq) is a debugging artifact. You can remove that:
>>
>> (let [test-str "foo=1;bar=2;baz=3"]
>> (reduce conj {}
>> (map #(apply hash-map (.split % "="))
>> (.split test-str ";"))))
>
> Ok, my example seems to have misled. You're missing the point a little
> bit:
>
> 1. I was trying to avoid the (reduce conj {} ...), by having the map
> function do it. Why even build a list that's only going to get thrown
> away when I want a hash-map at the end?

you're not actually building a list. The function map returns a (lazy)
*sequence*, which is an instance of ISeq. This just means you're
getting something that supports the operations first and rest. So,
since map returns a sequence and you want a Map (i.e. key/value data
structure), you'll have to turn it into one by using (conj {} ...) or
(into {} ...).

hth,
--Chris

Nathan Hawkins

unread,
May 4, 2009, 10:26:55 AM5/4/09
to clo...@googlegroups.com

There's the source of my misunderstanding. I knew map returned a
sequence, but hadn't quite connected "lazy" to the problem at hand.

I had stumbled on a couple different ways of converting the sequence to
a hash-map (I found (into {} ...), (apply hash-map ...) and (reduce conj
{} ...)), but I was thinking in terms of how I'd solve the problem in
Common Lisp so I thought that running map and then converting the
result to a hash-map was going to iterate the list twice, as well as
cons the results twice.

Thanks for clearing that up.

Nathan

Christophe Grand

unread,
May 4, 2009, 10:31:21 AM5/4/09
to clo...@googlegroups.com
Nathan Hawkins a écrit :

> Ok, my example seems to have misled. You're missing the point a little
> bit:
>
> 1. I was trying to avoid the (reduce conj {} ...), by having the map
> function do it. Why even build a list that's only going to get thrown
> away when I want a hash-map at the end?
>
> 2. The functions used to split the strings were not important, only an
> example. It could just as easily be a function to extract fields from a
> java object.
>
>
> To some extent, I guess I'm thinking in terms of Common Lisp, where I'd
> build an a-list with mapcar and cons.
>

With f a function that return a [key value] pair (or a (key value) pair
but not a {key value} pair):
(reduce #(apply assoc %1 (f %2)) {} coll)

if you want to have f return a map you can
(reduce #(merge %1 (f %2)) {} coll)

--
Professional: http://cgrand.net/ (fr)
On Clojure: http://clj-me.blogspot.com/ (en)


Nathan Hawkins

unread,
May 4, 2009, 12:38:36 PM5/4/09
to clo...@googlegroups.com

This is exactly what I was trying to, but I hadn't thought of using
reduce.

Thank you.

Nathan

Michel S.

unread,
May 4, 2009, 4:49:25 PM5/4/09
to Clojure
The last can be simplified by using into:
(into {} (map split-kv (split-pairs test-str)))

--
Michel

Christophe Grand

unread,
May 4, 2009, 5:07:35 PM5/4/09
to clo...@googlegroups.com
Michel S. a écrit :

>> (apply conj {} (map split-kv (split-pairs test-str)))
>>
>>
> The last can be simplified by using into:
> (into {} (map split-kv (split-pairs test-str)))
>

It should be noted that into (and conj) is somewhat tricky with maps:
user=> (into {} '({1 2} {3 4}))
{3 4, 1 2}
user=> (into {} '([1 2] [3 4]))
{3 4, 1 2}
user=> (into {} '((1 2) (3 4)))
java.lang.ClassCastException: java.lang.Integer cannot be cast to
java.util.Map$Entry (NO_SOURCE_FILE:0)

It bit me before.

Christophe

Michel S.

unread,
May 4, 2009, 7:03:28 PM5/4/09
to Clojure


On May 4, 5:07 pm, Christophe Grand <christo...@cgrand.net> wrote:
> Michel S. a écrit :
>
> >> (apply conj {} (map split-kv (split-pairs test-str)))
>
> > The last can be simplified by using into:
> > (into {} (map split-kv (split-pairs test-str)))
>
> It should be noted that into (and conj) is somewhat tricky with maps:
> user=> (into {} '({1 2} {3 4}))
> {3 4, 1 2}
> user=> (into {} '([1 2] [3 4]))
> {3 4, 1 2}
> user=> (into {} '((1 2) (3 4)))
> java.lang.ClassCastException: java.lang.Integer cannot be cast to
> java.util.Map$Entry (NO_SOURCE_FILE:0)
>
> It bit me before.
>
Yes, me too. The design is a bit unfortunate, as I cannot convert a
Scheme-style association list to a map easily:

user=> (into {} '((cars bmw chevrolet ford peugeot)
(genres adventure horror mystery)))

java.lang.ClassCastException: clojure.lang.Symbol cannot be cast to
java.util.Map$Entry (NO_SOURCE_FILE:0)

--
Michel

Kevin Downey

unread,
May 5, 2009, 2:19:11 AM5/5/09
to clo...@googlegroups.com
(into {} (apply map vector
'((cars bmw chevrolet ford peugeot)
(genres adventure horror mystery))))

{ford mystery, chevrolet horror, bmw adventure, cars genres}
--
And what is good, Phaedrus,
And what is not good—
Need we ask anyone to tell us these things?

Christophe Grand

unread,
May 5, 2009, 3:39:21 AM5/5/09
to clo...@googlegroups.com
Kevin Downey a écrit :

> (into {} (apply map vector
> '((cars bmw chevrolet ford peugeot)
> (genres adventure horror mystery))))
>
> {ford mystery, chevrolet horror, bmw adventure, cars genres}
>

or:
user=> (apply zipmap '((cars bmw chevrolet ford peugeot) (genres
adventure horror mystery)))

{ford mystery, chevrolet horror, bmw adventure, cars genres}

But I'm unsure it's what Michel was after. I thought an alist was a list
of pointed pairs (or a list of 2-elts lists) but I may be wrong since I
come from Javaland.

user=> (into {} (map vec '((a 1) (b 2) (c 3))))
{c 3, b 2, a 1}


Christophe


>
> On Mon, May 4, 2009 at 4:03 PM, Michel S. <michel...@gmail.com> wrote:
>
>>
>> Yes, me too. The design is a bit unfortunate, as I cannot convert a
>> Scheme-style association list to a map easily:
>>
>> user=> (into {} '((cars bmw chevrolet ford peugeot)
>> (genres adventure horror mystery)))
>>
>> java.lang.ClassCastException: clojure.lang.Symbol cannot be cast to
>> java.util.Map$Entry (NO_SOURCE_FILE:0)
>>
>> --
>> Michel
>>


--

Nathan Hawkins

unread,
May 5, 2009, 10:08:41 AM5/5/09
to clo...@googlegroups.com


In Common Lisp, an alist is something like this:

((cars . genres) (bmw . adventure) (chevrolet . horror) (ford .
mystery))

Where (cars . genres) is a cons cell with values in both slots. So an
alist is a list of key/value pairs where order is significant and keys
could appear more than once. Not much different than:

[[cars genres] [bmw adventure] [chevrolet horror] [ford mystery]]

Reply all
Reply to author
Forward
0 new messages