How to Iterate over Map Keys and Values in Clojure?

Clojure

Clojure Problem Overview


I have the following map which I want to iterate:

(def db {:classname "com.mysql.jdbc.Driver" 
         :subprotocol "mysql" 
         :subname "//100.100.100.100:3306/clo" 
         :username "usr" :password "pwd"})

I've tried the following, but rather than printing the key and value once, it repeatedly prints the key and values as various combinations:

(doseq [k (keys db) 
        v (vals db)] 
  (println (str k " " v)))

I came up with a solution, but Brian's (see below) are much more logical.

(let [k (keys db) v (vals db)] 
  (do (println (apply str (interpose " " (interleave k v))))))

Clojure Solutions


Solution 1 - Clojure

That's expected behavior. (doseq [x ... y ...]) will iterate over every item in y for every item in x.

Instead, you should iterate over the map itself once. (seq some-map) will return a list of two-item vectors, one for each key/value pair in the map. (Really they're clojure.lang.MapEntry, but behave like 2-item vectors.)

user> (seq {:foo 1 :bar 2})
([:foo 1] [:bar 2])

doseq can iterate over that seq just like any other. Like most functions in Clojure that work with collections, doseq internally calls seq on your collection before iterating over it. So you can simply do this:

user> (doseq [keyval db] (prn keyval))
[:subprotocol "mysql"]
[:username "usr"]
[:classname "com.mysql.jdbc.Driver"]
[:subname "//100.100.100.100:3306/clo"]
[:password "pwd"]

You can use key and val, or first and second, or nth, or get to get the keys and values out of these vectors.

user> (doseq [keyval db] (prn (key keyval) (val keyval)))
:subprotocol "mysql"
:username "usr"
:classname "com.mysql.jdbc.Driver"
:subname "//100.100.100.100:3306/clo"
:password "pwd"

More concisely, you can use destructuring to bind each half of the map entries to some names that you can use inside the doseq form. This is idiomatic:

user> (doseq [[k v] db] (prn k v))
:subprotocol "mysql"
:username "usr"
:classname "com.mysql.jdbc.Driver"
:subname "//100.100.100.100:3306/clo"
:password "pwd"

Solution 2 - Clojure

You can simply do

(map (fn [[k v]] (prn k) (prn v)) {:a 1 :b 2})

The result is:

:a
1
:b
2

Is this what you were looking for?

Solution 3 - Clojure

Just a short addition to Brian's answer:

Your original version could also be written as follows.

(doseq [[k v] (map vector (keys db) (vals db))]
  (println (str k " " v)))

In this case this is obviously silly. But in general this works also for unrelated input sequences, which do not stem from the same map.

Solution 4 - Clojure

It's not totally clear if you are trying to solve something beyond just printing out values (side effects), and if that's all you're going for then I think the doseq solution above would be the most idiomatic. If you want to do some operations on the keys and values of the map and return the same type of data structure, then you should have a look at reduce-kv, for which you can find the docs for here

Like reduce, reduce-kv accepts a function, a starting value/accumulator, and some data, but in this case the data is a map instead of a sequence. The function gets passed three args: the accumulator, current key, and current value. If you do want to do some data transformation and return some data, this would seem like the right tool for the job to me.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionsjacView Question on Stackoverflow
Solution 1 - ClojureBrian CarperView Answer on Stackoverflow
Solution 2 - ClojureMrinal SaurabhView Answer on Stackoverflow
Solution 3 - ClojurekotarakView Answer on Stackoverflow
Solution 4 - ClojurerdgdView Answer on Stackoverflow