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
x
number 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)
General Questions
- 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-freebies
returns 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?