HomeMathEmailBlogGitHub

Project Euler #14

2023-05-26

Euler #14 asks Which starting number, under one million, produces the longest Collatz sequence?

The naive and relatively fast approach looks at follows:

(defn next-collatz [n]
  (if (even? n)
    (quot n 2)
    (inc (* 3 n))))

(defn collatz-seq [n]
  (if (= n 1)
    [1]
    (cons n (lazy-seq (collatz-seq (next-collatz n))))))

(defn collatz-seq-count [n]
  (count (collatz-seq n)))

(defn euler-14 [n] 
  (apply max-key collatz-seq-count (range 1 n)))

It gets the job done and in reasonable time:

(time (euler-14 1000000))
"Elapsed time: 3591.471 msecs"
=> 837799

But a pretty obvious optimization is to remember previous lengths of Collatz sequences so that it isn't necessary to recompute the whole sequence. I tried going the memoize route for this, but the fact that you need to check if a value has already been cached complicated the procedure.

Here is my messy, but quicker approach:

(defn next-collatz [n]
  (if (even? n)
    (quot n 2)
    (inc (* 3 n))))

(defn collatz-seq [n]
  (if (= n 1)
    [1]
    (cons n (lazy-seq (collatz-seq (next-collatz n))))))

(defn collatz-seq-count [n map]
  (loop [m 1 val n]
    (cond
      (= val 1) m
      (contains? map val) (+ m (get map val))
      :else (recur (inc m) (next-collatz val)))))

(defn euler-14 [n]
  (loop [m 1 max-key 0 max-val 0 map {}]
    (if (>= m n)
      max-key
      (let [count (collatz-seq-count m map)]
          (recur (inc m) (if (> count max-val) m max-key) (max max-val count) (assoc map m count))))))

It shaves off about two seconds:

(time (euler-14 1000000))
"Elapsed time: 916.799042 msecs"
=> 837799