Reputation: 1887
I want to write a function in clojure which would return a map give a string of the form "key1$value1,key2$value2". I came up with this.
(defn get-map
"Returns a map of key value pairs from a string formatted in the form 'key1$value1,key2$value2'"
[line]
(let [result {}]
(for [item (split line #",")]
(let [pair (split item #"\$")]
(assoc result (nth pair 0)
(if (= (.size pair) 2) (nth pair 1) ""))))))
Although it works the only problem with this code is that it returns the map inside a list.
=>(get-map "key1$value1,key2,value2")
({"key1" "value1"} {"key2" "value2"})
I tried to return the result as the last expression of the first let form but then the result is an empty map.
(defn get-map
"Returns a map of key value pairs from a string formatted in the form 'key1$value1,key2$value2'"
[line]
(let [result {}]
(for [item (split line #",")]
(let [pair (split item #"\$")]
(assoc result (nth pair 0)
(if (= (.size pair) 2) (nth pair 1) ""))))
result))
=>(get-map "key1$value1,key2,value2")
{}
Two questions -
Also if you have suggestions to write the same function in a better, more idiomatic clojure way that would be appreciated.
Upvotes: 1
Views: 408
Reputation: 568
In Clojure, for
isn't a loop it's a list comprehension. Basically it takes each item in your
collection and binds it to whatever name you specified (item
in this case) and then evaluates
the expression (let [pair...
in this case). The results of each of those evaluations are returned in a
sequence.
For example:
(for [item (split "key1$value1,key2$value2" #",")]
item)
;returns: ("key1$value1" "key2$value2")
In your function it's for
that actually creates the list. Names bound by let
are immutable
so each time for
evaluates its expression result
is still an empty map. That's why you're getting
a list of maps with a single entry in each one.
Here's a way to produce a list of key-value pairs that uses the not-found capability of nth
to provide a default if the $ is missing:
(for [item (split "key1$value1,key2$value2,key3" #",")]
(let [pair (split item #"\$")]
[(nth pair 0) (nth pair 1 "")]))
;returns: (["key1" "value1"] ["key2" "value2"] ["key3" ""])
Then you can use into
to turn that into a map. Here's the complete function:
(defn get-map
"Returns a map of key value pairs from a string formatted in the form 'key1$value1,key2$value2'"
[line]
(into {}
(for [item (split line #",")]
(let [pair (split item #"\$")]
[(nth pair 0) (nth pair 1 "")]))))
Upvotes: 2
Reputation: 26446
The for
form does lazy list comprehension, so is not appropriate for sequentially updating a structure. You can use for
to produce your list of pairs, and reduce
to package them into a map. Or, use into
, which does the reduction:
(defn get-map [line]
(into {}
(for [item (split line #",")]
(split item #"\$"))))
Another possible implementation using re-seq
:
(defn get-map [s]
(into {} (map #(subvec % 1) (re-seq #"([^\$]+)\$([^,]+),*" s))))
Upvotes: 2
Reputation: 34820
(defn get-map
"Returns a map of key value pairs from a string formatted in the form 'key1$value1,key2$value2'"
[line]
(->> (clojure.string/split line #",")
(mapcat #(clojure.string/split % #"\$"))
(apply hash-map)))
user> (get-map "key1$value1,key2$value2")
{"key1" "value1", "key2" "value2"}
Answer/hint on question 1: For always returns a sequence. It is list comprehension in Clojure.
Answer/hint on question 2: Clojure's datastructures are immutable. This means that assoc-ing on an existing map doesn't alter the existing map, but returns a new one.
Upvotes: 2