7.2 パターンマッチングを用いたデータ構造の利用
問 7.5
2分木に対する以下の各関数を定義せよ.
-
1.
ノードの総数を求める関数 nodes.
-
2.
型が int tree である木を対象とし, ノードの値の総和を求める関数 sumTree.
-
3.
引数で与えられた関数を木の各ノードのデータに適用して得られる新しい 木を作る関数mapTree.
-
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.
’a newTree型の文字列表現の生成には, ’a型の文字列表現の生成が必要である. そこで, 木の文字列表現生成関数を, ’a -> string型の関数を引数として取る高階関数とする. 文字列表現からの復元関数も同様に, string -> ’a型の関数を引数として取る高階関数とする.
-
2.
string treeの場合, ‘(’と‘)’がノードの値に含まれないと仮定し, これらを,文字列表現における部分木の区切り記号として用いた. しかし,たとえばint * int型のデータを考えれば明らかなよう に,一般にこの仮定は成立しない. 区切り文字は,可能な限り,種々の文字列表現に現れないものを選ぶ必要がある. ここでは,文字列定数
"\000"
と"\001"
の2つを利用することとする. さらに,特別な型を処理したい場合に備え, 変数val lp = "\000" val rp = "\001"
を定義し,プログラムではこれらの変数を使用することとする.
-
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.
変換の対象とする木 t.
-
2.
木が Empty のときの値 z.
-
3.
左部分木と右部分木をそれぞれ treeFold した結果とノードのデー タから,木 Node(,,) の結果を計算する関数 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.
treeFold の定義を完成せよ.
-
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