鍋あり谷あり

テーマを決めずに適当に書いています。

コラッツ逆展開

http://ll.jus.or.jp/2006/blog/doukaku2 の話。3回目。
g:haskell:id:jmk:20060712:1152721034 でやっていた逆展開を私もやってみる。
ただし、そのまんまやると h(100)の計算が終わらないようなので、終わるように工夫して:

def collatz( n )
  done=3 # 1,2,4 は計算済みとする
  wait=[ [8,4] ] # 1,2,4 に入れるのは 8 だけ

  def wait.special_insert a # 二分探索で整列しつつ挿入
    r=[0,size]
    while r[0]+1<r[1] do
      m=( r[0]+r[1] )/2
      r[ -self[m][0] <= -a[0] ? 0 : 1 ] = m
    end
    insert( r[ self.empty? || -a[0] <= -self[r[0]][0] ? 0 : 1 ], a )
  end

  while done<n
    a = wait.pop
    if a[0]<=n then
      done+=1
      max = a if !max || max[1]<a[1]
    end
    wait.special_insert [ a[0]*2, a[1]+1 ]
    wait.special_insert [ (a[0]-1)/3, a[1]+1 ] if 3==(a[0]-1)%6
  end

  max[0]
end

p collatz(100)

これで、手元の処理系で collatz(100) は1秒未満。1000 でも現実的な時間で終わる。10000 は試してない。
C++ の std::set のような、ソート済み配列が ruby にはないのでそれも実装する必要があったのが面倒だった。
std::set のためだけに C++ で書こうかとも思ったが、やっぱり ruby にした。
最初は挿入関数を書かずに push してから sort! していたんだが、やっぱりそれはあんまりだと思ってちゃんと書いた。そしたら劇的に速くなった。当たり前。

1,2,4 を計算済みにしている関係で、 引数の値が 3以下だとうまくいかない。まあドンマイということで。