プログラミング言語 Standard ML 入門 (問題の解答例)
14 システム時計の利用

14.1 TimeTimer ストラクチャ

問 14.1

日時の表現と操作は DATE シグネチャを持つ Date ストラクチャで提供されている. DATE シグネチャはその型から容易にその機能を推定できる. 必要なら Date ストラクチャを使い,現在の日時と時刻を表す文字列 を返す関数
currentTime : unit -> string  
を定義せよ.

解答例  以下に定義と実行の例を示す。

   # fun currentTime () = Date.toString (Date.fromTimeLocal (Time.now()));
   val currentTime = fn : unit -> string
   # currentTime ();
   val it = "Sun Jul 19 10:17:42 2020" : string
問 14.2

関数 nlogn : int -> realintcomp : int*int -> order を定義 し,checkTime 関数を完成させよ.

解答例  以下に例を示す。 以下では、intcompは、Intストラクチャに定義されている compare関数を使用している。

   fun log2 x = Math.log10 x / (Math.log10 2.0)
   fun nlogn n = ((Real.fromInt n) * (log2 (Real.fromInt n)))
   fun checkTime n =
       let
         val array = genArray n
         val tm = timeRun ArrayQuickSort.sort (array,Int.compare)
         val nlognRatio = tm / (nlogn n)
       in
         (n, tm div 1000, nlognRatio)
       end
問 14.3

関数 printResult : int * int * real -> unit は, nの値,時間,時間とn/nlog(n)との比の値を受け取り, size=10000, milli-secs=40, micro-secs/n log(n)=0.30103000 の形式でプリントする関数である. printResult 関数を定義し, checkTime 関数を完成させ, 前節で作成したソートプログラムの評価を行え.

解答例  文脈から、完成させるべき関数は、checkTimeではなく、testSort関数である。 ここでは、SML#の動的型付け機構を使って定義されている以下の型の汎用の 清書関数

   # Dynamic.pp;
   val it = fn : [’a#reify. ’a -> unit]

を使った定義例を以下に示す。

   fun printResult (n, tm, ratio) =
       Dynamic.pp
        {"size" = n, "milli-secs" = tm, "micro-secs/n long(n)" = ratio}
   fun testSort n = printResult (checkTime n)

以下は、SML#の対話型環境下での実行結果である。

   # app testSort [1000, 10000, 1000000, 10000000];
   {"micro-secs/n long(n)" = 0.0, "milli-secs" = 0, size = 1000}
   {"micro-secs/n long(n)" = 0.0602059991328, "milli-secs" = 8, size = 10000}
   {"micro-secs/n long(n)" = 0.0834856521308, "milli-secs" = 1664, size = 1000000}
   {
     "micro-secs/n long(n)" = 0.0822069913873,
     "milli-secs" = 19116,
     size = 10000000
   }
   val it = () : unit
問 14.4

配列の大きさnのリストを受け取り,各nについて nの値,計算時間,nlog2(n)の値との比を

    - evalSort [10000,100000,1000000,10000000];
              array size          milli-sec.      micro s./n log(n)
                   10000                  30          0.22577250
                  100000                 460          0.27694760
                 1000000                5740          0.28798536
                10000000               66630          0.28653755
    ------------------------------------------------------------
                                     avarage          0.26931075

のような形式で表示する関数 evalSort を以下の手順で作成せよ.

  1. 1.

    20文字以下の大きさの文字列を受け取り,その前に空白文字を 付け加え,20文字の大きさの文字列を返す関数 padString : string -> string を定義せよ.

  2. 2.

    padString を使って,2つの整数と1つの実数を受け取り,以下のよう な形式でプリントする関数 printLine を定義せよ. - printLine; val it = fn : int * int * real -> unit - printLine (10000,30,0.22577250); 10000 30 0.22577250 val it = () : unit

  3. 3.

    以上を使って evalSort を定義せよ.

解答例  evalSortの定義例を以下に示す。 この例では、padStringを定義する代わりに、StringCvtストラクチャの padLeft関数を使用している。

    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’))
          val title = (StringCvt.padLeft #" " 20 "array size")
                      ^ (StringCvt.padLeft #" " 20 "milli-sec.")
                      ^ (StringCvt.padLeft #" " 20 "micro s./nlogn")
                      ^ "\n"
          fun formatReal a = StringCvt.padLeft #" " 20 (Real.fmt (StringCvt.FIX (SOME 8)) a)
          fun printLine (n,a,c) =
              let
                val ns =  StringCvt.padLeft #" " 20 (Int.toString n)
                val sa =  StringCvt.padLeft #" " 20 (Int.toString a)
                val sc = formatReal c
              in
                print (ns ^ sa ^ sc ^ "\n")
              end
        in
          (print title;
           map printLine L’;
           print "------------------------------------------------------------\n";
           print ("                                 avarage" ^ (formatReal av));
           print "\n")
        end

以下は、SML#の対話型環境下での実行結果である。 現在の実行環境では、大きさ10000の配列では短時間に終了し、誤差が大きくなるとため、 100000、1000000, 10000000、67108863(最大配列サイズ)のテスト行っている。

   # evalSort [100000, 1000000, 10000000, Array.maxLen div 4];
             array size          milli-sec.      micro s./nlogn
                 100000                 152          0.09151312
                1000000                1784          0.08950625
               10000000               20300          0.08729870
               67108863              144932          0.08306366
   ------------------------------------------------------------
                                    avarage          0.08784543
   val it = () : unit
問 14.5

関数 checkTime を一般化し,汎用のテスト 関数

    eval : {prog:’a->’b, input:’a, size:’a-> int, base:int -> real}
           -> int * real * real

を定義せよ. ただし,引数レコードの各フィールドの意味は以下のとおりとする.

フィールド 値の意味
prog 評価すべきプログラム
input プログラムへの入力
size 入力の大きさを返す関数
base 評価の基準となる関数

結果の型の意味は checkTime の場合と同様である.

解答例  checkTimeと同様であれば、結果の型はint * int * realである。 以下は定義例である。

   fun eval {prog, input, size, base} =
       let
         val tm = timeRun prog input
         val n = size input
         val ratio = Real.fromInt tm / base n
       in
         (n, tm div 1000, ratio)
       end
問 14.6

自然数のリストを受け取り,各要素nに対して,大きさnの乱数配列 を生成し,上記のようなn回の比較を行い,処理時間およびそのnとの比を

    - evalCompare [500000,1000000,5000000];
              array size          milli-sec.          micro s./n
                  500000                  40          0.08000000
                 1000000                  70          0.07000000
                 5000000                 370          0.07400000
    ------------------------------------------------------------
                                     avarage          0.07466667

のような形式で表示する関数 evalCompare を,eval を使って定義せよ.

解答例  以下に定義例を示す。

   fun evalCompare L =
       let
         fun comp array =
             let
               val n = Array.length array
               val p = Array.sub(array, 0)
               val m = n div 2
               fun  loop x = if x <= m then ()
                             else (Int.compare (Array.sub(array, n - x), p);
                                   Int.compare (Array.sub(array, x - 1), p);
                                   loop (x -1))
             in
               loop n
             end
         fun evalN n = eval {prog = comp, input = genArray n, size = Array.length, base = real}
         val L’ = map evalN L
         val av = (foldr (fn ((_,_,x),y) => y+x) 0.0 L’)/(Real.fromInt (List.length L’))
         val title = (StringCvt.padLeft #" " 20 "array size")
                     ^ (StringCvt.padLeft #" " 20 "milli-sec.")
                     ^ (StringCvt.padLeft #" " 20 "micro s./n")
                     ^ "\n"
         fun formatReal a = StringCvt.padLeft #" " 20 (Real.fmt (StringCvt.FIX (SOME 8)) a)
         fun printLine (n,a,c) =
             let
               val ns =  StringCvt.padLeft #" " 20 (Int.toString n)
               val sa =  StringCvt.padLeft #" " 20 (Int.toString a)
               val sc = formatReal c
             in
               print (ns ^ sa ^ sc ^ "\n")
             end
       in
         (print title;
          map printLine L’;
          print "------------------------------------------------------------\n";
          print ("                                 avarage" ^ (formatReal av));
          print "\n")
       end

以下にSML#でのテスト例を示す。

   # evalCompare [1000000, 10000000, Array.maxLen div 4];
             array size          milli-sec.          micro s./n
                1000000                   8          0.00800000
               10000000                  80          0.00800000
               67108863                 564          0.00840426
   ------------------------------------------------------------
                                 avarage          0.00813475
   val it = () : unit
問 14.7

evalSortevalCompare を組み合わせ,ソート関数の処理性能を

   - normalEvalSort [500000,1000000,5000000,10000000];
             array size       time in cunit             T/n log(n)
                 500000                2740          3.75926754
                1000000                5860          3.81825925
                5000000               33270          3.88323623
               10000000               68970          3.85195525
   ------------------------------------------------------------
                                    avarage          3.82817957
   The estimated sort time function: T(n) = 3.8 n log(n).

のような形式で表示をする関数 normalEvalSort を定義せよ.

解答例  以下に定義例を示す。

   fun evalCompare L =
       let
         fun comp array =
             let
               val n = Array.length array
               val p = Array.sub(array, 0)
               val m = n div 2
               fun  loop x = if x <= m then ()
                             else (Int.compare (Array.sub(array, n - x), p);
                                   Int.compare (Array.sub(array, x - 1), p);
                                   loop (x -1))
             in
               loop n
             end
         fun evalN n =
             let val array = genArray n
             in eval {prog = comp, input = array, size = Array.length, base = real} end
         val L’ = map evalN L
         val av = (foldr (fn ((_,_,x),y) => y+x) 0.0 L’)/(Real.fromInt (List.length L’))
       in
         av
       end
   fun evalSortN c L =
       let
         fun sort array = ArrayQuickSort.sort (array, Int.compare)
         fun base n = c * nlogn n
         fun evalN n = eval {prog = sort, input = genArray n, size = Array.length, base = base}
         val L’ = map evalN L
         val av = (foldr (fn ((_,_,x),y) => y+x) 0.0 L’) / (Real.fromInt (List.length L’))
         val title = (StringCvt.padLeft #" " 20 "array size")
                     ^ (StringCvt.padLeft #" " 20 "time in cunit")
                  ^ (StringCvt.padLeft #" " 20 "C/nlogn")
                  ^ "\n"
         fun formatReal a = StringCvt.padLeft #" " 20 (Real.fmt (StringCvt.FIX (SOME 8)) a)
         fun printLine (n,a,c) =
          let
            val ns =  StringCvt.padLeft #" " 20 (Int.toString n)
            val sa =  StringCvt.padLeft #" " 20 (Int.toString (Real.floor (real a * 1000.0/ c)))
            val sc = formatReal c
          in
            print (ns ^ sa ^ sc ^ "\n")
          end
         val C = Real.fmt (StringCvt.FIX (SOME 2)) av
       in
         (print title;
          map printLine L’;
          print "------------------------------------------------------------\n";
          print ("                                 avarage" ^ (formatReal av) ^ "\n");
          print ("The estimated sort time function: T(n) = " ^ C ^ " n log(n)\n"))
       end
   fun normalEvalSort L = evalSortN (evalCompare L) L

以下にSML#でのテスト例を示す。

   # normalEvalSort [1000000, 10000000, Array.maxLen div 4];
             array size       time in cunit             C/nlogn
                1000000              236268          7.43223684
               10000000             2756460          7.62717282
               67108863            20683151          7.75259017
   ------------------------------------------------------------
                                    avarage          7.60399994
   The estimated sort time function: T(n) = 7.60 n log(n)
   val it = () : unit