プログラミング言語 Standard ML 入門 (問題の解答例)
6 リスト

6.5 リスト処理の一般構造と汎用のリスト処理関数

問 6.8

以下の各関数が行う処理に関して上記の分析を行い,対応するZfを MLの式として定義せよ.

  • map

  • @

  • flatten

  • filter

  • permutations

解答例 

  • map f L
    リストがnilの時はnil、 リストが h::t の形なら, 部分リストtに対する値Rを計算すると、 Rは、tfmapした結果であるから、 全体の結果はf h :: Rである。 従って、Z=𝚗𝚒𝚕f=fn (h,R) => f h :: R である。

  • L1 @ L2
    第一要素L1foldする。 L1nilの時は第2要素L2L1h::t の形なら, RtL2の連結結果のはずであるから、 全体の結果はh :: Rである。 従って、 Z=𝙻𝟸f=fn (h,R) => h :: R である。

  • flatten L
    リストがnilの時はnil、 リストが h::t の形なら, 部分リストtに対する値Rを計算すると、 hはリスト、Rtflattenした結果のはずであるから、 全体の結果はh @ Rである。 従って、Z=𝚗𝚒𝚕f=fn (h,R) => h @ R である。

  • filter P L
    リストがnilの時はnil、 リストが h::t の形なら, 部分リストtに対する値Rを計算すると、 Rtfilterした結果のはずであるから、 全体の結果は、もしP htrueならh @ RfolseならRである。 従って、Z=𝚗𝚒𝚕f=fn (h,R) => if P h then h::R else R である。

  • permutations
    リストがnilの時は[nil]、 リストが h::t の形なら, 部分リストtに対する値Rを計算すると、 Rtpermutationした結果のはずであるから、 全体の結果は、Rの各要素に対して、その可能な位置にhを挿入したリストすべてである。 従って、Z=[nil]f=fn (h,R) => foldr (fn (a,b) => insertAll h a @ b) nil R である。

問 6.9

以下のリスト処理関数を foldr を使って定義せよ.

  1. 1.

    mapflattenmemberuniqueprefixListpermutations の各関数.

  2. 2.

    リストと 条件(bool型の関数) を受け取り,リストの中に条件を満たす要素が存在するか否か判定する関数 existsおよび, リストの中のすべての要素が条件を満たすか判定する関数 forall. 定義上,任意の条件関数Pに対して,exists P nilfalseforall P niltrue を返すことに注意せよ.

  3. 3.

    整数リストのプレフィックス和を求める関数 prefixSum. ただし,整数リスト[a1,a2,,an]のプレフィックス和 は[a1,a1+a2,,a1+an-1,a1++an]である.

解答例 

   fun map f L = foldr (fn (h,R) => f h :: R) nil L

   fun flatten L = foldr (fn (h,R) =>  h @ R) nil L

   fun member e L = foldr (fn (h,R) => R orelse e = h) false L

   fun unique L =
       foldr (fn (h,R) => if member h R then R else h :: R) nil L

   fun prefixList L =
       foldr (fn (x,l)=> [x]::(map (fn y => x::y) l)) nil L;

   fun permutations L =
       let fun insertAll s nil = [[s]]
             | insertAll s (h::t) =
                 let val L = insertAll s t
                 in (s::(h::t)) :: (map (fn x => h::x) L)
                 end
       in foldr (fn (x,y) => foldr (fn (a,b) => insertAll x a @ b) nil y)
                [nil]
                L
       end

   fun exists p L =
       foldr (fn (y,r) => p y orelse r) false L

   fun forall p L =
       foldr (fn (y,r) => p y andalso r) true L

   fun prefixSum L =
       foldr (fn (x,y) => x::(map (fn z => z + x) y)) nil L
問 6.10

foldr の処理は,以下のような等式の結果を求めることと理解できる.

foldr f Z [a1,a2,,an]=f(a1,f(a2,f(,f(an,Z))))

これに対して,以下のような動作をする foldl もトップレベルで定義されている.

foldr f Z [a1,,an-1,an]=f(an,f(an-1,f(,f(a1,Z))))
  1. 1.

    foldl の定義を与えよ.

  2. 2.

    foldl を使って rev を定義せよ.

解答例 

   fun foldl f z nil = z
     | foldl f z (h::t) = foldl f (f (h,z)) t

   fun rev L = foldl (op ::) nil L
問 6.11

関係のリスト表現で,同一の要素が一回しか含まれないとき,正規形と呼ぶ ことにする. 上で定義した tc は,入力が正規形であっても結果は正規形とは限ら ない.

  1. 1.

    tc Rが正規形とならない正規形のリストRの例を挙げよ.

  2. 2.

    正規形の入力に対しては,必ず正規形の結果を返すように,tc の定 義を修正せよ.

解答例  正規形の入力Rに対して、tc Rが正規形にならない例: tc [(1,1),(1,2),(2,3)]

正規形を維持するためのtcの改良:簡単な解決法は、

   fun normalTc R = unique (tc R)
問 6.12

関係のリスト表現に関する以下の関数を定義せよ.

  1. 1.

    関係Rと組(a,b)に対して,(a,b)Rか否かを判定する関数 isRelated

  2. 2.

    関係Rと点aに対して,集合{x|(a,x)R}を求める関数 targetOf

  3. 3.

    関係Rと点aに対して,集合{x|(x,a)R}を求める関数 sourceOf

  4. 4.

    関係Rの逆R-1を求める関数 inverseRel

解答例 

   fun isRelated (R,a) = member a R

   fun targetOf (R,a) = filter (fn (x,y) => x = a) R

   fun sourceOf (R,a) = filter (fn (x,y) => y = a) R

   fun inverseRel R = map (fn (a,b) => (b, a)) R