I am trying to learn Clojure for some time. In my experience, it has been rather too easy to produce write-only code.
Here is a solution to a simple problem with very little essential complexity. Input and output formats are extremely simple, too. Which means all complexity in it must be accidental. How to improve its legibility, intelligibility?
- Is the decomposition of the problem into functions all right?
Also other specific problems:
How to input/output numbers? Is there any benefit to use
Double/parseDouble? How to format floating point numbers to fixed precision without messing with the default locale?
How to avoid the explicit
recur, which is currently a translation of a while loop?
Are there definitions that should/shouldn't have bee private/dynamic?
You start with 0 cookies. You gain cookies at a rate of 2 cookies per second [...]. Any time you have at least C cookies, you can buy a cookie farm. Every time you buy a cookie farm, it costs you C cookies and gives you an extra F cookies per second.
Once you have X cookies that you haven't spent on farms, you win! Figure out how long it will take you to win if you use the best possible strategy.
(ns cookie-clicker (:use [clojure.string :only [split]]) (:require [clojure.java.io :as io] [clojure.test :refer :all])) ;;See http://code.google.com/codejam/contest/2974486/dashboard#s=p1 (defn parse-double [s] (java.lang.Double/parseDouble s)) (defn parse-row [line] (map parse-double (split line #"\s+"))) (defn parse-test-cases [rdr] (->> rdr line-seq rest (map parse-row))) (def initial-rate 2.0) (defn min-time [c f x] (loop [n 0 ; no of factories used tc 0 ; time cost of factories built r initial-rate ; cookie production rate t (/ x r)] ; total time (let [n2 (inc n) tc2 (+ tc (/ c r)) r2 (+ r f) t2 (+ tc2 (/ x r2))] (if (> t2 t) t (recur n2 tc2 r2 t2))))) (java.util.Locale/setDefault (java.util.Locale/US)) (defn ans [n t] (str "Case #" n ": " (format "%.7f" t))) (defn answers [test-cases] (map #(ans %1 (apply min-time %2)) (rest (range)) test-cases)) (defn spit-answers [in-file] (with-open [rdr (io/reader in-file)] (doseq [answer (answers (parse-test-cases rdr))] (println answer)))) (defn solve [in-file out-file] (with-open [w (io/writer out-file :append false)] (binding [*out* w] (spit-answers in-file)))) (def ^:dynamic *tolerance* 1e-6) (defn- within-tolerance [expected actual] (< (java.lang.Math/abs (- expected actual)) *tolerance*)) (deftest case-3 (is (within-tolerance 63.9680013 (min-time 30.50000 3.14159 1999.19990)))) (defn -main  (solve "resources/cookie_clicker/B-large-practice.in" "resources/cookie_clicker/B-large-practice.out"))