課題1

解答例

I.グラフ

I-1

(* I-1
 整数 n>=0 を一つ引数にとり、連続するn個の'*'からなる文字列を返す関数
   stars : int -> string
 を定義せよ。

 実行例:
 - stars 5;
 val it = "*****" : string
 *)
T.A.による解答
(* 解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

(* 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
*)
T.A.による解答
(* 解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

(* 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
 *)
T.A.による解答
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. 日付計算

II-1

(* 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
 *)
T.A.による解答
fun isLeap y = y mod 400 = 0 orelse (y mod 4 = 0 andalso y mod 100 <> 0);

II-2

(* 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
 *)
T.A.による解答
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

(* II-3
 年を引数にとり、その年の日数を計算する関数
   daysOfYear : int -> int
 を定義せよ。

 実行例:
 - daysOfYear 1996;
 val it = 366 : int
 - daysOfYear 1999;
 val it = 365 : int
 *)
T.A.による解答
fun daysOfYear y =
    if isLeap y then 366 else 365;

II-4

(* 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
 *)
T.A.による解答
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

(* 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
 *)
T.A.による解答
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.健康

III-1

(* 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
*)
T.A.による解答
fun Broca tall = if tall <= 150.0 then tall - 100.0
		 else (tall - 100.0) * 0.9;

III-2

(* III-2
 BMI法によれば、標準体重はつぎのように定義される。
     標準体重[kg] = 22.0 * (身長[cm]/100) * (身長[cm]/100)

 身長[cm]を引数にとり、この定義にしたがって標準体重[kg]を計算する関数
    BMI : real -> real
 を定義せよ。

 実行例:
 - BMI 184.0;
 val it = 74.4832 : real
 *)
T.A.による解答
fun BMI tall = let val mtall = tall / 100.0
	       in 22.0 * mtall * mtall
	       end;

III-3

(* 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
 *)
T.A.による解答
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

(* 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
 *)
T.A.による解答
fun dayCalorie f work tall = f tall * (workCalorie work);

III-5

(* 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
 *)
T.A.による解答
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;

IV.型推論

(*
 以下の式をSMLに与え、SMLが推論する型を確認せよ。
 そして、その型を推論する過程を推定して説明せよ。

 ただし、型変数名は説明に都合のよいようにSMLとは異なる名前を用いてよい。
 たとえばSMLは
 - fn x => x;
 val it = fn : 'a -> 'a
 のように推論された型を表示するが、答案では 'b -> 'b と推論してもよい。
 *)

IV-1

(* IV-1 *)
fn x => fn y => (x y) : int;
(* val it = fn : ('a -> int) -> 'a -> int *)
T.A.による解答
(*
 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

(* IV-2 *)
fn x => fn y : int => x y;
(* val it = fn : (int -> 'a) -> int -> 'a *)
T.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

(* IV-3 *)
fn x => fn y => x (fn x => y x) y;
(* val it = fn : (('a -> 'b) -> ('a -> 'b) -> 'c) -> ('a -> 'b) -> 'c *)
T.A.による解答
(*
 外側の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.ハノイの塔

(* 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"
 を呼び出すことによって定義できる。
*)
T.A.による解答
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