Project Euler #014 – コラッツの問題

In: Project Euler

9 1月 2010

Project EulerをClojureでおさらいシリーズ。
Problem 14

オリジナルはこちら

日本語訳:

正の整数に対して次のように繰り返す数列を定義する。

n → n/2 (nが偶空の場合) n → 3n + 1 (nが奇数の場合)

この規則に沿って 13からはじめると、次の数列を生成する。

13 → 40 → 20 → 10 → 5 → 16 → 8 → 4 → 2 → 1

13からはじまり1でおわる10項の数列とみることができる。
どの数からはじめても最終的には1になると考えられているがまだ証明 はされていない。(コラッツの問題)

100万未満のどの数からスタートすれば最も長い数列を得られるかを求めよ。

注意: 途中の項の数字が100万を越えてもかまわない。

計算の重複によるムダをいかに少なくするかがカギかな。

Nに対する数列の長さは再帰的に求めて、その値をキャッシュするのがよさげ。

以前Rubyで解いたバージョンはこちら:

#!/usr/bin/env ruby
 
def next_collatz(n)
  return 1 if n == 1
  return n / 2 if n.even?
  3 * n + 1
end
 
def get_chains_num(n)
  c = 1
  until n == 1
    n = next_collatz(n)
    c += 1
  end
  c
end
 
def (Chains = [0, 1]).[](n)
  return super if super
  if n < 2_000_000
    return Chains[n] = Chains[next_collatz(n)] + 1
  else 
    return get_chains_num(n)
  end
end
 
max = 0
point = 1
1.upto(1_000_000 - 1).each do |n|
  val = Chains[n]
  if val > max
    max = val 
    point = n
  end
end
 
p "At #{point} : chain length = #{max}"

Project Euler #002 のときと同様に配列の特異メソッドとして定義することで、Nに対するコラッツ数の長さ の値をキャッシュしています。

これが意外と速い。
ruby1.9系だと20秒かかりませんでした。

さて、Clojureで書いてみます。

(ns euler.p014)
 
(defn collatz-length
  "Return chain length of Collatz sequence of N."
  [n]
  (cond (= n 1) 1
	(even? n) (inc (collatz-length (/ n 2)))
	:else (inc (collatz-length (inc (* 3 n))))))
 
(defn memoize-under
  "Apply memoize when arg is under limit."
  [limit f]
  (let [mem (atom {})]
    (fn [arg]
      (if (> arg limit)
	(f arg)
	(if-let [e (find @mem arg)]
	  (val e)
	  (let [ret (f arg)]
	    (swap! mem assoc arg ret)
	    ret))))))
 
(def collatz-length (memoize-under 400000 collatz-length))
 
(defn pick-grater-last
  "Pick coll of which last value is greater."
  [coll-1 coll-2]
  (if (> (last coll-1)
	 (last coll-2))
    coll-1
    coll-2))
 
(println "Answer : "
	 (first
	  (reduce pick-grater-last
		  (map #(vector % (collatz-length %))
		       (range 1 1000000)))))

キャッシュなしだと60秒を越えてしまったのでmemoizeを使うことに。

ところがmemoizeをそのまま使うとヒープの上限を越えてしまいました。

Javaのヒープを増やすという解決策は美しくないので、memoizeのソースを参考にして「ある数値以下の時だけmemoizeする関数」を定義することにしました。(memoize-under)

  • キャッシュがヒットする頻度の分布は下の方に偏向しているはず
  • memoizeはハッシュなのでヒットする回数の少ないものはかえってキャッシュ 検索の効率を下げる

との考えから40万をキャッシュする値の上限にしてあります。

この方法だと20秒かからずに計算が終了しました。

blog comments powered by Disqus
Get Adobe Flash playerPlugin by wpburn.com wordpress themes

About this blog

私 manjilab のポータル的サイトになっております。日々気付いたこと、考えたこと、発表したいものを載せていきます。

Photostream

    Lisp indent 考察Lisp indent 考察Lisp indent 考察Lisp indent 考察Lisp indent 考察Tips to use Clojure(Lisp) with TextMateTips to use Clojure(Lisp) with TextMateTips to use Clojure(Lisp) with TextMate