Haskellで自然言語処理100本ノックの第1章を解いてみる。【後編】

# Haskellで自然言語処理100本の1章を解いてみます！
[Haskellで自然言語処理100本ノックの第1章を解いてみる。【中編〜bi-gramとは】](https://qiita.com/ayase/items/0deb22e34787ddcd3132)からだいぶ時間が空いてしまいました。
忙しくなってしまったとはいえ放置しすぎてすみません。仕事やめたので許してください。

# 問題と解答
## 06. 集合
>**"paraparaparadise"と"paragraph"に含まれる文字bi-gramの集合を，それぞれ, XとYとして求め，XとYの和集合，積集合，差集合を求めよ．さらに，'se'というbi-gramがXおよびYに含まれるかどうかを調べよ．**

```haskell:06.hs
module Main where

import qualified Data.List as List
import qualified Data.Set  as Set
import qualified System.IO as IO

ngram :: Int -> [a] -> [[a]]
ngram n xs  | n <= List.length xs = List.take n xs : ngram n (List.drop 1 xs)
            | otherwise           = []

main :: IO()
main = do
    let x               = Set.fromList $ Main.ngram 2 "paraparaparadise"  -- set X
        y               = Set.fromList $ Main.ngram 2 "paragraph"         -- set Y
        union           = Set.union         x y  -- union of the sets X and Y.
        intersection    = Set.intersection  x y  -- intersection of the sets X and Y.
        difference      = Set.difference    x y  -- difference of the sets X and Y.
    IO.print union
    IO.print intersection
    IO.print difference
    IO.print $ Set.member "se" x
    IO.print $ Set.member "se" y
```

```txt:出力
fromList ["ad","ag","ap","ar","di","gr","is","pa","ph","ra","se"]
fromList ["ap","ar","pa","ra"]
fromList ["ad","di","is","se"]
True
False
```

05 . n-gramの復習と集合の基礎を問う問題みたいな感じだろうか。
前問ができていればあとは`Set`を使えばいいだろう。

## 07. テンプレートによる文生成
>**引数x, y, zを受け取り「x時のyはz」という文字列を返す関数を実装せよ．さらに，x=12, y="気温", z=22.4として，実行結果を確認せよ．**

```haskell:07.hs
module Main where

import qualified Text.Show as Show
import qualified Text.PrettyPrint as PrettyPrint
import qualified System.IO as IO

answer :: (Show a, Show b, Show c) => a -> b -> c -> String
answer x y z = Show.show x ++ "時の" ++ Show.show y ++ "は" ++ Show.show z

main = do
    let x = 12
        y = PrettyPrint.text "気温"
        z = 22.4
    IO.putStrLn $ Main.answer x y z
```

```txt:出力
12時の気温は22.4
```
とりあえず引数`x`, `y`, `z`を`Show`クラスのインスタンスとして扱うことでそれっぽくしたつもりなのだが、、

ひっかかりポイントは`String`に`show`関数を適用すると`y`に"気温"を渡したときうまく表示されないこと。
今回は`text`関数を使い、`Doc型`に変換することできちんと表示された。

## 08. 暗号文
>**与えられた文字列の各文字を，以下の仕様で変換する関数cipherを実装せよ．**

>**英小文字ならば(219 - 文字コード)の文字に置換**
>**その他の文字はそのまま出力**
>**この関数を用い，英語のメッセージを暗号化・復号化せよ．**

```haskell:08.hs
module Main where

import qualified Data.List as List
import qualified Data.Char as Char
import qualified System.IO as IO

cipher :: Char -> Char
cipher c | isLower c = Char.chr $ 219 - Char.ord c
         | otherwise = c
        where isLower c = ('a' <= c) && (c <= 'z')

main :: IO()
main = do
    let msg    = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."    
        encrypt = List.map Main.cipher msg
        decrypt = List.map Main.cipher encrypt
    IO.print encrypt
    IO.print decrypt
```

```txt:出力
"Hr Hv Lrvw Bvxzfhv Blilm Clfow Nlg Ocrwrav Foflirmv. Nvd Nzgrlmh Mrtsg Aohl Srtm Pvzxv Svxfirgb Cozfhv. Aigsfi Krmt Czm."
"Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can."
```

試してみるとわかるが、`cipher(x) = x'かつcipher(x') = xが成り立つ`ので**復号化関数も`cipher`でよい。**

## 09. Typoglycemia
>**スペースで区切られた単語列に対して，各単語の先頭と末尾の文字は残し，それ以外の文字の順序をランダムに並び替えるプログラムを作成せよ．ただし，長さが４以下の単語は並び替えないこととする．適当な英語の文（例えば"I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ."）を与え，その実行結果を確認せよ．**

```haskell:09.hs
module Main where

import qualified Data.List as List
import qualified Control.Monad as Monad
import qualified System.IO as IO
import qualified System.Random.Shuffle as Shuffle

typoglycemia :: String -> IO String
typoglycemia xs = if List.length xs <= 4 then
                    return xs
                  else do
                    shuffled <- Shuffle.shuffleM $ List.tail $ List.init xs
                    return $ [List.head xs] ++ shuffled ++ [List.last xs]

main :: IO()
main = do
    let words = List.words "I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ."
    shuffled <- Monad.mapM Main.typoglycemia words
    IO.print $ List.unwords shuffled
```

```txt:出力
"I c'ondult bleieve that I cluod alltaucy uadtsrennd what I was rinadeg : the pnneoheaml pweor of the huamn mind ."
```

めんどうそうだったので`Shuffle`モジュール使わせてもらいました。
`Monad`と突き当たってますが、ここではあまり難しい操作していないので大丈夫でしょう。

# 第1章を終えて
Haskellはリストの操作が得意なのですが、HaskellではStringが[Char]の型シノニム（型エイリアス）であることから、必然的に文字列の操作も得意なようです。
前回からかなり間を空けてしまいましたが、そのうち他の章にもチャレンジするかもしれません。