ZODIACの黙示録

なにもしないことをする

「Haskell」で「Look and Say 数列」を生成してみた

Haskell」について

どうも。ゾディアックです。

私はしがない大学生でございます。

今回は関数型言語で有名な「Haskell」で「Look and Say 数列」を生成してみました。

Haskellって何って言われたら、まあ、語らなくちゃいけなくなるのでアレなんですが、

まぁ、軽く言うと「純粋関数型言語」であるという説明でいいでしょう。

みなさんがよくご存知の、「C言語」は、俗に「手続き型言語」と言われております。

関数を書くときにごちゃごちゃしちゃいます。

直感的に書けません。数式で書けません。関数を引数にとれません。

そもそも関数っていうのはですね、数学の分野です。

関数の定義に倣って欲しいし、一つの引数を受け取って一つの返り値を返して欲しいですよね。

Haskellの関数は基本的に引数を一つしか取れません。

ですが、カリー化という手法で、「add x y = x + y」みたいな多価関数が実現出来ます。

こんな感じの言語を「関数型言語」って言うんじゃないの?って思ってます。

そして「純粋」っていうのが「副作用」が無いって意味なんですが話すと長くなり、面倒なのでやめておきます。

Haskell」の説明はこの程度にしときます(汗)

本題に入りましょう。

本題

「Look and Say 数列」とは

まず、「Look and Say 数列」っていうのは、

「1,11,21,1211,111221,312211,132221,...」って数列なんです。

法則性が見えますか...?

私は説明されるまでわかりませんでした(汗)答えを聞いてナルホドって感じでした。

答えを言うとですね、まず「見る」。

「1」です。そして「言う」。

"1"コの"1"です。

次の項は「11」になります(笑)。

「11」です。"2"コの"1"です。よって次の項は「21」になります。

わかります。「しね」って感じですよね(笑)。

この要領で生成していくんですね。

今回はこの数列を「Haskell」で生成してみようと言う話です。

Haskell」で書いてみる

まず実際のコードを

import Data.List

reverseRle :: [Char] -> [Char] 
reverseRle = concatMap (\s -> show (length s) ++ [head s]) . group

las :: [[Char]]
las = ["1"] ++ (map reverseRle las)

これだけです!Haskellはやっぱすげぇ!


そして、「group」関数を使いたいので、「Data.List」をインポートします。

「group」関数っていうのは、文字列が与えられた時に、
同じ要素が続いた箇所をグループにします。

「"AAABBBBBCC"」であったら「["AAA","BBBBB","CC"]」と返します。

そして、「reverseRle」は引数取ってねぇじゃんって思うかも知れませんが、違います。

引数は取ります。合成関数の「.」が効いてます。

「reverseRle a」としたら「a」が引数なんですが、

まずは「group」関数を適用して、次に「.」以前を適用します。

そしてその結果をラングレス圧縮します。そして「reverse」します。

ラングレス圧縮っていうのが、以下のコードで書けます。

import Data.List

rle :: [Char] -> [Char] 
rle = concatMap (\s -> head s : show (length s)) . group

入力は文字列、つまり[Char]じゃないとダメです。返り値も[Char]を返します。

例えば「rle "AAABBBBBCC"」とやったら、「"A3B5C2"」って返ってきます。

これが、「"3A5B2C"」になって欲しいので、「reverseRle」が必要なんですね。

そもそも何でラングレス圧縮するのかということですが、

課題の数列が、前の項の、同じ要素が続いた箇所をグループにし、その文字の連続回数とその文字をくっつけた数字(型的には[Char])を次の項とするような数列だからです。

「show」は「Int -> [Char]」です。「111」を「"111"」にして返します。

「length」は「"111"」であったら「長さ」を返すのでこの場合は「3」を返します。

「length」は「[Char] -> Int」なので、いじりやすくするために、文字列にしたいので、「show」で文字列に調整します。

「head s」で「s :: [Char]」なんですが、返り値が「Char」になってしまうので困ります。

"[ ]"でくくって[Char]にします。

先ほどの「reverseRle」ですが、無名関数(「\」のラムダ式)で、「s」として引数をとってますが、

「s」はgroupされた["1","11","21",...]とあったら、このなかの各要素に、

headとlengthしてshowした値をくっつけた値を返しています。

これは「map」関数で、各要素に対して、第一引数の関数を適用していきます。

「concatMap」関数ですが、これは第一引数で「map」して、結果を「concat」するものです。

例えば、「concatMap ("1"++) ["1","2"]」ってやりますと、

「"11"と"12"」が生成されて、最終的にこれらをくっつけるので、「"1112"」が返ってきます。

この関数だと、例えば、「s」が「"21"」だったら、「"1211"」を返します。

「"2" ++ "1"」でくっつけているんですね。「++」は文字列同士の結合です。

「:」はリストの先頭にくっつけます。

例えば、「"11" : ["21"]」でしたら、「["11","21"]」となります。

これで、reverseRle関数については分かって頂けると思います。

次行きます。「las」関数です。これは「"l"ook "a"nd "s"ay 数列」なので「las」です(わかりづらい)。

las関数は再帰的に定義されています。

las関数の中でlas関数が使われてますから「再帰的」ということです。

まずは、初項は「"1"」ですね。

そして、「map」関数で「reverseRle」関数をそれぞれの要素に適用してやります。

Haskellは「遅延評価」が採用されていますから、lasの値が決定されてねぇじゃんってならないんです。

初項は「"1"」ですから、先ほどの「reverseRle」関数をこれに適用させます。

すると返り値は「"11"」です。

今の段階でlasは["1","11"]です。

次に適用するのは、「"11"」です。返り値は「"21"」です。

今の段階でlasは["1","11"."21"]です。

この要領で次々に無限に生成できます。

定義的には無限リストです。

は?やばくね?って普通の人は思うかも知れませんが、さっきも言ったように、Haskellは遅延評価を採用していますから、大丈夫なんです。必要なときに評価するのです。

上の関数を、Haskellの式を対話的に評価したりプログラムを解釈実行したりできる、GHCiというGHCの対話環境を使って、loadして使うといいですね。

「ghci」でGHCiは起動できます。

そして「:l 「haskellファイル」」

でloadしてやります。

lasは無限リストなので、「take」とか「!!」を使ってやって処理しましょう。

「take 10 lst」ってやると、lstを10項分返してくれます。

「lst!!10」ってやると10項目じゃなくて11項目が返ってきます。Haskellのリストは0からですから。

まぁこんな感じです。ありがとうございました。