🐥

アルゴ式 「文字コード」をHaskellで解いて、その復習

に公開

Haskellは使っている人が少ないので、初学者でも把握できて見本に出来るようなコードを見れる機会が少ないと思います。そこで、初学者向けの問題と解説を行っている「アルゴ式」のサイトの問題を実際に解いたコードと、その解き方の復習を記しておきたいと思いました。

つまりは、初学者による初学者のためのHaskellコード 集がここに爆誕!していきます。

問題

アルゴ式の「コーディングによる問題解決」にある「ロジック実装中級」の「文字と文字列」の中の問題「文字列コード」を解いたときの復習ノートです。

問題「文字コード」は以下のページ。
https://algo-method.com/tasks/14c32f697c2ffd9b

この問題の中心は、文字コードと文字の相互変換を行う処理です。

文字と文字コードはordchr

文字と文字コードの行き来は、ord関数chr関数を使うのが定番です。

関数 処理
chr 文字コードの数値を文字に
ord 文字を文字コードの数値に

この関数はData.Charモジュールをインポートする必要があります。
ghciなら:mを使って読み込みます。

ghci> :m Data.Char

文字コードを文字にするならchr。なので、'a'に対応する数値97を渡して実験してみましょう。

print $ chr 97

文字から文字のコードにしたい場合は、ord(ordは、orderつまり順序の略)。
そこで、ord に直接'a'を渡してコードを確認してみましょう。

print $ ord 'a'

使いどころ

シーザー暗号系の文字コードをいくつかずらす系の場合、
この関数で文字を数値に変換することで数値的に処理できます。

解答例

問題では、出力するフォーマットが指定されています。
なので、数値を受け取ってshowchrを組み合わせて文字列にする関数を定義し、これをmap系で数値リスト(指定の97から122)を関数に一個づつ渡す流れが良さそうです。

解答例
import Data.Char

main :: IO ()
main = do
    mapM_  (putStrLn . solve)  [97..122]

solve :: Int -> String
solve i = show i ++ " " ++ [chr i]

この解答例を組み立てるために必要なhaskell基礎知識は以下の通りです。

文字と1文字の文字列の相互変換なら[]head

haskellでの「文字列」は「文字のリスト」ということを把握するのは必須。
文字列はリストなので++演算子で結合できます。また、そもそも、文字列は、出力の際にputStrLnで出力しますが、これに文字を混在できないので、文字を文字列へ変換する処理が必要があります。

文字を1文字の文字列にするのは[]でくくってリストにすれば良いです。

print ['a']

逆に1文字の文字列を文字にするには、headで一番はじめの要素を取れば良いです。

print $ head "a"

この文字列から文字への処理は、更に、ordを使って文字コードにする処理の場合に活用できます。

数値と文字列の相互変換ならshowread

haskellで数値とその数値の文字列の相互変換はshowreadを使うのが定番。
これらが対になっていることを把握しておきます。

関数 処理
show 数値を文字列に
read 文字列を数値に

本来的には文字通り、showは、内容を表示するために文字列化するもので、
readはデータ内容を表現した文字列をそのデータの値として読むものです。
なので、特にreadを利用する場合、原則として、値の型を指定する必要があることを意識しておきましょう。

ghciで以下を試すと、出力が"で囲われて文字列であることがわかります。

print $ show 1 

一方、ghciで以下を試すとパース(読めない)とエラーが出ます。
これは、"1"を何の値として読めばよいかがわからないからです。

print $ read "1" 

なので、readがすべきことを丁寧(型シグネチャ)に書いてあげれば、ちゃんと読み込めます。

例えば、整数(Int)なら

print $ (read :: String -> Int) "1" 

じゃなくて、実数(Double)なら

print $ (read :: String -> Double) "1" 

リストを改行区切りで出力するならmapM_

mapM_関数の1つ目の引数に標準出力へ表示させるIOアクションです。
よくあるパターンは、putStrLnprintをおいて、その後にリストを置きます。
mapM_の最後のアンダーバー'_'を忘れないように!

数値等ならprint

mapM_  print [1..10]

文字列ならば、putStrLn

mapM_  putStrLn ["osaka","kyoto","nara"]

リスト処理の考察と関数の引数の把握

mapM_の1つ目の引数には(a->IO())のような関数、つまり、ある値をとってIOアクションを返すような関数がきて、2つ目の引数がリストになります。

上の例では、第1引数の関数自体合成関数にして、数値を受け取っています。

    mapM_  (putStrLn . solve)  [97..122]

これを、第一引数の関数をputStrLnにして、文字列を受け取るならば、次のようになります。

    mapM_  putStrLn $ solve [97..122]

haskellでは、各ワードの結合の強さを意識、特に、演算子がないもワードの羅列がもっとも結合が強いことに意識して、関数の引数がどうなっているかを把握するのに慣れる訓練が必要です。

Discussion