Project Euler Problem 39

Ivar Thorson bio photo By Ivar Thorson

I found this one to be rather interesting, but maybe I just had a lot of coffee this morning and am in an especially good mood. Problem 39 asks us to find the number n < 1000 for which there are the most number of right triangles with integer sides and a perimeter of n.

Although we could generate triangles at random and then check that they are right triangles using a brute force approach, why would we do that when a variety of equations already let us generate the so-called “pythagorean triples” directly?

(defn pythagorean-triple [m n k]
  "Uses Eulcid's formula to generate pythagorean triples."
  (map #(* k %) [(- (* m m) (* n n)) (* 2 m n) (+ (* m m) (* n n))]))

(defn pythagorean-perimeters [p]
  (let [sq (int (Math/sqrt p))]
    (into #{}
          (for [n (range 1 sq)
                m (range (inc n) sq)
                k (range 1 (/ p (+ (* m m) (* n n))))
                t [(sort (pythagorean-triple m n k))]
                :when (>= p (apply + t))]
            t))))

(defn euler-39 []
  (let [h (reduce (fn [hsh e]
                    (if (nil? (hsh e))
                      (assoc hsh e 1)
                      (assoc hsh e (inc (hsh e)))))
                  {}
                  (map #(apply + %) (pythagorean-perimeters 1000)))]
    (reduce #(if (> (h %1) (h %2)) %1 %2) (keys h))))

(time (euler-39)) ;; "Elapsed time: 33.017842 msecs"

Writing pythagorean-perimeters was a little tricky, but once that was in place, pushing everything into a hashmap was a simple way to record the number of ways a particular perimeter could be formed. Performance seems very good, although I think euler-39 could still be improved somehow by using a standard sequence function…

(defn euler-39 []
  (let [h (frequencies (map #(apply + %) (pythagorean-perimeters 1000)))]
    (reduce #(if (> (h %1) (h %2)) %1 %2) (keys h))))

Ahh, that’s much better.