Problem 13

今日は Problem 13 をやってみた。これは簡単。

 cf. Project Euler – Problem 13

nums :: [Integer]
nums = [37107287533902102798797998220837590246510135740250,
        46376937677490009712648124896970078050417018260538,
        74324986199524741059474233309513058123726617309629,
        ...
        72107838435069186155435662884062257473692284509516,
        20849603980134001723930671666823555245252804609722,
        53503534226472524250874054075591789781264330331690]

euler013 :: String
euler013 = take 10 $ show $ sum nums

main :: IO ()
main = putStrLn euler013
^o^ >runhaskell euler013.hs
5537376230

追記:

数字をファイル(euler013.txt)から読み込むようにしてみた。

euler013 :: [Integer] -> String
euler013 = take 10 . show . sum

main :: IO ()
main = do nums <- getContents >>= return . lines
          putStrLn $ euler013 $ map read nums
^o^ >runhaskell euler013a.hs < euler013.txt
5537376230

Problem 12

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

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

module Main where

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")

Array.iter

Array.iter は配列に対して ‘a -> unit 型の関数を順に適用する関数。List.iter の配列版。これを使えばコマンドライン引数の数だけ繰り返せる。

let () = Array.iter (fun s -> print_endline s) Sys.argv
^o^ >argv2 foo bar buz
argv2
foo
bar
buz

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]