プログラミング言語 Standard ML 入門 (問題の解答例)
16.3 書式付き書き出し処理
問 16.7
上で定義した formatData 関数を使って, string * string * string 型データと int * int * int list 型の データを受け取り,
- printData ("first", "second", "third") [(1,2,3),(4,5,6)]; first second third 1 2 3 4 5 6
のような形式でプリントする関数 printTriple を書け.
解答例
fun printTriple (s1, s2, s3) L = let val width = 10 fun prS s = print (formatData {kind=STRING, width=SOME width, align=RIGHT} (S s)) fun prI i = print (formatData {kind=INT StringCvt.DEC, width=SOME width, align=RIGHT} (I i)) fun printLine (i1,i2,i3) = (prI i1; prI i2; prI i3; print "\n") in prS s1; prS s2; prS s3; print "\n"; map printLine L end
問 16.8
第16.2節で定義した intScan を利用し, 与えられた部分文字列の先頭が整数の表現であれば SOME と残りの 部分文字列を返し,数字表現でなければ NONE と与えられた部分文字列その ものを返す関数
scanInt : substring -> int option * substring
を書け.
解答例
fun scanInt s = case intScan s of SOME (i,s) => (SOME i, s) | NONE => (NONE, s)
問 16.9
以上の関数定義をまとめて,以下のシグネチャを持つストラクチャFormat を構築せよ.
signature FORMAT = sig datatype kind = INT of StringCvt.radix | REAL of StringCvt.realfmt | STRING | BOOL datatype align = LEFT | RIGHT datatype format = LITERAL of string | SPEC of {kind:kind,width:int option,align:align} datatype argument = I of int | R of real | S of string | B of bool exception formatError val format : string -> argument list -> string val printf : string -> argument list -> unit end
ただし,printf 関数は,format 関数を使ってフォーマットした文字 列を標準出力に印字する関数である.
解答例
structure Format : FORMAT = struct exception formatError structure S = Substring datatype kind = INT of StringCvt.radix | REAL of StringCvt.realfmt | STRING | BOOL datatype align = LEFT | RIGHT datatype format = LITERAL of string | SPEC of {kind:kind,width:int option,align:align} datatype argument = I of int | R of real | S of string | B of bool fun formatData {kind,width,align} data= let val body = case (kind,data) of (INT radix,I i) => Int.fmt radix i | (REAL fmt,R r) => Real.fmt fmt r | (STRING,S s) => s | (BOOL,B b) => Bool.toString b | _ => raise formatError in case width of NONE => body | SOME w => (case align of LEFT => StringCvt.padRight #" " w body | RIGHT => StringCvt.padLeft #" " w body) end fun scanInt s = let val r= Int.scan StringCvt.DEC S.getc s in case r of NONE => (NONE,s) | SOME(n,s) => (SOME n,s) end fun oneFormat s = let val s = S.triml 1 s in if S.isPrefix "%" s then (LITERAL "%",S.triml 1 s) else let val (a,s) = if S.isPrefix "-" s then (LEFT,S.triml 1 s) else (RIGHT,s) val (w,s) = scanInt s val (c,s) = case S.getc s of NONE => raise formatError | SOME s => s in (SPEC {width=w,align=a, kind=case c of #"d" => INT StringCvt.DEC | #"s" => STRING | #"f" => REAL (StringCvt.FIX NONE) | #"e" => REAL (StringCvt.SCI NONE) | #"g" => REAL (StringCvt.GEN NONE) | _ => raise formatError}, s) end end fun parse s = let val (s1,s) = StringCvt.splitl (fn c => c <> #"%") S.getc s val prefix = if s1 = "" then nil else [LITERAL s1] in if S.isEmpty s then prefix else let val (f,s) = oneFormat s val L = parse s in prefix@(f::L) end end fun format s L = let val FL = parse (S.full s) fun splice (h::t) L = (case h of LITERAL s => s ^ (splice t L) | SPEC s => (formatData s (List.hd L) ^ (splice t (List.tl L)))) | splice nil l = "" in (splice FL L) end fun printf s L = print (format s L) end
問 16.10
日時を表す書式を以下のように定める.
%H | 24時間制の時間(00から23) |
%I | 12時間制の時間(01から12) |
%k | 24時間制の時間(0から23) |
%M | 分 |
%S | 秒 |
%d | 日(01から31) |
%m | 月(01から12) |
%Y | 年 |
format の定義を参考にして,日時に関する以下の埋め込み書式指定を含む 文字列を受け取り,日時をプリントする関数
showTime : string -> unit
を書け. たとえば
- showTime "The time is %H hour %M minutes on %m/%d/%Y.\n"; The time is 21 hour 04 minutes on 9/9/2000. val it = () : unit
のように動作をする.
解答例
structure ShowTime = struct local structure S = Substring in exception FormatError fun dH t = StringCvt.padLeft #"0" 2 (Int.toString (Date.hour (Date.fromTimeLocal t))) fun dI t = let val h = Date.hour (Date.fromTimeLocal t) val i = if h = 0 then 12 else if h > 12 then h - 12 else h in StringCvt.padLeft #"0" 2 (Int.toString i) end fun dk t = Int.toString (Date.hour (Date.fromTimeLocal t)) fun dM t = Int.toString (Date.minute (Date.fromTimeLocal t)) fun dS t = Int.toString (Date.second (Date.fromTimeLocal t)) fun dd t= StringCvt.padLeft #"0" 2 (Int.toString (Date.day (Date.fromTimeLocal t))) fun dm t = case Date.month (Date.fromTimeLocal t) of Date.Jan => "01" | Date.Feb => "02" | Date.Mar => "03" | Date.Apr => "04" | Date.May => "05" | Date.Jun => "06" | Date.Jul => "07" | Date.Aug => "08" | Date.Sep => "09" | Date.Oct => "10" | Date.Nov => "11" | Date.Dec => "12" fun dY t = Int.toString (Date.year (Date.fromTimeLocal t)) datatype spec = EMBED of Time.time -> string | LITERAL of string fun oneFormat s = let val s = S.triml 1 s in if S.isPrefix "%" s then (LITERAL "%",S.triml 1 s) else let val (c,s) = case S.getc s of NONE => raise FormatError | SOME s => s in (EMBED (case c of #"H" => dH | #"I" => dI | #"k" => dk | #"M" => dM | #"S" => dS | #"d" => dd | #"m" => dm | #"Y" => dY | _ => raise FormatError), s) end end fun parse s = let val (s1,s) = StringCvt.splitl (fn c => c <> #"%") S.getc s val prefix = if s1 = "" then nil else [LITERAL s1] in if S.isEmpty s then prefix else let val (f,s) = oneFormat s val L = parse s in prefix@(f::L) end end fun format s tm = let val FL = parse (S.full s) fun splice (h::t) = (case h of LITERAL s => s ^ (splice t) | EMBED f => f tm ^ (splice t)) | splice nil = "" in splice FL end fun showTime s = print (format s (Time.now())) end end
問 16.11
Format ストラクチャを用いて,第13章で作成したソート関 数の評価プログラムの印字処理部分を書き直せ.
解答例 以下に変更部分の例を示す。
fun evalSort L = let val L’ = map checkTime L val av = (foldr (fn ((_,_,x),y) => y+x) 0.0 L’)/(Real.fromInt (List.length L’)) fun printLine (n,a,c) = Format.printf "%20d%20d%20f\n" [Format.I n, Format.I a, Format.R c] in (Format.printf "%20s%20s%20s\n" [Format.S "array size", Format.S "milli-sec.", Format.S "micro s./nlogn"]; map printLine L’; print "------------------------------------------------------------\n"; Format.printf "%40s%20f\n" [Format.S "avarage", Format.R av] ) end