reduce-kv

added
1.4

ns
clojure.core

type
function

(reduce-kv f init coll)

Reduces an associative collection. f should be a function of 3
arguments. Returns the result of applying f to init, the first key
and the first value in coll, then applying f to that result and the
2nd key and value, etc. If coll contains no entries, returns init
and f is not called. Note that reduce-kv is supported on vectors,
where the keys will be the ordinals.

                Let's assume you want to apply a function to a vector of maps,

Input: [{:a 1 :b 2} {:a 3 :b 4}]

such that all vals are incremented by 1.

Result: [{:a 2 :b 3} {:a 4 :b 5}]

An easy way to do so is using reduce-kv,

(def vector-of-maps [{:a 1 :b 2} {:a 3 :b 4}])

(defn update-map [m f] 
  (reduce-kv (fn [m k v] 
    (assoc m k (f v))) {} m))

(map #(update-map % inc) vector-of-maps)

=> ({:b 3, :a 2} {:b 5, :a 4})
            
                ;; Swap keys and values in a map
user=> (reduce-kv #(assoc %1 %3 %2) {} {:a 1 :b 2 :c 3})
{1 :a, 2 :b, 3 :c}
            
                ;; Swap keys with values, only if values are not empty,
;; while turning values into proper keys

(def someMap { :foo "food", :bar "barista", :baz "bazaar"})

(defn swap [someMap]
  (reduce-kv (fn [m k v]
               (if (empty? v) m (assoc m (keyword v) (name k)))) {} someMap))

(swap someMap)

=> {:food "foo", :barista "bar", :bazaar "baz"}

            
                ;; Calculate total wins and winning streaks
(def all-games 
  [{:game 1 :won true} 
   {:game 2 :won false} 
   {:game 3 :won true} 
   {:game 4 :won true}])

(reduce-kv
  (fn [result index game]
    (let [last-game (last result)
          wins (if (:won game) 
                 (inc (:total last-game 0)) 
                 (:total last-game))
          streak (if (:won game) 
                   (inc (:streak last-game 0)) 
                   0)]
      (println (assoc game :total wins :streak streak))
      (conj result (assoc game :total wins :streak streak))))
  []
  all-games)

;; Output
;; {:game 1, :won true, :total 1, :streak 1}
;; {:game 2, :won false, :total 1, :streak 0}
;; {:game 3, :won true, :total 2, :streak 1}
;; {:game 4, :won true, :total 3, :streak 2}

;; [{:game 1, :won true, :total 1, :streak 1} {:game 2, :won false, :total 1, :streak 0} {:game 3, :won true, :total 2, :streak 1} {:game 4, :won true, :total 3, :streak 2}]
            
                ;; You can define map-kv using reduce-kv, 
;; to do something to every value in a map.

(defn map-kv [f coll]
  (reduce-kv (fn [m k v] (assoc m k (f v))) (empty coll) coll))

(map-kv inc {:a 12, :b 19, :c 2})
;;=> {:c 3, :b 20, :a 13}

;; It works on vectors, too.
(map-kv inc [1 1 2 3 5])
;;=> [2 2 3 4 6]
            
                ;; It works with indexes on vectors as well

(reduce-kv (fn [res idx itm] (assoc res idx itm)) {} ["one" "two" "three"])

;;=> {2 "three", 1 "two", 0 "one"}
            
                
(defn update-map-entries[m e]
     (reduce-kv (fn [r k v] (assoc  r k v))  m e))

;;user=> (update-map-entries {:a 1 :b 2 :c 3} {:a 5 :b 9})
;;{:a 5, :b 9, :c 3}
;;user=> (update-map-entries {:a 1 :b 2 :c 3} {:a 5 :b 9 :d 8})
;;{:a 5, :b 9, :c 3, :d 8}