Project Euler Problem 12

Ivar Thorson bio photo By Ivar Thorson

Project Euler problem 12 was the hardest so far, despite it’s simplicity. I started of course with a brute force approach, since it is quick to write:

(def triangle-nums (map first (iterate (fn [[n m]] [(+ n m) (+ m 1)]) [1 2])))

(defn num-divisors-slow [n]
  (+ 1 (count (filter zero? (map #(mod n %) (range 1 (+ 1 (/ n 2))))))))

(defn euler-12-slow [divisors]
  (first (drop-while #(> divisors (num-divisors-slow %)) triangle-nums)))

But when I saw how terrible the performance of such an algorithm was, I realized I would need to become more clever about how I found the number of divisors. My intuition told me that it must involve prime factors, so I wrote a function in the style of in problem 3 to return a list of all prime factors in the correct multiplicity:

(defn prime-factors-of [num]
  "Returns a sorted list of prime factors of num, including multiplicity."
  (let [q (Math/sqrt num)
        factor? (fn [nom den] (zero? (rem nom den)))] 
    (loop [n num
           d 2
           r []]
      (cond
       (> d q) (concat r [n]) 
       (= n d) (concat r [n])      
       (factor? n d) (recur (/ n d) d (conj r d)) 
       true          (recur n (inc d) r)))))

Great, huh? I was happy with how easily that particular insight had come…clearly thinking about prime numbers so much in problem 7 organized something in my mind.

But then I got completely stuck, for probably 2-3 hours, as I tried to work out by hand the rule for computing the number of proper divisors of a number based on its prime factors. It didn’t seem like a simple combination or permutation…maybe it had something to do with binomials? Chaos ruled my mind, and I got stuck thinking of ways to generate every possible divisor

When I regained my senses, I embarrassedly looked around on the appropriate page of wikipedia. The number of proper divisors is simply (a+1)(b+1)(c+1)... , where a,b,c are the multiplicity of each factor. For example, the number 12 has the prime factors (2,2,3). The multiplicity of 2 is 2, and the multiplicity of 3 is 1. So the number of divisors is (2+1)(1+1)=6, which is indeed true (1,2,3,4,6,12).

Much simpler than I had thought! At this point, the solution wrote itself:

(defn num-divisors-fast [num]
  (let [freqs (reduce #(assoc %1 %2 (inc (get %1 %2 0)))
                      {} (prime-factors-of num))]
    (reduce #(* %1 (inc %2)) 1 (vals freqs))))

(defn euler-12-fast [divisors]
  (first (drop-while #(> divisors (num-divisors-fast %)) triangle-nums)))

(euler-12-fast 500)

Ahh, beautiful!