In addition to commonly known reduce
function Clojure has its specialized version called reduce-kv
. This function is designed to work with associative collections (like map or vector). Here is its signature:
(reduce-kv f init coll)
where:
f
is a 3 arguments function that takes init (or previous) value as a first argument, key as second and the key value as the lastinit
is a base value for a reduce operationcoll
is an associative collection on which reduce operation will work
The final result is computed in such way that first function f
is applied to the init
value and the first key and the first value in the collection. Next f
is applied the the previous result and the second key and value. This algorithm is repeated till all keys and values are processed. If supplied collection has no key value pairs (is empty) then init
value is returned.
Because in Clojure vector is an associative collection (where every value in a vector has corresponding integer value starting from 0
) recuce-kv
function can be used for it.
Let’s look at the few examples:
- Let’s say that for such map:
{:a [1 2 3] :b [5 4 6] :c [20 21 100]}
we want to shuffle vectors corresponding to keys. To do that we can write this code:
(reduce-kv (fn [m k v]
(assoc m k (shuffle v)))
{}
{:a [1 2 3], :b [5 4 6], :c [20 21 100]})
=> {:a [3 1 2], :b [4 6 5], :c [21 100 20]}
- In different scenario let’s say we have a map with product names and their price:
(def products {:phone 200 :tv 300 :pc 100 :xbox 150})
and we want to sum the prices only for :phone
, :tv
and :xbox
. To do that we can write something like this:
(reduce-kv (fn [s k v]
(if (#{:phone :tv :xbox} k)
(+ s v) s))
0 products)
=> 650
- In the last example let’s look how we could sum numbers in the vector that are only at the even positions:
(def numbers (vec (range 10)))
=> [0 1 2 3 4 5 6 7 8 9]
(reduce-kv (fn [s k v]
(if (even? k) (+ s v) s))
0 numbers)
=> 20
In this case please remember that reduce-kv
works, because it operates on a vector - which is associative collection (in essence, it implements IKVReduce
protocol). If the same code would be invoked for a list
we would get such error:
IllegalArgumentException No implementation of method: :kv-reduce of protocol:
#'clojure.core.protocols/IKVReduce found for class: clojure.lang.PersistentList