I’ve been dabbling in Clojure by solving programming puzzles. This particular puzzle is from HackerRank, with the objective of figuring out how much chocolate one could eat given three parameters:
- The cost of a piece of chocolate
- How much money you have
- The candy shop owner will give you a free chocolate for
xnumber of wrappers
In the code below, those three numbers are passed to
total-chocolates-consumed which returns the answer.
(ns hr.clojure.algorithms.implementation.implementation.chocolate-feast) (defn int-division [numerator denominator] (->> (/ numerator denominator) int)) (defn number-to-purchase [totalToSpend costPerChocolate] (int-division totalToSpend costPerChocolate)) (defn number-of-freebies [totalWrappers wrappersPerFreebie] [(int-division totalWrappers wrappersPerFreebie) (int (mod totalWrappers wrappersPerFreebie))]) (defn total-chocolates-consumed [totalToSpend costPerChocolate wrappersPerFreebie] (let [purchasedChocolates (number-to-purchase totalToSpend costPerChocolate) freebies (first (number-of-freebies purchasedChocolates wrappersPerFreebie)) totalChocolates [purchasedChocolates freebies] startRemainder (second (number-of-freebies purchasedChocolates wrappersPerFreebie))] (loop [chocs totalChocolates remainder startRemainder] (cond (> wrappersPerFreebie (+ remainder (last chocs))) (reduce + chocs) :else (recur (conj chocs (first (number-of-freebies (+ (last chocs) remainder) wrappersPerFreebie))) (second (number-of-freebies (+ (last chocs) remainder) wrappersPerFreebie)))))))
I've written tests for this code here:
(ns hr.clojure.algorithms.implementation.implementation.chocolate-feast.spec) (load-file "YOUR-PATH-TO-FILE") (use 'hr.clojure.algorithms.implementation.implementation.chocolate-feast) (use 'clojure.test) (deftest chocolate-feast (testing "number-to-purchase" (is (= 0 (number-to-purchase 2 10))) (is (= 2 (number-to-purchase 10 4))) (is (= 666 (number-to-purchase 1000 1.5))) (is (= 3 (number-to-purchase 6 2))) (is (= 5291 (number-to-purchase 10000 1.89)))) (testing "number-of-freebies" (is (= [0 1] (number-of-freebies 1 2))) (is (= [100 0] (number-of-freebies 100 1))) (is (= [2 0] (number-of-freebies 3 1.5))) (is (= [1 1] (number-of-freebies 3 2)))) (testing "total-chocolates-consumed" (is (= 6 (total-chocolates-consumed 10 2 5))) (is (= 3 (total-chocolates-consumed 12 4 4))) (is (= 5 (total-chocolates-consumed 6 2 2))) (is (= 899 (total-chocolates-consumed 43203 60 5))))) (deftest math-library (testing "int-division" (is (= 0 (int-division 7 12))) (is (= 2 (int-division 12 6))) (is (= 2 (int-division 12 5))))) (run-tests)
- Is this how tests are written in Clojure?
- Is the code readable? Can you tell what I was thinking?
How idiomatic is my code?
- Should I use camel case for parameter names?
- Should I hyphenate my function names?
- Is this “waterfall” sort of formatting typical?
What is a good way to communicate what a function does?
- For instance,
number-of-freebiesreturns a vector of [chocolates earned with wrappers, wrappers leftover]. Could I return a type that actually indicates what those values are to the consumer of that function?
How do I refactor
total-chocolates-consumed to be less complex?
- There is some redundant code in there for figuring the number of freebies, could I pull that out into a local value within the loop?
- Should I be using a loop or some higher-order function?