I recently began learning Clojure. In order to get better acquainted with it, I've been tackling Project Euler challenges.
Problem 14 is not really a difficult one. It asks for the number that results in the longest Collatz sequence.
The following iterative sequence is defined for the set of positive integers:
n → n/2 (n is even) n → 3n + 1 (n is odd)
Using the rule above and starting with 13, we generate the following sequence: 13 → 40 → 20 → 10 → 5 → 16 → 8 → 4 → 2 → 1
It can be seen that this sequence (starting at 13 and finishing at 1) contains 10 terms. Although it has not been proved yet (Collatz Problem), it is thought that all starting numbers finish at 1.
Which starting number, under one million, produces the longest chain?
NOTE: Once the chain starts the terms are allowed to go above one million.
Although my solution does solve the problem, I am not sure if it conforms to the Clojure way of doing things, since it seems to be verbose.
- Does this conform to functional practices?
- Are there any mistakes which result in code being longer than needed?
- Is there a way of omitting the loop/recur and doing the same with list functions such as
map
andapply
- Any other suggestions in general?
(ns problem14)
(use '[clojure.test :only (is)])
(defn count-collatz
"Returns a vector [a b] where b is the
number that initiated the sequence, and
a is the number of steps taken to reach 1."
[input-num]
(loop [num input-num count 1]
(if (= num 1)
(vector count input-num)
(do (if (= (mod num 2) 0)
(recur (/ num 2) (inc count))
(recur (+ (* num 3) 1) (inc count)))))))
;; Test case from the project description.
(is (= (count-collatz 13) [10 13]))
(loop [number 1 peak [0 0]]
(if (>= number 1e6)
(str "Longest chain is " (last peak))
(do (let [result (count-collatz number)]
(do (if (> (first result) (first peak))
(recur (inc number) result)
(recur (inc number) peak)))))))
1 Answer 1
CAVEAT I am not a clojure programmer. (But hopefully you will agree with what I suggest ;))
Put all your code in functions such that all top level forms are def
s. Similarly put your tests in deftest
forms and run them with run-tests
.
In clojure, too, extract meaningful expressions to their own functions and give them names. Then promote baked in magic constants in them to parameters so that they are more testable, in REPL and elswhere.
You can use destructuring let
to give meaningful names to a function returning a vector.
Applying these general rules
(defn longest-chain [limit]
(loop [number 1 peak-len 0 peak-val 0]
(if (>= number limit)
peak-val
(do (let [[curr-len curr-val] (count-collatz number)]
(do (if (> curr-len peak-len)
(recur (inc number) curr-len curr-val)
(recur (inc number) peak-len peak-val))))))))))
(defn -main []
(println "Longest chain is" (longest-chain 1e6)))
Here is how you can rewrite these functions using core library.
(= x 0)
is(zero? x)
(zero? (mod num 2))
is(even? x)
- repeatedly applying a function without explicit
loop
/recur
isiterate
. - You can use
for
comprehension of arange
instead ofloop
/recur
withinc
ing a parameter. - You can also use core library functions that operate on sequences (
map
,reduce
et al) for a more functional style. - You can use threading macros to reduce nesting levels as necessary; and to make your functions read top-down instead of more counter-intuitive inside-out.
- You can use
(apply max-key k coll)
to get "the [value] for which (k x), a number, is greatest."
Code after above changes:
(defn count-collatz [n]
(let [f (fn [n] (if (even? n) (/ n 2) (+ (* n 3) 1)))]
(->> n
(iterate f)
(take-while #(> % 1))
count
inc)))
(defn longest-chain [limit]
(->> limit
(range 1)
(map #(vector (count-collatz %) %))
(apply max-key first)
second)
Or using the fact that max-key
can get arbitrary function as its first parameter, we get:
(defn longest-chain [limit]
(apply max-key count-collatz (range 1 limit)))
-
\$\begingroup\$ For a self-professed "non-Clojure programmer," you sure do know your way around writing idiomatic Clojure code! +1 \$\endgroup\$Dave Yarwood– Dave Yarwood2014年05月07日 15:54:23 +00:00Commented May 7, 2014 at 15:54