プログラミング言語 Standard ML 入門 (問題の解答例)
7 データ構造の定義と利用

7.2 パターンマッチングを用いたデータ構造の利用

問 7.5

2分木に対する以下の各関数を定義せよ.

  1. 1.

    ノードの総数を求める関数 nodes.

  2. 2.

    型が int tree である木を対象とし, ノードの値の総和を求める関数 sumTree.

  3. 3.

    引数で与えられた関数を木の各ノードのデータに適用して得られる新しい 木を作る関数mapTree.

  4. 4.

    string tree 型データを,前節で解説した post-order表現,およびin-order表現に変換する以下の各関数.
    toPostOrder : string tree -> string toInOrder : string tree -> string  
    文字列の形式は,以前同様とする.

解答例 

   fun nodes Empty = 0
     | nodes (Node(_, L, R)) = 1 + nodes L + nodes R

   fun sumTree Empty = 0
     | sumTree (Node(n, L, R)) = n + sumTree L + sumTree R

   fun mapTree f Empty = Empty
     | mapTree f (Node(x, L, R)) = Node(f x, mapTree f L, mapTree f R)

   fun toPostOrder Empty = ""
     | toPostOrder (Node(a,b,c)) =
         "(" ^ toPostOrder b ^ ")"
       ^ "(" ^ toPostOrder c ^ ")"
       ^  a

   fun toInOrder Empty = ""
     | toInOrder (Node(a,b,c)) =
         "(" ^ toInOrder b ^ ")"
       ^ a
       ^ "(" ^ toInOrder c ^ ")"
問 7.6

本問では string tree型データとpre-order,in-order,post-orderの各文字列表 現との変換処理を,以下の方針に従い,7.1節で定義 した多相型の’a newTree型に一般化することを考える.

  1. 1.

    ’a newTree型の文字列表現の生成には, ’a型の文字列表現の生成が必要である. そこで, 木の文字列表現生成関数を, ’a -> string型の関数を引数として取る高階関数とする. 文字列表現からの復元関数も同様に, string -> ’a型の関数を引数として取る高階関数とする.

  2. 2.

    string treeの場合, ‘(’と‘)’がノードの値に含まれないと仮定し, これらを,文字列表現における部分木の区切り記号として用いた. しかし,たとえばint * int型のデータを考えれば明らかなよう に,一般にこの仮定は成立しない. 区切り文字は,可能な限り,種々の文字列表現に現れないものを選ぶ必要がある. ここでは,文字列定数 "\000" と "\001" の2つを利用することとする. さらに,特別な型を処理したい場合に備え, 変数

       val lp = "\000"
       val rp = "\001"
    

    を定義し,プログラムではこれらの変数を使用することとする.

  3. 3.

    ’a newTreeでは,子ノードが存在しないことを表現する必要が ある. 種々の表現が可能であるが,string treeの場合の関数の再利用性 などを考慮し,子ノードが存在しないことを, string treeの場合の空の木の表現"()"に対応させ, 文字列"\000\001"で表すことにする.

以上の方針に従い,文字列表現への変換とその逆変換を行う以下の関数を定義せよ.

   val newTreeToPreOrder : (’a -> string) -> ’a newTree -> string
   val newTreeToInOrder : (’a -> string) -> ’a newTree -> string
   val newTreeToPostOrder : (’a -> string) -> ’a newTree -> string
   val preOrderToNewTree : (string -> ’a) -> string -> ’a newTree
   val inOrderToNewTree : (string -> ’a) -> string -> ’a newTree
   val postOrderToNewTree : (string -> ’a) -> string -> ’a newTree

さらに,int * int型と文字列との変換関数を定義し, (int * int) newTree型に対して,上記の各関数のテストを行え.

解答例 

   datatype ’a newTree
     = Leaf of ’a
     | Node of ’a * ’a newTree * ’a newTree
     | NodeL of ’a * ’a newTree
     | NodeR of ’a * ’a newTree

   val lp = "\000"
   val rp = "\001"
   val LP = #"\000"
   val RP = #"\001"

   fun searchLP s p =
       if substring(s,p,1) = lp then p
       else searchLP s (p+1)

   fun searchRP s p n =
       if substring(s,p,1) = lp then searchRP s (p+1) (n+1)
       else if substring(s,p,1) = rp then
         if n=0 then p else searchRP s (p+1) (n - 1)
       else searchRP s (p+1) n

   fun decompose s =
      let val lp1 = searchLP s 0
          val rp1 = searchRP s (lp1+1) 0
          val lp2 = searchLP s  rp1
          val rp2 = searchRP s (lp2+1) 0
      in (substring (s,0,lp1),
          substring (s,lp1+1,rp1-lp1 -1),
          substring (s,lp2+1,rp2 - lp2-1))
      end

   fun decomposeIn s =
      let val lp1 = searchLP s 0
          val rp1 = searchRP s (lp1+1) 0
          val lp2 = searchLP s  rp1
          val rp2 = searchRP s (lp2+1) 0
      in (substring (s, rp1+1, lp2 - rp1 - 1),
          substring (s, lp1+1, rp1 - lp1 -1),
          substring (s, lp2+1, rp2 - lp2 - 1))
      end

   fun decomposePost s =
      let val lp1 = searchLP s 0
          val rp1 = searchRP s (lp1+1) 0
          val lp2 = searchLP s  rp1
          val rp2 = searchRP s (lp2+1) 0
      in (substring (s,rp2+1,size s - rp2 - 1),
          substring (s,lp1+1,rp1-lp1 -1),
          substring (s,lp2+1,rp2 - lp2-1))
      end

   fun newTreeToPreOrder toString t =
       let
         val preOrder = newTreeToPreOrder toString
       in
         case t of
           Leaf v => toString v ^ lp ^ rp ^ lp ^ rp
         | Node(v, L, R) => toString v ^ lp ^ preOrder L ^ rp ^ lp ^ preOrder R ^ rp
         | NodeL(v, L) => toString v ^ lp ^ preOrder L ^ rp ^ lp ^ rp
         | NodeR(v, R) => toString v ^ lp ^ rp ^ lp ^ preOrder R ^ rp
       end

   fun newTreeToInOrder toString t =
       let
         val inOrder = newTreeToInOrder toString
       in
         case t of
           Leaf v => lp ^ rp ^ toString v ^ lp ^ rp
         | Node(v, L, R) => lp ^ inOrder L ^ rp ^ toString v ^ lp ^ inOrder R ^ rp
         | NodeL(v, L) => lp ^ inOrder L ^ rp ^ toString v ^ lp ^ rp
         | NodeR(v, R) => lp ^ rp ^ toString v ^ lp ^ inOrder R ^ rp
       end

   fun newTreeToPostOrder toString t =
       let
         val postOrder = newTreeToPostOrder toString
       in
         case t of
           Leaf v => lp ^ rp ^ lp ^ rp ^ toString v
         | Node(v, L, R) => lp ^ postOrder L ^ rp ^ lp ^ postOrder R ^ rp ^ toString v
         | NodeL(v, L) => lp ^ postOrder L ^ rp ^ lp ^ rp ^ toString v
         | NodeR(v, R) => lp ^ rp ^ lp ^ postOrder R ^ rp ^ toString v
       end

   fun toNewTree decomp fromString s =
       let
          val (root,left,right) = decomp s
          val toTree = toNewTree decomp fromString
       in
         case (left, right) of
           ("","") => Leaf (fromString root)
         | (_, "") => NodeL (fromString root, toTree left)
         | ("",_) => NodeR (fromString root, toTree right)
         | _ => Node (fromString root, toTree left, toTree right)
       end

   fun preOrderToNewTree fromString s = toNewTree decompose fromString s
   fun inOrderToNewTree fromString s = toNewTree decomposeIn fromString s
   fun postOrderToNewTree fromString s = toNewTree decomposePost fromString s

int * int型に対する処理関の定義例。

   fun intIntToString (x : int * int) = Dynamic.format x
   fun stringToIntInt s =
       let
         val ss = Substring.full s
         val (x,y) = Substring.splitl (fn x => x <> #",") ss
         val x = Substring.dropl (fn x => x = #"(") x
         val x = Substring.string (Substring.takel Char.isDigit x)
         val y = Substring.dropl (fn x => x = #"," orelse Char.isSpace x) y
         val y = Substring.string (Substring.takel Char.isDigit y)
         val i1 = valOf (Int.fromString x)
         val i2 = valOf (Int.fromString y)
       in
         (i1,i2)
       end

   fun intIntNewTreeToPreOrder t = newTreeToPreOrder intIntToString t
   fun intIntNewTreeToInOrder t = newTreeToInOrder intIntToString t
   fun intIntNewTreeToPostOrder t = newTreeToPostOrder intIntToString t

   fun preOrderToIntIntNewTree s = preOrderToNewTree stringToIntInt s
   fun inOrderToIntIntNewTree s = inOrderToNewTree stringToIntInt s
   fun postOrderToIntIntNewTree s = postOrderToNewTree stringToIntInt s

以下は(int * int) newTreeに対するSML#でのテスト例である。

   # val T = Node((1,2), Leaf (2,3), NodeR((3,4), Leaf (4,5)));
   val T =
     Node ((1, 2), Leaf (2, 3), NodeR ((3, 4), Leaf (4, 5))) : (int * int) newTree
   # val preOrderOfT = intIntNewTreeToPreOrder T
   # val inOrderOfT = intIntNewTreeToInOrder T;
   val preOrderOfT =
     "(1, 2)\^@(2, 3)\^@\^A\^@\^A\^A\^@(3, 4)\^@\^A\^@(4, 5)\^@\^A\^@\^A\^A\^A"
     : string
   # val inOrderOfT = intIntNewTreeToInOrder T;
   val inOrderOfT =
     "\^@\^@\^A(2, 3)\^@\^A\^A(1, 2)\^@\^@\^A(3, 4)\^@\^@\^A(4, 5)\^@\^A\^A\^A"
     : string
   # val postOrderOfT = intIntNewTreeToPostOrder T;
   val postOrderOfT =
     "\^@\^@\^A\^@\^A(2, 3)\^A\^@\^@\^A\^@\^@\^A\^@\^A(4, 5)\^A(3, 4)\^A(1, 2)"
     : string
   # val T1 = preOrderToIntIntNewTree preOrderOfT;
   val T1 =
     Node ((1, 2), Leaf (2, 3), NodeR ((3, 4), Leaf (4, 5))) : (int * int) newTree
   # val T2 = inOrderToIntIntNewTree inOrderOfT;
   val T2 =
     Node ((1, 2), Leaf (2, 3), NodeR ((3, 4), Leaf (4, 5))) : (int * int) newTree
   # val T3 = postOrderToIntIntNewTree postOrderOfT;
   val T3 =
     Node ((1, 2), Leaf (2, 3), NodeR ((3, 4), Leaf (4, 5))) : (int * int) newTree
問 7.7

リストデータを処理するさまざまな関数は,高階関数 foldr を使って 簡単に定義できた. 2分木に対しても,foldr に対応する高階関数があると便利である. ’a tree を処理する汎用の高階関数 treeFold を書いてみよう.

まず treeFold の取るべき引数を考えてみよう. foldr の場合との対応から,以下の引数が必要であると推定できる.

  1. 1.

    変換の対象とする木 t.

  2. 2.

    木が Empty のときの値 z.

  3. 3.

    左部分木Lと右部分木Rをそれぞれ treeFold した結果とノードのデー タxから,木 Node(x,L,R) の結果を計算する関数 f.

処理の対象となる木の型を ’a tree とし,結果の型を ’b とす る. すると引数 z の型は ’b であるはずである. 変換関数 f は,木構成子 Node の型と対応させて,’a * ’b * ’b -> ’b の型を持つ関数と定義すると,treeFold を使って種々 の木変換関数を定義する上で都合がよい. 以上より treeFold は以下のような関数として定義できる.

   # fun treeFold f z Empty = z
       | treeFold f z (Node (x, L, R)) =
            ...
   val treeFold = fn : (’a * ’b * ’b -> ’b) -> ’b -> ’a tree -> ’b
  1. 1.

    treeFold の定義を完成せよ.

  2. 2.

    前節で定義した各関数を treeFold を使って定義せよ.

解答例  treeFoldの定義例は以下の通り。

   fun treeFold f z Empty = z
     | treeFold f z (Node(a,b,c)) = f (a,treeFold f z b,treeFold f z c);

上記の各関数は、treeFoldを使って以下の通り定義できる。

   fun nodes t = treeFold (fn (x,y,z) => 1 + y + z) 0 t
   fun sumTree t = treeFold (fn (x,y,z) => x + y + z) 0 t
   fun mapTree f t = treeFold (fn (x,y,z) => Node(f x, y, z)) Empty t
   fun toPostOrder t =
       treeFold
         (fn (x,L,R) => "(" ^ L ^ ")" ^ "(" ^  R ^ ")" ^ x)
         ""
         t
   fun toInOrder t =
       treeFold
         (fn (x,L,R) => "(" ^ L ^ ")" ^ x ^ "(" ^  R ^ ")")
         ""
         t