Lesser known Clojure: reduce kv


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 last
  • init is a base value for a reduce operation
  • coll 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


You may also like

Lesser known Clojure: variants of threading macro

ClojureScript: JavaScript Interop

Template method pattern