mehdix
mehdix

Reputation: 5154

How to re-implement `zipmap` in Clojure?

As an exercise I am trying to re-implement zipmap. The following statement works fine, it takes keys and values and turns them into a map:

user=> (into {} (mapv vector [:a :b] [1 2]))
{:a 1, :b 2}

However, I run into problem when I try to turn the above statement into a function:

user=> ((fn [& xs] (into {} (mapv vector xs))) [:a :b] [1 2])
IllegalArgumentException Vector arg to map conj must be a pair 
Clojure.lang.ATransientMap.conj (ATransientMap.java:37)

What is the problem with my implementation and why it happens?

Upvotes: 0

Views: 260

Answers (2)

Gary
Gary

Reputation: 231

The problem is the way you are getting your arguments to your anonymous function

[& xs] will roll the arguments up into a list

so in your non-function version (into {} (mapv vector [:a :b] [1 2])) you're mapping vector over two collections

in your function version you're mapping vector over one collection and basically doing this:

(into {} (mapv vector [[:a :b] [1 2]])) which evaluates to (into {} [[[:a :b] [1 2]]]]) which gives you an error

Possible Solutions:

Since you're trying to re-implement zipmap, why not use the same arg list it uses [keys vals] instead of [& xs] and write your function like this:

((fn [keyz valz] (into {} (mapv vector keyz valz))) [:a :b] [1 2])

You could also unroll the collection yourself:

((fn [& xs] (into {} (mapv vector (first xs) (second xs)))) [:a :b] [1 2])

I think using the explicit arg list is a more precise way of going about it since you're expecting to get two specific collections

Upvotes: 3

Taylor Wood
Taylor Wood

Reputation: 16194

You can make your sample work by using apply to apply xs to mapv vector.

((fn [& xs] (into {} (apply mapv vector xs))) [:a :b] [1 2])

This is due to how variadic arguments are bound to xs. Without apply, you'd essentially be calling it like this (mapv vector [[:a :b] [1 2]]) but you want it to be called like (mapv vector [:a :b] [1 2]).

Upvotes: 4

Related Questions