Search code examples
clojureclojurescript

Calculate winrate with loop recur


(defn to-percentage [wins total]
  (if (= wins 0) 0
      (* (/ wins total) 100)))

(defn calc-winrate [matches]
  (let [data (r/atom [])]
    (loop [wins 0
           total 0]
      (if (= total (count matches))
        @data
        (recur (if (= (get (nth matches total) :result) 1)
                 (inc wins))
               (do
                 (swap! data conj (to-percentage wins total))
                 (inc total)))))))

(calc-winrate [{:result 0} {:result 1} {:result 0} {:result 1} {:result 1}])

I got the following code, calc-winrate on the last line returns [0 0 50 0 25]. I'm trying to make it return [0 50 33.33333333333333 50 60].

Am I doing the increment for wins wrong? When I print the value of wins for each iteration I get

0
nil
1
nil
1

so I'm guessing I somehow reset or nil wins somehow?

Also, could this whole loop be replaced with map/map-indexed or something? It feels like map would be perfect to use but I need to keep the previous iteration wins/total in mind for each iteration.

Thanks!


Solution

  • Here's a lazy solution using reductions to get a sequence of running win totals, and transducers to 1) join the round numbers with the running totals 2) divide the pairs 3) convert fractions to percentages:

    (defn calc-win-rate [results]
      (->> results
           (map :result)
           (reductions +)
           (sequence
             (comp
               (map-indexed (fn [round win-total] [win-total (inc round)]))
               (map (partial apply /))
               (map #(* 100 %))
               (map float)))))
    
    (calc-win-rate [{:result 0} {:result 1} {:result 0} {:result 1} {:result 1}])
    => (0.0 50.0 33.333332 50.0 60.0)