昨年からぼつぼつと Haskell の本を読んでいたのだが、なかなか習得にはおぼつかない。
そんなところ、開発中のプログラムのテストデータを作成する必要に迫られ、ふと、このテストデータ生成を Haskell で書いたらどうなるか、と思い立ち、ちょっと試してみた。
テストデータ生成の要件はこんな感じ。
[1,2,3,4,5] といったリストがあるので、このリスト中の要素の出現順序を乱数に基づいてランダムに並べ替え、たとえば [5,1,3,2,4] といったようなリストを得る、というもの。
実際のテストデータ生成要件はむちょっと込み入っているが、骨子としては、上記生成アルゴリズムが実装できればあとはたやすい。
Haskell で乱数を扱うとなると、参照透明性の破壊、ということでモナドの出番ということになるそうだ。モナドについての理解が浅いものの、とりあえず無限リストとして乱数列をとりだせば取り扱いは簡単だということがわかり、乱数の取り扱いはかろうじて突破。
ということで Haskell (処理系はGHC)で作成したプログラムはこちら。
import Random
main = do
gen <- getStdGen
rnds <- return (randomRs (0.0,0.999) gen)
print (lesson1 rnds)
lesson1 rnds = shuffle rnds [1,2,3,4,5]
getat::Int->[a]->a
getat index = head.reverse.take (index+1)
removeat::[a]->Int->[a]
removeat items index = (take index items) ++ (reverse.take ((length items)-index-1).reverse) items
shuffle :: [Double]->[a]->[a]
shuffle rnds [] = []
shuffle (r:rnds) items = shuffleCore (randomindex r items) rnds items
randomindex::Double->[a]->Int
randomindex r = floor.(r*).fromIntegral.length
shuffleCore::Int->[Double]->[a]->[a]
shuffleCore index rnds items = ((getat index items):(shuffle rnds (removeat items index)))
...思ったより長いコードになった。
これがベストなコードなのかは、まだ自分では見極められるレベルにない。
実際、つい先だってまで、リストの連結に ++ を使うことがわからず、リストを連結する関数を自作していたので、上記コードよりまだ数行多かった。
また、リスト中の任意のインデックスの要素を取り出す関数とか、任意のインデックスの要素を削除する関数を自作しているが、このようなリストを操作する関数は処理系にはじめから付属しているのではないかという気もするが調べきれず。
それに、そもそものアルゴリズムがこれでよいのかもまだ自信が持てない(なにぶん、まだ"関数"脳になりきれていないので)。
とはいえ、とりあえず一歩だ。