Problem 12

今日は Problem 12をやってみた。

cf. Project Euler - Problem 12
via 自堕落系徒然日記 - Problem 12 三角数の約数

 import Data.List
 
 triangleNumbers :: [Integer]
 triangleNumbers = snd $ mapAccumL (\a x -> (a+x, a)) 1 [2..]
 
 divisors :: Integer -> [Integer]
 divisors n = concat [ z | x <- [1..floor (sqrt (fromInteger n))]
                     , let (y,r) = n `divMod` x
                     , r == 0
                     , let z = if x == y then [x] else [x,y]]
 
 euler012 :: Integer
 euler012 = f $ euler012'
   where
     euler012' = find (\x -> (length . divisors) x > 500) triangleNumbers
     f (Just x) = x
 
 main :: IO ()
 main = print euler012

約数を求める関数 divisors はこないだの nobsun さんのやつにちょっと手を入れた。
コンパイルしてから実行して数秒といったところ。もっと工夫できるのかも。

 ^o^ >ghc -o euler012 euler012.hs
 
 ^o^ >euler012
 76576500

unfold で1から10までのリスト

unfold を覚えたので,こないだの1から10までのリストを unfold を使ってやってみる。
と思ったら unfold が見つからないのでまずはその定義から。

 # let rec unfold f init =
     match f init with
       Some (a, b) -> a :: unfold f b
     | None        -> []
   ;;
 val unfold : ('a -> ('b * 'a) option) -> 'a -> 'b list = <fun>

で,リストを作る関数。

 # let list_of_int i j =
     unfold (fun x -> if x + 1 = j then None else Some (x, x+1)) i
   ;;
 val list_of_int : int -> int -> int list = <fun>
 # list_of_int 1 10;;
 - : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]

Sys.argv

コマンドライン引数は Sys.argv に配列として格納される。インデックス0がプログラム名で,以下引数の数だけ続く。
インデックス0を出力する例:

 let () = print_endline Sys.argv.(0)
 ^o^ >ocamlc -o argv.exe argv.ml
 
 ^o^ >argv
 argv

インデックス1を出力する例:

 let () = print_endline Sys.argv.(1)
 ^o^ >ocamlc -o argv.exe argv.ml
 
 ^o^ >argv foo
 foo

引数を与えないとエラーになる。

 ^o^ >argv
 Fatal error: exception Invalid_argument("index out of bounds")

unfold

なるほど,unfold ってこういうふうに使えるのか。

cf. ZOETROPEの日記 - Foundations of F#を読む(3)

Haskellでやってみる。

 *Main> take 10 $ Data.List.unfoldr (\(n0,n1) -> Just (n0, (n1, n0+n1))) (1, 1)
 [1,1,2,3,5,8,13,21,34,55]
 

unfoldr に与える関数は b -> Maybe (a, b) 型で,Nothing が返ると終端になるらしい。だから上の例では無限リストになって,最初の10個だけ取り出している。

インターフェイスファイル

ソースファイル(=モジュール)と同名で拡張子が .mli のファイルを用意しておくことで,モジュールにシグネチャを与えることができる。
前にも例に挙げた Table モジュールを例にとると,
table.mli

 type ('a, 'b) t
 val empty : ('a, 'b) t
 val add : 'a -> 'b -> ('a, 'b) t -> ('a, 'b) t
 val retrieve : 'a -> ('a, 'b) t -> 'b option
 val dump : ('a, 'b) t -> ('a * 'b) list

table.ml

 type ('a, 'b) t = Empty | Entry of 'a * 'b * ('a, 'b) t
 
 let empty = Empty
 
 let add key content table = Entry (key, content, table)
 
 let rec retrieve key = function
   Empty -> None
 | Entry (key', content, rest) -> 
   if key = key' then Some content else retrieve key rest
 
 let rec delete key = function
   Empty -> Empty
 | Entry (key', content, rest) ->
   if key = key' then delete key rest
                 else Entry (key', content, delete key rest)
 
 let rec dump = function
   Empty -> []
 | Entry (key, content, rest) ->
   (key, content) :: (dump (delete key rest))

こういう風にtable.mliとtable.mlを書く。これで

 module type TABLE =
   sig
     ...
   end
 
 module Table : TABLE =
   struct
     ...
   end

と書いたのと同じになる。


コンパイルするときは,インターフェイス(mli)ファイルを先にコンパイルする。手順としては

  1. 各mliファイルを ocamlc でコンパイル(cmiファイルができる)
  2. 各mlファイルを ocamlc -c でコンパイルcmoファイルができる)
  3. cmoファイルを ocamlc でリンク

となる。mlファイルよりもmliファイルを先にコンパイルしておくことに注意。でないとcmiファイルがないので,コンパイラが推論して勝手にcmiファイルを作り,意図したのと違うことになる。

Problem 2

今日は Problem 2。フィボナッチ数列のうち4,000,000以下で偶数のみを合計せよ,という問題。

cf. http://projecteuler.net/index.php?section=problems&id=2

Haskell ではやってる人がいるので,OCaml でやってみた。

 let fibs_under_n n =
   let rec fibs a b x =
     let c = a + b in
     if c > x then [] else c :: fibs b c x
   in
   1 :: 2 :: fibs 1 2 n
 
 
 let even n = if n mod 2 = 0 then true else false
 
 
 let sum lis = List.fold_left (+) 0 lis
 
 
 let euler002 n = sum (List.filter even (fibs_under_n n))
 
 
 let () = print_int (euler002 4000000)

even とか sum とかありそうな関数なんだけど,マニュアル見ても見つからなかったから自分で定義した。
コンパイルして実行:

 ^o^ >ocamlc -o euler002.exe euler002.ml
 
 ^o^ >euler002
 4613732

追記:

_さんに教えてもらった方法で。

 # let fibs_under_n2 n =
     let rec fibs a b x =
       let c = 4 * b + a in
       if c > x then [] else c :: fibs b c x
     in
     2 :: 8 :: fibs 2 8 n
   ;;
 # fibs_under_n2 5000;;
 - : int list = [2; 8; 34; 144; 610; 2584]

元の関数を使うと

 # List.filter even (fibs_under_n 5000);;
 - : int list = [2; 8; 34; 144; 610; 2584]

1から10までのリスト

って OCaml ではどう書けばいいんだろう。
Haskell では簡単に [1..10] と書ける。

 Prelude> [1..10]
 [1,2,3,4,5,6,7,8,9,10]

これなら10といわずいくつまででも簡単だ。だけど OCaml こういう書き方はできないらしい。

 # [1..10];;
 Characters 4-6:
   [1..10];;
       ^^
 Syntax error
 # [1;;10];;
 Characters 2-4:
   [1;;10];;
     ^^
 Syntax error

ともかく関数を書いてみた。

 # let rec list_of_int f t =
     if f > t then [] else f :: list_of_int (f+1) t
   ;;
 # list_of_int 1 10;;
 - : int list = [1; 2; 3; 4; 5; 6; 7; 8; 9; 10]

分割コンパイル

ソースファイルは1つじゃなくてもいい。次の例では,fact関数を定義している fact.ml と,それを呼び出す main.ml に分けている。

fact.ml

 let rec fact n = if n = 0 then 1 else n * fact (n-1)

main.ml

 let () = print_int (Fact.fact 5)

OCamlではファイル=モジュール(ストラクチャ)なので,fact.mlに定義されているfact関数は,Fact.fact として呼び出すか,open Fact してから使う。


さて,それぞれを(リンクせずに)コンパイルするには -c オプションを使う。

 ^o^ >ocamlc -c fact.ml
 
 ^o^ >ocamlc -c main.ml
 
 ^o^ >ls
 fact.cmi  fact.cmo  fact.ml  main.cmi  main.cmo  main.ml

.cmo と .cmi という2種類のファイルができている。 .cmo がオブジェクトファイルだ。
これらをリンクして実行形式のファイルを作るには,ソースファイルから直接コンパイルするときと同じようにすればいい。

 ^o^ >ocamlc -o fact5.exe fact.cmo main.cmo
 
 ^o^ >ls *.exe
 fact5.exe

単にリンクする.cmoファイルを並べるだけだけど,参照される側(ここではfact.cmo)を先に書くことに注意。逆にするとエラーになる。

 ^o^ >ocamlc -o fact5.exe main.cmo fact.cmo
 Error while linking main.cmo: Reference to undefined global `Fact'

さて,うまくいってるだろうか。

 ^o^ >fact5
 120