I'm learning Clojure and am a rank n00b at it, trying to learn from books and online tutorials (but I'm sometimes concerned that I am picking up bad habits or at least not all the good habits). For exercise I did the Breaking the Records problem on Hackerrank.
TL;DR problem description:
For a list of scores (in historic order), count the number of times the previous best score was exceeded as well as the previous worst score was undercut.
In an iterative language it is quite easy to just iterate through the list; but Clojure does not do iteration and I decided to tackle this problem for exercise in constructing a (tail end) recursive solution. To make it easier for myself, I first did a recursive solution in Java, which I then translated. All in all, it's quite a simple recursive function without rocket surgery involved.
Obviously my code works, as shown by the included unit tests. My concerns are however as follows:
- When put next to the Java code, the two look fairly similar. Did I follow "idiomatic" Clojure programming, or is it just a clumsy "word-for-word transliteration"?
- Are there any areas that could have been more compact and/or easier to understand by using different Clojure constructs?
Your critical input will be highly appreciated - including regarding the unit testing, as that is arguably an important part of programming, which I want to learn and practice in parallel.
Code:
(ns hackerrank.breaking-records
(:require [clojure.test :refer :all]))
(defrecord Record [min max countworse countbetter])
(defn recalc-record [rec newscore]
(Record.
(min newscore (:min rec))
(max newscore (:max rec))
(+ (:countworse rec) (if (> (:min rec) newscore) 1 0))
(+ (:countbetter rec) (if (< (:max rec) newscore) 1 0))))
(defn accumulate [curr-record remaining-scores]
(if (nil? (second remaining-scores))
curr-record
(recur (recalc-record curr-record (second remaining-scores)) (rest remaining-scores)))
)
(defn breaking-records [scores]
(let [result (accumulate (Record. (first scores) (first scores) 0 0) scores)]
(list (:countbetter result) (:countworse result))))
(deftest test-records
(testing "edge cases"
(is (= '(0 0) (breaking-records '())) "no games played yet")
(is (= '(0 0) (breaking-records '(5))) "single game"))
(testing "hackerrank examples"
(is (= '(2 4) (breaking-records '(10 5 20 20 4 5 2 25 1))))
(is (= '(4 0) (breaking-records '(3 4 21 36 10 28 35 5 24 42)))))
)