Scalaプログラミング4 Listを使ってみる
Beginning Scala programming4
初心者ぺちぱーがGitHubでScalaレッスンを始めたぞ。4回目はコレクションクラスのList
を使ってみよう。
引き続き、練習問題はここのページだ。今回の問題は素数を求めるやつだ。
- Scalaスケーラブルプログラミング第2版
- 作者
- Martin Odersky
- Lex Spoon
- Bill Venners
- 出版社
- インプレスジャパン
- 発売日
- 2011-09-27
- メディア
- 単行本(ソフトカバー)
- 価格
- ¥ 4,968
Lessen 4: Sieve of Eratosthenes
素数判定
与えられた数が素数かどうか調べる
あるいは与えられた数までの素数を列挙する
素数判定に使うアルゴリズムで有名なものはエラトステネスの篩だ。今回の問題では、wikipediaに書いてある手順をScalaで実装してみようと思う。
ステップ1: 整数を最初の素数である 2 から昇順で探索リストに羅列する。
ステップ2: リストの先頭の数を素数リストに記録する。
ステップ3: 前のステップで素数リストに加えられた数の全ての倍数を、探索リストから削除する。
ステップ4: 探索リストの最大値が素数リストの最大値の平方よりも小さい場合、素数リストおよび探索リストに残っている数が素数となる。探索リストの最大値が素数リストの最大値の平方よりも大きい場合、ステップ 2 に戻る。
step 1
まず、ステップ1はScalaだと簡単よね。1回目のレッスンでも出てきたやり方で出来る。
このrange
の範囲から素数を探すわけだ。
step2
ステップ2は、このrange
の先頭を取り出す。これもScalaだと簡単で、head
メソッドを使えばいい。
このlist
に判明した素数が記録されていくんだな、きっと。ここで使っているList
はimmutable.List
だから、実際には別のインスタンスになるんだと思う。range
も同じくimmutable.Range
なので、実際には同じインスタンスを使い回すわけではなくて、新しいインスタンスを作ることになるはずだ。
step3
ステップ3は、このlist
の最後の要素で割り算して余りが0の要素をrange
から消す。ここの処理にはfilter()
メソッドが使えそうだ。
filter()
メソッドに渡す引数は、条件となる関数だ。filter()
メソッドは条件が真になった要素を返すので、
余りが0の要素を消す -> 余りが0でない要素を残す
と読み替えると、こんな感じで書けるはずだ。
filter
filter()
の引数についてちょっと補足。この関数は高階関数なので、関数を引数にとる。引数になる関数は、
のような格好をしてる。T
型の引数を受け取って、Boolean
を返す関数だ。今はInt
型のコレクションを使っているから、Int
型の引数を受け取って、Boolean
を返す関数をfilter()
に渡している。JavaScriptとかPythonとかPHPでも、こんなやついたよね。
Function literal
コップ本の8章を見てみよう。p.153を見ると、こんな事をやっている。
JavaScriptでもよく見かける格好してるよね。無名関数の書き方と一緒だ。
filter()
に渡している引数は、この関数リテラルを使っている。関数定義して名前を付けなくてもいい場合は、便利よね。p.155にちょうどfilter()
を使った例が載っている。
REPL
ちょっとREPLで試してみよう。ターミナルを起動して、scala
って打ってみよう。
確かにrange1
から2の倍数が消えてるよね。ここまでは大丈夫そうだ。range1
の型がRange
じゃなくてVector
になってるけど、これもSeq
型の一つらしいぞ。後でちょっと調べてみよう。
step4
ステップ4は、ややこしそうだな。"探索リストの最大値が素数リストの最大値の平方よりも小さい場合、素数リストおよび探索リストに残っている数が素数となる。"と言っているのは、こういうことか。
最大値はmax
メソッドで取得できる。最後の条件式がelse
になる場合は、ステップ2に戻って、同じことを繰り返すと。再帰関数になりそうよね、これ。
::: operator
List
の要素と、別のList
の要素を合わせて、新しいList
を作る場合にはコロン3つの演算子を使う。
別の型からList
に変換するときはtoList
メソッドを使う。
Create function
まずは、ここまでの手順をまとめてみよう。
最後のステップ2に戻るところで、新しく作った素数リストのlist1
と、探索リストのrange1
を関数に渡さないといけないだろうから、この2つを引数にした方が良さそうだ。ちょっと書いてみよう。
これだけだと、最初にステップ1とステップ2を実行するところがないから、その関数も追加してみよう。
これで、toList(max)
を実行すると、max
までの素数を求める部分が出来たんじゃないか!?
Test your code!
どうも不格好な気もするけれど、ここまでの段階で書いたコードを整理してみたのが、このobject
だ。eratosthenesSieve()
メソッドは外部から呼ばれる事はないはずだから、private
を付けてみた。このオブジェクトが公開するのはtoList()
メソッドだけだ。
よし。じゃあテストしてみよう。はて?ところで、Scalaのユニットテストって何を使うんだろう。コップ本の14章を読んでみると、Scala向けのテスティングフレームワークって、いくつかあるらしい。
最初はxUnit使うのが無難だろうか。ScalaTest 1.9.1 (for 2.10.0+ and 2.9.0+)が現時点の最新版だったので、これを使ってみる。ScalaTestではいくつかのスタイルでテストコードが書けるらしい。xUnitっぽい書き方をするのは、FunSuiteかな。
Getting started with FunSuiteを参考に、テストコードを書いてみよう。
おお!エラーが出てないってことはうまく動いたんじゃないか!?やったね!Scalaでテストできるようになったよ!
FunSuiteのScalaDocを見てみると、assert()
だけじゃなくて、expectResult()()
も使えるみたいだ。これを使って、テストコードを書き換えてみよう。
最初の引数に期待値を渡して、次の括弧の引数に実行結果を渡す書き方なんだな。
Refactor
少し長くなってしまったので、リファクタリングは次回やろうと思う。
Conclusion
今回も色々出てきたな。まとめ。
head
、last
、max
、:::
、toList
など、List
やRange
の基本操作filter()
メソッドの使い方- 関数リテラルの使い方
- ScalaTest, FunSuiteでユニットテスト