Beginning Scala programming4
初心者ぺちぱーがGitHubでScalaレッスンを始めたぞ。今回は、4回目の問題をリファクタリングしてみよう。
-
Scalaスケーラブルプログラミング第2版
- 作者
- Martin Odersky
- Lex Spoon
- Bill Venners
- 出版社
- インプレスジャパン
- 発売日
- 2011-09-27
- メディア
- 単行本(ソフトカバー)
- 価格
- ¥ 4,968
Lessen 4: Sieve of Eratosthenes
前回はこんなobject
を作ってみた。
テストコードはこんなのを書いてみた。
Refactor Sieve of Eratosthenes
まずは、val
宣言しているところが冗長な気がするので、一度しか参照されてない部分については、ちょっと直してみよう。
toList(max)
の7行目のList(range.head)
は、初回は2であることが自明だから、List(2)
でも良さそうだ。
じゃあ、テストしてみよう。クラス名を変えただけで、テストコードの中身は一緒だ。
よし。ちゃんと動いてそうだな。
Remember FizzBuzz
eratosthenesSieve(list, range)
メソッドをよく見ると、ステップ3で作ったrange1
というコレクションに対して、ステップ4で別のコレクションを作っている。これって、2回目のFizzBuzzで見たコードとよく似てるコードだ。ステップ4もif
式じゃなくて、パターンマッチにしてみると、range
に対して、filter
した後、続けてmatch
と書けそうだ。
Placeholder syntax
コップ本のp.155を見てみよう。関数リテラルを簡潔にするプレースホルダー構文というのがあるらしい。
パラメーターが関数リテラル内で1度しか使われない場合には、1個以上のパラメーターのプレースホルダー(場所を用意していることを示す記号)として、アンダースコアを使うことができる。
例として載っているのは、0よりも大きい数字を返す処理だ。
ということは、MyFirstPrimeNumber3
の11行目はちょっと簡単になる。
テストしてみよう。テストコードは中身が一緒でクラス名をMyFirstPrimeNumber3
に変えただけなので省略。
よし。大丈夫だ。
List operation
さて、コップ本をまた開いてみよう。今度はp.324だ。表17.1 集合の一般的な操作 をよく見てみると、++
演算子というのがある。
型が違うコレクションでも自動的に変換して要素を追加したコレクションを作っているように見えるね。ちょっと試してみよう。
おお!動いた!左側の型に合わせて、要素を追加したコレクションを作る操作なのかな。
そうすると、MyFirstPrimeNumber3
の14行目の
この部分もtoList
する必要はなくて++
すればいいんだな。
Use head instead of last
コップ本のp.297を見てみよう。
headやtailはともに一定の時間で実行されるが、initとlastはリスト全体をたどらないと結果値を計算できないので、リストの長さに比例する計算時間を要する
と書いてある。11行目のlist.last
というのは、追加された値を示すわけだから、これも引数で渡すようにしてみよう。
テストーー
よしよし。ちゃんと動いてるぞ。
1 is not prime number
そういえば、1って素数じゃなかったよね。
1を渡したときのテストコードを追加してみよう。
あー。。なんか例外出てきた。14行目のrange1.max
がいけないみたいだ。要素が空の場合はmax
使えないよ?ってことか。
1以下の場合は素数の計算をする必要はなくて、List()
を返せばいいし、2の場合はList(2)
を返せばいいわけだから、toList(max)
を変更すれば良さそうだ。これはパターンマッチで出来そうだな。
テストコードにも、2と0と-1の場合を追加してみよう。
よし。なんとかなったぞ。
Tail recursion
IntelliJ IDEAを使っていると、今回作ったeratosthenesSieve
の左側に、サンジのグルグル眉毛みたいなマークが出ていないだろうか。
これは、この関数が末尾再帰だということを表すマークらしい。
のp.166を読んでみよう。最適化されるみたいね。
Scalaコンパイラーは末尾再帰を検出したら、パラメーターを新しい値に更新した後、再帰呼び出しを関数の冒頭にジャンプするコードに書き換えるのである。
以上からわかるように、問題を解決するために再帰アルゴリズムを使うことを躊躇してはならない。再帰による解は、ループを使った解よりも優美で簡潔になることがよくある。解が末尾再帰になっていれば、実行時に余計なオーバーヘッドがかかったりはしない。
Conclusion
前回書いたコードと比べると、ずいぶんScalaっぽくなったんじゃないか!?どうだろうか。次回はtoList()
を使ったcontains()
メソッドを作ってみようと思う。
まとめ!
- 関数リテラルのプレースホルダー構文いいよ!
++
演算子でList
とRange
から要素を合わせたコレクションを作れる
- 末尾再帰はScalaコンパイラーで最適化される