(* I-1 整数 n>=0 を一つ引数にとり、連続するn個の'*'からなる文字列を返す関数 stars : int -> string を定義せよ。 実行例: - stars 5; val it = "*****" : string *) |
(* 解1 *)
fun stars n =
if n = 0 then ""
else "*" ^ stars (n-1);
(* 解2 *)
val stars =
let fun f str n =
if n = 0 then str else f ("*" ^ str) (n-1)
in
f ""
end;
|
(* I-2 int -> int の関数fと、整数の組(x1,x2) (ただしx1 < x2)を引数にとり、 (f x1) + (f (x1 + 1)) + ... + (f (x2 - 1)) + (f x2) を返す関数 sumrange : (int -> int) -> (int * int) -> int を定義せよ。 実行例: - sumrange (fn x => x) (1,4); val it = 10 : int - sumrange (fn x => x * x) (1,4); val it = 30 : int *) |
(* 解1 *)
fun sumrange f (x1, x2) =
let fun g x =
if x2 = x then f x2
else f x + g (x + 1)
in
g x1
end;
(* 解2 *)
fun sumrange f (x1, x2) =
let fun g x sum =
if x2 = x then sum + f x2
else g (x+1) (sum + f x)
in g x1 0
end
|
(* I-3 int -> int の関数fと、整数の組(x1,x2) (ただしx1 < x2)を引数にとり、 x1 から x2 まで(x1,x2ともに含む)の整数iを順に f に与え、 1行ごとに(f i)個の'*'を印字する関数 graph : (int -> int) -> (int * int) -> () を定義せよ。 ただし、 f i <= 0 の場合、その行には空行を印字する。 実行例: - graph (fn x=> x) (1,4); * ** *** **** val it = () : unit - graph (fn x => x * x - 3) (1, 4); * ****** ************* val it = () : unit - graph (fn x => (x - 2) * (x - 2)) (0, 4); **** * * **** val it = () : unit *) |
fun graph f (x1, x2) =
let fun g x =
let val v = f x
val line = (if v > 0 then stars v else "") ^ "\n"
in
if x = x2 then line
else line ^ (g (x + 1))
end
in
print (g x1)
end;
|
(* II-1 うるう年を判定する関数 isleap : int -> bool を定義せよ。 ただし、400で割り切れるか、4で割り切れてかつ100で割り切れない年をうるう年とする。 実行例: - isLeap 1999; val it = false : bool - isLeap 2000; val it = true : bool - isLeap 2004; val it = true : bool - isLeap 2100; val it = false : bool *) |
fun isLeap y = y mod 400 = 0 orelse (y mod 4 = 0 andalso y mod 100 <> 0); |
(* II-2 年と月とを引数にとり、その月の日数を計算する関数 daysOfMonth : int * int -> int を定義せよ。 実行例: - daysOfMonth (2002,2); val it = 28 : int - daysOfMonth (2002,3); val it = 31 : int - daysOfMonth (2002,4); val it = 30 : int *) |
fun daysOfMonth (y, m) =
if m = 1 orelse m = 3 orelse m = 5 orelse m = 7 orelse m = 8 orelse m = 10 orelse m = 12 then
31
else
if m = 2 then
if isLeap y then 29 else 28
else 30;
|
(* II-3 年を引数にとり、その年の日数を計算する関数 daysOfYear : int -> int を定義せよ。 実行例: - daysOfYear 1996; val it = 366 : int - daysOfYear 1999; val it = 365 : int *) |
fun daysOfYear y =
if isLeap y then 366 else 365;
|
(* II-4 年月日を引数にとり、その年の1月1日からその日までの日数を計算する関数 daysFromNewYear : int * int * int -> int および、年月日を引数にとり、その日からその年の大晦日までの日数を計算する関数 daysToNewYear : int * int * int -> int を定義せよ。 実行例: - daysFromNewYear (1999,1,1); val it = 1 : int - daysFromNewYear (1999,12,31); val it = 365 : int - daysToNewYear (1999,1,1); val it = 364 : int - daysToNewYear (1999,12,31); val it = 0 : int *) |
fun daysFromNewYear (y, m, d) =
let fun daysOfM m' = if m' = m then 0
else daysOfMonth (y, m') + daysOfM (m'+1)
in daysOfM 1 + d
end;
(* 解1 *)
fun daysToNewYear (y, m, d) = daysOfYear y - daysFromNewYear (y,m,d);
(* 解2 *)
fun daysToNewYear (y, m, d) =
let fun daysOfM m' = if m' = m then 0
else daysOfMonth (y, m') + daysOfM (m'-1)
in daysOfM 12 + (daysOfMonth (y, m) - d)
end;
|
(* II-5 二つの年月日を引数にとり、その間の日数を計算する関数 diffDate : (int * int * int) -> (int * int * int) -> int を定義せよ。 ただし、 diffDate (y1,m1,d1) (y2,m2,d2) であるとき、(y1,m1,d1)は(y2,m2,d2)よりも前の日付であると仮定してよい。 実行例: - diffDate (2001,1,1) (2001,12,31); val it = 364 : int - diffDate (2001,1,1) (2002,12,31); val it = 729 : int - diffDate (2001,1,1) (2001,1,31); val it = 30 : int *) |
fun diffDate (y1,m1,d1) (y2,m2,d2) =
if y1 < y2 then
let fun daysOfY y' = if y' = y2 then 0
else daysOfYear y'
in daysToNewYear (y1,m1,d1) + daysOfY (y1 + 1) + daysFromNewYear (y2,m2,d2)
end
else if m1 < m2 then
let fun daysOfM m' = if m' = m2 then 0
else daysOfMonth (y1, m') + daysOfM (m'+1)
in (daysOfMonth (y1,m1) - d1) + daysOfM (m1 + 1) + d2
end
else d2 - d1;
|
(* III-1
Broca法によれば、標準体重はつぎのように定義される。
標準体重[kg] = (身長[cm] - 100.0) * 0.9
ただし、身長が150.0cm以下の場合、身長[cm] - 100.0
身長[cm]を引数にとり、この定義にしたがって標準体重[kg]を計算する関数
Broca : real -> real
を定義せよ。
実行例:
- Broca 184.0;
val it = 75.6 : real
*)
|
fun Broca tall = if tall <= 150.0 then tall - 100.0 else (tall - 100.0) * 0.9; |
(* III-2
BMI法によれば、標準体重はつぎのように定義される。
標準体重[kg] = 22.0 * (身長[cm]/100) * (身長[cm]/100)
身長[cm]を引数にとり、この定義にしたがって標準体重[kg]を計算する関数
BMI : real -> real
を定義せよ。
実行例:
- BMI 184.0;
val it = 74.4832 : real
*)
|
fun BMI tall = let val mtall = tall / 100.0 in 22.0 * mtall * mtall end; |
(* III-3 標準体重1kgあたりの所要エネルギー量は職種別に デスクワーク 25.0kcal 外回り営業 30.0kcal 肉体労働 35.0kcal と定義される。 職種を引数にとり、標準体重1kgあたりの所要エネルギー量[kcal]を計算する関数 workCalorie : int -> real を定義せよ。 ただし、 「デスクワーク従事者」 = 1, 「外回り営業」 = 2, 「肉体労働」 = 3 とする。また、それ以外の引数を与えられた場合は 0.0 を返すこととする。 実行例: - workCalorie 1; val it = 25.0 : real - workCalorie 5; val it = 0.0 : real *) |
fun workCalorie work =
if work = 1 then 25.0
else if work = 2 then 30.0
else if work = 3 then 35.0
else 0.0;
|
(* III-4
1日の適正エネルギー量[kcal]は
1日の適正エネルギー量[kcal] = 標準体重[kg] * 標準体重1kgあたりの所要エネルギー量[kcal]
と定義される。
標準体重計算方法(Broca,BMI)と、職種、身長[cm]を引数にとり、1日の適正エネルギー量[kcal]を計算する関数
dayCalorie : (real -> real) -> int -> real -> real
標準体重計算方法 -> 職種 -> 身長 -> 1日の適正エネルギー量
を定義せよ。
実行例:
- dayCalorie Broca 1 184.0;
val it = 1890.0 : real
- dayCalorie BMI 1 184.0;
val it = 1862.08 : real
*)
|
fun dayCalorie f work tall = f tall * (workCalorie work); |
(* III-5
肥満度はつぎのように定義される。
体重 / 標準体重 肥満度
=========================
0.8未満 痩せすぎ
0.8以上 〜 0.9未満 痩せ気味
0.9以上 〜 1.1以下 適正
1.1超 〜 1.2以下 肥満気味
1.2超 太りすぎ
標準体重計算方法(Broca,BMI)と、身長[cm]および体重[kg]とを引数にとり、肥満度を計算する関数
fatness : (real -> real) -> (real * real) -> int
標準体重計算方法 -> (身長 * 体重) -> 肥満度
を定義せよ。
ただし、
「痩せすぎ」 = 1, 「痩せ気味」 = 2, 「適正」 = 3, 「肥満気味」 = 4, 「太りすぎ」 = 5
とする。
実行例:
- fatness BMI (184.0, 74.0);
val it = 3 : int
*)
|
fun fatness f (tall,weight)=
let val stdw = f tall
val fat = weight / stdw
in if fat < 0.8 then 1
else if fat < 0.9 then 2
else if fat <= 1.1 then 3
else if fat <= 1.2 then 4
else 5
end;
|
(* 以下の式をSMLに与え、SMLが推論する型を確認せよ。 そして、その型を推論する過程を推定して説明せよ。 ただし、型変数名は説明に都合のよいようにSMLとは異なる名前を用いてよい。 たとえばSMLは - fn x => x; val it = fn : 'a -> 'a のように推論された型を表示するが、答案では 'b -> 'b と推論してもよい。 *) |
(* IV-1 *)
fn x => fn y => (x y) : int;
(* val it = fn : ('a -> int) -> 'a -> int *)
|
(*
xの型を'aとおく。 (1)
yの型を'bとおく。 (2)
(2)および x y がintであることから、'a = 'b -> int (3)
(2)および (x y) : int より fn y => x y の型は 'b -> int (4)
(1)および(4)より fn x => fn y => (x y) : int の型は
'a -> 'b -> int
であるが、(3)より、
('b -> int) -> 'b -> int
である。
*)
|
(* IV-2 *) fn x => fn y : int => x y; (* val it = fn : (int -> 'a) -> int -> 'a *) |
(* xの型を'aとおく。 (1) yの型はintである。 (2) x y および(1),(2)より、ある型'bについて 'a = int -> 'b (3) であり、 x y の型は 'b (4) (2)および(4)より、 fn y : int => x y の型は int -> 'b (5) (1)および(5)より、fn x => fn y : int => x y の型は 'a -> (int -> 'b) であるが、これは(3)より、 (int -> 'b) -> (int -> 'b) である。 *) |
(* IV-3 *)
fn x => fn y => x (fn x => y x) y;
(* val it = fn : (('a -> 'b) -> ('a -> 'b) -> 'c) -> ('a -> 'b) -> 'c *)
|
(*
外側のxの型を'aとおく。 (1)
yの型を'bとおく。 (2)
まず、fn x => y x の型を推論する。
内側のxの型を'cとおく。 (3)
y x および(2)より、ある型'dについて、
'b = 'c -> 'd (4)
であり、fn x => y x の型は 'c -> 'd (5)
式 x (fn x => y x) y および(1)より、ある型'eについて
'a = ('c -> 'd) -> ('c -> 'd) -> 'e (6)
であり、x (fn x => y x) y の型は 'e (7)
(2),(4),(7)より、fn y => x (fn x => y x) y の型は
('c -> 'd) -> 'e (8)
(1),(8)より、fn x => fn y => x (fn x => y x) y の型は
'a -> ('c -> 'd) -> 'e
であるが、これは(6)より、
(('c -> 'd) -> ('c -> 'd) -> 'e) -> ('c -> 'd) -> 'e
である。
*)
|
(* V-1 ハノイの塔問題はFrancois Edouard Anatole Lucas(1842-1891)という数学者によってつくられ、 再帰法の例としてよく引用される。 3本の杭A,B,Cとn枚の異なる円盤がある。 円盤の中心に穴があいており、穴を杭に通しながら円盤に積み上げていくことができる。 ただし、杭に円盤を積み上げるとき、大きい円盤の上に小さい円盤を置かなければならないとする。 始めn枚の円盤が杭Aに積み上げられているとする。 これらの円盤をすべて杭Cに積み上げたい。円盤は一度に一枚しか移動できないとする。杭Bは補助として用いることができる。 ここで、0以上の正数nを引数にとり、n個の円盤を持つハノイの塔問題の解答を表す文字列を返す関数 hanoi = fn : int -> string を定義せよ。 実行例: - hanoi 3; val it = "[A->C][A->B][C->B][A->C][B->A][B->C][A->C]" : string この文字列は、杭Aの一番上の円盤を杭Cに移動し、つぎに、杭Aの一番上に現れた円盤を杭Bに移動し... という手順により、すべての円盤を杭Cに移動できることを意味している。 ヒント: まず、hanoi'という補助的な関数を定義する。 hanoi'は円盤の枚数、円盤の初期位置の杭の名前、補助用の杭の名前、目的地の杭の名前の4個の引数をとり、 円盤を移動させる手順を表す文字列を返す関数である。 hanoi' : int -> string -> string -> string -> string ハノイの塔の初期状態では、n個の円盤、つまり高さnの塔(ここでは塔の高さは円盤の枚数とする)が立っているが、 その塔を、最下層にある1枚の円盤と、その上に重なっているn-1枚の円盤(これを(n-1)部分塔と呼ぶ)、 という二つの部分に分けて考えることにする。するとハノイの塔問題を解く手順は (i) (n-1)部分塔を初期状態から補助用の杭へ移動させる。 (ii) 最下層の円盤を初期状態から目的地へ移動させる。 (iii) (n-1)部分塔を補助用の杭から目的地へ移動させる。 という三つの部分に分けることができる。これらの部分のうち、(i),(iii)は全体の構造と同じなので、 hanoi'自身を再帰的に呼び出すことによって解くことができる。 hanoi'が定義できたなら。hanoiは hanoi' n "A" "B" "C" を呼び出すことによって定義できる。 *) |
fun hanoi n =
let
fun hanoi' n source work dest =
if n >= 1 then
hanoi' (n-1) source dest work
^ "[" ^ source ^ "->" ^ dest ^ "]"
^ hanoi' (n-1) work source dest
else
""
in
hanoi' n "A" "B" "C"
end
|