せっかくなので

1回目はずいぶんベタだったが、2回目もなんかあんまり違いの出なさそうなお題がでるなぁと。
まだ、OCaml な回答を書いている人がすくなさそうなので。というほど気の利いたプログラムを書いたわけじゃありませんが。それにしても、巷の Haskell ブームがスゴイですね。


ふつうの人なら、ふつうに書いて memo 化して終わりだろうなぁ。

let memo = Hashtbl.create 100000
;;
let rec g n =
  if Hashtbl.mem memo n then
    Hashtbl.find memo n
  else
    let v =
      if n = 1 then 0
      else if n mod 2 = 0 then g (n / 2) + 1
      else g (3 * n + 1) + 1 in
    Hashtbl.add memo n v;
    v
;;

let h n =
  let max_i = ref (-1) in
  let max_v = ref (-1) in
  for i = 1 to n do
    let v = g i in
    if !max_v < v then (max_i := i; max_v := v)
  done;
  !max_i
;;

Arg.parse []
  (fun s -> print_int (h (int_of_string s)); print_newline ())
  ""

ベタだ。ベタベタすぎて、気持ち悪い。なんだかんだ、やっぱり標準ライブラリは関数少ないよねぇと思った。変な for 文が入ってる。fold_left の index 付き版が欲しいよねぇとか。

そんなこといっても、Hashtbl がメモリ食いつぶすかもしれないじゃんという人のための、direct map 実装。

let size = ref 100000
let cache = ref (Array.create 0 None)

let load n =
  match !cache.(n mod !size) with
  | Some (i, v) when i = n -> Some v
  | _ -> None
;;
let memo n v =
  !cache.(n mod !size) <- Some (n, v)
;;

let rec g n =
  match load n with
  | Some v -> v
  | None ->
      let v =
        if n = 1 then 0
        else if n mod 2 = 0 then g (n / 2) + 1
        else g (3 * n + 1) + 1 in
      memo n v;
      v
;;

let h n =
  cache := Array.create !size None;

  let max_i = ref (-1) in
  let max_v = ref (-1) in
  for i = 1 to n do
    let v = g i in
    if !max_v < v then (max_i := i; max_v := v)
  done;
  !max_i
;;

Arg.parse
  ["-c", Arg.Set_int size, "cache size"]
  (fun s -> print_int (h (int_of_string s)); print_newline ())
  ""

ベタだ。引数オプションは Set_XXX で。Arg module は便利だ。

OCaml の何が悲しいって、32 bit マシンでも整数型は 32 bit もないところ。最近は実験環境が 64 bit なので、とくに困ることがあるわけでもないが。上のプログラムは、ocamlopt @ Opteron 2.4 GHz で a.out 1000000 が Hashtbl 版は 3.5 sec、Array 版は 2.6 sec くらい。Array 版は、キャッシュのサイズに依るけどねぇ。

追記

あれ、よく見たらこれ DP にならないじゃん。何やってんだオレ。そしたら、なおさらどこが「関数型的」なのか分からない。

let rec g i n =
  if n = 1 then i
  else if n mod 2 = 0 then g (i + 1) (n / 2)
  else g (i + 1) (3 * n + 1)
;;

let h n =
  let max_i = ref (-1) in
  let max_v = ref (-1) in
  for i = 1 to n do
    let v = g 0 i in
    if !max_v < v then (max_i := i; max_v := v)
  done;
  !max_i
;;

Arg.parse []
  (fun s -> print_int (h (int_of_string s)); print_newline ())
  ""

ただの tail recursive にして、1000000 が 1 sec くらい。これじゃ、ただのループでも簡単に書けるし。うーん、空しい。