This is probably straightforward, but I just can't get over it. I have a data structure that is a nested map, like this:
(def m {:1 {:1 2 :2 5 :3 10} :2 {:1 2 :2 50 :3 25} :3 {:1 42 :2 23 :3 4}})
I need to set every m[i][i]=0
. This is simple in non-functional languages, but I cant make it work on Clojure. How is the idiomatic way to do so, considering that I do have a vector with every possible value? (let's call it v
)
doing (map #(def m (assoc-in m [% %] 0)) v)
will work, but using def
inside a function on map
doesn't seems right.
Making m into an atomic version and using swap!
seems better. But not much It also seems to be REALLY slow.
(def am (atom m))
(map #(swap! am assoc-in[% %] 0) v)
What is the best/right way to do that?
UPDATE
Some great answers over here. I've posted a follow-up question here Clojure: iterate over map of sets that is close-related, but no so much, to this one.
You're right that it's bad form to use def
inside a function. It's also bad form to use functions with side-effects (such as swap
) inside map
. Furthemore, map
is lazy, so your map
/swap
attempt won't actually do anything unless it is forced with, e.g., dorun
.
Now that we've covered what not to do, let's take a look at how to do it ;-).
There are several approaches you could take. Perhaps the easiest for someone coming from an imperative paradigm to start with is loop
:
(defn update-m [m v]
(loop [v' v
m' m]
(if (empty? v')
;; then (we're done => return result)
m'
;; else (more to go => process next element)
(let [i (first v')]
(recur (rest v') ;; iterate over v
(assoc-in m' [i i] 0)))))) ;; accumulate result in m'
However, loop
is a relatively low-level construct within the functional paradigm. Here we can note a pattern: we are looping over the elements of v
and accumulating changes in m'
. This pattern is captured by the reduce
function:
(defn update-m [m v]
(reduce (fn [m' i]
(assoc-in m' [i i] 0)) ;; accumulate changes
m ;; initial-value
v)) ;; collection to loop over
This form is quite a bit shorter, because it doesn't need the boiler-plate code the loop
form requires. The reduce
form might not be as easy to read at first, but once you get used to functional code, it will become more natural.
Now that we have update-m
, we can use it to transform maps in the program at large. For example, we can use it to swap!
an atom. Following your example above:
(swap! am update-m v)
Using def
inside a function would certainly not be recommended. The OO way to look at the problem is to look inside the data structure and modify the value. The functional way to look at the problem would be to build up a new data structure that represents the old one with values changed. So we could do something like this
(into {} (map (fn [[k v]] [k (assoc v k 0)]) m))
What we're doing here is mapping over m
in much the same way you did in your first example. But then instead of modifying m
we return a []
tuple of key and value. This list of tuples we can then put back into a map.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With