プログラミング言語 Standard ML 入門 (問題の解答例)
16 データのフォーマッティング

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 を利用し, 与えられた部分文字列の先頭が整数nの表現であれば SOME n と残りの 部分文字列を返し,数字表現でなければ 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