(defn do-to-map [amap keyseq f]
(reduce #(assoc %1 %2 (f (%1 %2))) amap keyseq))
Breakdown:
It helps to look at it inside-out. In Clojure, hash-maps act like functions; if you call them like a function with a key as an argument, the value associated with that key is returned. So given a single key, the current value for that key can be obtained via:
(some-map some-key)
We want to take old values, and change them to new values by calling some function f
on them. So given a single key, the new value will be:
(f (some-map some-key))
We want to associate this new value with this key in our hash-map, "replacing" the old value. This is what assoc
does:
(assoc some-map some-key (f (some-map some-key)))
("Replace" is in scare-quotes because we're not mutating a single hash-map object; we're returning new, immutable, altered hash-map objects each time we call assoc
. This is still fast and efficient in Clojure because hash-maps are persistent and share structure when you assoc
them.)
We need to repeatedly assoc
new values onto our map, one key at a time. So we need some kind of looping construct. What we want is to start with our original hash-map and a single key, and then "update" the value for that key. Then we take that new hash-map and the next key, and "update" the value for that next key. And we repeat this for every key, one at a time, and finally return the hash-map we've "accumulated". This is what reduce
does.
- The first argument to
reduce
is a function that takes two arguments: an "accumulator" value, which is the value we keep "updating" over and over; and a single argument used in one iteration to do some of the accumulating.
- The second argument to
reduce
is the initial value passed as the first argument to this fn
.
- The third argument to
reduce
is a collection of arguments to be passed as the second argument to this fn
, one at a time.
So:
(reduce fn-to-update-values-in-our-map
initial-value-of-our-map
collection-of-keys)
fn-to-update-values-in-our-map
is just the assoc
statement from above, wrapped in an anonymous function:
(fn [map-so-far some-key] (assoc map-so-far some-key (f (map-so-far some-key))))
So plugging it into reduce
:
(reduce (fn [map-so-far some-key] (assoc map-so-far some-key (f (map-so-far some-key))))
amap
keyseq)
In Clojure, there's a shorthand for writing anonymous functions: #(...)
is an anonymous fn
consisting of a single form, in which %1
is bound to the first argument to the anonymous function, %2
to the second, etc. So our fn
from above can be written equivalently as:
#(assoc %1 %2 (f (%1 %2)))
This gives us:
(reduce #(assoc %1 %2 (f (%1 %2))) amap keyseq)