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

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

問 6.8

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

  • •

    map

  • •

    @

  • •

    flatten

  • •

    filter

  • •

    permutations

解答例 

  • •

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

  • •

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

  • •

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

  • •

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

  • •

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

問 6.9

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

  1. 1.

    map, flatten, member, unique, prefixList, permutations の各関数.

  2. 2.

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

  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