実例によるPureScript 4 章

演習はいっぱいあった。A, B, C, D, E とする。

演習 A.

演習 1. (簡単) 入力が偶数であるとき、かつそのときに限りtrueに返すような再帰関数を書いてみましょう。

こんなのでいいのかな。

isEven :: Int -> Boolean
isEven n =
  if n == 0
    then true
    else if n == 1
      then false
      else if n < 0
       then isEven (n + 2)
       else isEven (n - 2)

演習 2. (少し難しい) 配列内の偶数の数を数える再帰関数を書いてみましょう。ヒント:Data.Array.PartialモジュールのunsafePartial head関数を使うと、空でない配列の最初の要素を見つけることができます。

evenCount :: Array Int -> Int
evenCount arr =
  if null arr
    then 0
    else c + evenCount (unsafePartial tail arr)
      where c = if isEven (unsafePartial head arr)
        then 1
        else 0


演習 B.

演習 1. (簡単)map関数や<$>関数を使用して、 配列に格納された数のそれぞれの平方を計算する関数を書いてみましょう。

(\n -> n * n) <$> (1 .. 5)

演習 2. (簡単)filter関数を使用して、数の配列から負の数を取り除く関数を書いてみましょう。

filter (\n -> n >= 0) [-2, -1, 0, 1, 2]

演習 3. (やや難しい)filter関数と同じ意味の中置演算子<$?>を定義してみましょう。先ほどの演習の回答を、この新しい演算子を使用して書き換えてください。また、PSCiでこの演算子の優先順位と結合性を試してみてください。

infixl 4 filter as <$?>


演習 C.

演習 1. (簡単) factors関数を使用して、整数の引数が素数であるかどうかを調べる関数isPrimeを定義してみましょう。

isPrime :: Int -> Boolean
isPrime n =
  if n < 2
    then false
    else if length (factors n) > 1
      then false
      else true

演習 2. (やや難しい) 2つの配列の直積集合を見つけるための関数を書いてみましょう。直積集合とは、要素a、bのすべての組み合わせの集合のことです。ここでaは最初の配列の要素、bは2つ目の配列の要素です。

cartesianProduct :: forall a b. Array a -> Array b -> Array (Tuple a b)
cartesianProduct a b = do
  x <- a
  y <- b
  pure (Tuple x y)

演習 3. (やや難しい) ピタゴラスの三つ組数とは、a² + b² = c²を満たすような3つの数の配列[a, b, c]のことです。配列内包表記の中でguard関数を使用して、数nを引数に取り、どの要素もnより小さいようなピタゴラスの三つ組数すべてを求める関数を書いてみましょう。その関数はInt -> Array (Array Int)という型を持っていなければなりません。

pythagoras3 :: Int -> Array (Array Int)
pythagoras3 n = do
  a <- (1 .. n)
  b <- (a .. n)
  c <- (b .. n)
  guard $ a * a + b * b == c * c
  pure [a, b, c]

演習 4. (難しい) factors関数を使用して、数nのすべての因数分解を求める関数factorizationsを定義してみましょう。数nの因数分解とは、それらの積がnであるような整数の配列のことです。ヒント:1は因数ではないと考えてください。また、無限再帰に陥らないように注意しましょう。

難しかった… 直積集合使った。改善の余地がありそうだが、今後レベルアップした際に振り返りたい。

factorizations :: Int -> Array (Array Int)
factorizations n = nubBy compare (concatMap (\a -> factorsProducts a) (factors n))
  where
    factorsProducts :: Array Int -> Array (Array Int)
    factorsProducts [1, a] = [[a]]
    factorsProducts [a, 1] = [[a]]
    factorsProducts [a, b] = (\x -> sort $ (fst x) <> (snd x)) <$> (cartesianProduct (factorizations a) (factorizations b))
    factorsProducts _ = [[]]


演習 D.

演習 1. (簡単)foldlを使って、真偽値の配列の要素すべてが真かどうか調べてみてください。

foldl (&&) true

演習 2. (やや難しい) 関数foldl (==) false xsが真を返すような配列xsとはどのようなものか説明してください。

(||) ではなく (==) なので、最後の要素が false となっている配列、だと思う。

演習 3. (やや難しい) 累積器引数を使用して、次の関数を末尾再帰形に書きなおしてください

countTailCall :: forall a. (a -> Boolean) -> Array a -> Int
countTailCall = countTailCall' 0
  where
    countTailCall' acc _ [] = acc
    countTailCall' acc p xs = if p (unsafePartial head xs)
      then countTailCall' (acc + 1) p (unsafePartial tail xs)
      else countTailCall' acc p (unsafePartial tail xs)

生成された JS で tco (tail call optimization) が効いていることを確認した。この記事が大変勉強になった。https://23prime.hatenablog.com/entry/2018/12/02/230657 
例で示されている count 関数は count コール後に + 1 がついているので呼び出し後に計算が必要であるが、上記の countTailCall はそのような計算はないので大丈夫。

演習 4. (やや難しい)foldlを使ってreverseを書いてみましょう。

foldl (\x xs -> cons xs x) []

からの

foldl (flip cons) []


演習 E.

演習 1. (簡単) ディレクトリのすべてのサブディレクトリの中まで、ディレクトリを除くすべてのファイルを返すような関数onlyFilesを書いてみてください。

onlyFiles :: Path -> Array Path
onlyFiles file = if isDirectory file
  then concatMap onlyFiles (ls file)
  else [file]

演習 2. (やや難しい) このファイルシステムで最大と最小のファイルを決定するような畳み込みを書いてください。

maxSizeFile :: Path -> Path
maxSizeFile path = foldl maxPath path (onlyFiles path)
  where
    maxPath :: Path -> Path -> Path
    maxPath a b = if (size a) > (size b) then a else b

minSizeFile :: Path -> Path
minSizeFile path = foldl minPath path (onlyFiles path)
  where
    minPath :: Path -> Path -> Path
    minPath a b = if isNothing(sizeB) || (isJust(sizeA) && (sizeA < sizeB)) then a else b
      where
        sizeA = size a
        sizeB = size b

minSizeFile の Maybe の処理はもうちょっとなんとかなりそうだが… レベルアップして帰って来たときの課題にしよう。

演習 3. (難しい) ファイルを名前で検索する関数whereIsを書いてください。この関数は型Maybe Pathの値を返すものとします。この値が存在するなら、そのファイルがそのディレクトリに含まれているということを表します。この関数は次のように振る舞う必要があります。

whereIs :: String -> Maybe Path
whereIs s = head do
  file <- allFiles root
  child <- ls file
  guard $ s == (filename child)
  pure file

こっちの head は Data.Array の方の head (Data.Array.Partial ではない)。


この記事が気に入ったらサポートをしてみませんか?