(4年半ほど仕事にやられてましたが何とか元気です。)
先日ひさしぶりにFizzBuzzを書く機会があったので再学習中のScalaでもやってみました。
■ 無限リストによる実装
まず、1から始まるFizzBuzzの無限リストを100まで評価する方法で実装してみました。
// 1から100までのFizzBuzz // scalac FizzBuzz.scala // scala FizzBuzz object FizzBuzz extends App { // 数値をFizzBuzzに変換する関数 def toFizzBuzz(n: Int): String = n match { case _ if n % (3*5) == 0 => "FizzBuzz" case _ if n % 3 == 0 => "Fizz" case _ if n % 5 == 0 => "Buzz" case _ => n.toString } // 整数の無限リスト(n, n+1, n+2, …)の各要素に対してfを適用する関数 // -> Stream((n,f(n)), (n+1,f(n+1)), (n+2,f(n+2)), ?)となる def mapSeqNums[T](f: Int => T, n: Int): Stream[(Int, T)] = { (n, f(n)) #:: mapSeqNums(f, n+1) } // (1) 1から100までのFizzBuzzを作成 val fizzbuzz = mapSeqNums(toFizzBuzz, 1) take 100 // 表示 fizzbuzz.print }
無限リストの実現にはストリームを利用してみました。
ストリーム (Stream) はリストに似ているが、要素は遅延評価される。そのため、ストリームは無限の長さをもつことができる。呼び出された要素のみが計算される。他の点においては、ストリームはリストと同じ性能特性をもつ。 http://docs.scala-lang.org/ja/overviews/collections/concrete-immutable-collection-classes.html
● 実行結果
実行するとこうなります。
$ scala FizzBuzz (1,1), (2,2), (3,Fizz), (4,4), (5,Buzz), (6,Fizz), (7,7), (8,8), (9,Fizz), (10,Buzz), (11,11), (12,Fizz), (13,13), (14,14), (15,FizzBuzz), (16,16), (17,17), (18,Fizz), (19,19), (20,Buzz), (21,Fizz), (22,22), (23,23), (24,Fizz), (25,Buzz), (26,26), (27,Fizz), (28,28), (29,29), (30,FizzBuzz), (31,31), (32,32), (33,Fizz), (34,34), (35,Buzz), (36,Fizz), (37,37), (38,38), (39,Fizz), (40,Buzz), (41,41), (42,Fizz), (43,43), (44,44), (45,FizzBuzz), (46,46), (47,47), (48,Fizz), (49,49), (50,Buzz), (51,Fizz), (52,52), (53,53), (54,Fizz), (55,Buzz), (56,56), (57,Fizz), (58,58), (59,59), (60,FizzBuzz), (61,61), (62,62), (63,Fizz), (64,64), (65,Buzz), (66,Fizz), (67,67), (68,68), (69,Fizz), (70,Buzz), (71,71), (72,Fizz), (73,73), (74,74), (75,FizzBuzz), (76,76), (77,77), (78,Fizz), (79,79), (80,Buzz), (81,Fizz), (82,82), (83,83), (84,Fizz), (85,Buzz), (86,86), (87,Fizz), (88,88), (89,89), (90,FizzBuzz), (91,91), (92,92), (93,Fizz), (94,94), (95,Buzz), (96,Fizz), (97,97), (98,98), (99,Fizz), (100,Buzz), empty
● 無限ループにならない理由
コメント(1)の処理で無限ループに陥らないのはtake関数が必要とする所までしかmapSeqNums関数が評価されない為です。 式の評価順序は以下のイメージです。
■ 素直な実装
遅延評価による無限リストでの実装との比較のために正格評価による素直な実装もしてみました。
● Rangeによる実装
Rangeによる素直な実装はこうなりました。 最もポピュラーなやり方です。
val fizzbuzz = (1 to 100).map(n => (n, toFizzBuzz(n)))
● Listによる実装
Listによる実装にするとこうなりました。
val fizzbuzz = List.range(1, 100).map(n => (n, toFizzBuzz(n)))
● ListBufferによる実装
ListBufferでするとこうなりました。手続き的な書き方で手動による最適化です。
def range2(s: Int, e: Int): scala.collection.mutable.ListBuffer[Int] = { val list = scala.collection.mutable.ListBuffer[Int](); var i = s while (i <= e) { list += i i += 1 } list } val fizzbuzz = range2(1, 100).map(n => (n, toFizzBuzz(n)))
● ListBufferによる実装(fizzbuzzしながら)
ここまでの素直な実装では次の2回ループが走ってしまう問題があります。
- 1〜100のリストを作成する
- 各数値をFizzBuzzに変換する(map)
1のついでに2をすればループを1回に減らす事ができるのでやってみました。
def rangeWith[T](s: Int, e: Int, f: Int => T): scala.collection.mutable.ListBuffer[T] = { val list = scala.collection.mutable.ListBuffer[T](); var i = s while (i <= e) { list += f(i) i += 1 } list } val fizzbuzz = rangeWith[(Int, String)](1, 100, n => (n, toFizzBuzz(n)))
■ 性能評価
結局どれが速いのか手元の環境で実測してみました。
機器 | プロセッサ | メモリ | OS | scala |
---|---|---|---|---|
MacBook Air | 1.7 GHz Intel Core i7 | 8 GB 1600 MHz DDR3 | OS X 10.9.4(13E28) | 2.10.4 |
● 測定方法
1から1000000までのfizzbuzzを求めるようにして、処理を必ず評価させるため最後にListへ変換するコードを入れました。
val fizzbuzz = ... val result = fizzbuzz.toList // force // fizzbuzz.print
そしてtimeコマンドて実測し5回の平均を出しました。
$ time scala FizzBuzz
● 結果
無限リストによる実装
項目 | 1回目 | 2回目 | 3回目 | 4回目 | 5回目 | 平均 |
---|---|---|---|---|---|---|
real(s) | 3.689 | 3.290 | 3.298 | 3.598 | 3.641 | 3.5032 |
user(s) | 12.088 | 10.745 | 11.136 | 12.122 | 12.504 | 11.719 |
sys(s) | 0.288 | 0.290 | 0.259 | 0.274 | 0.243 | 0.2708 |
Rangeによる実装
項目 | 1回目 | 2回目 | 3回目 | 4回目 | 5回目 | 平均 |
---|---|---|---|---|---|---|
real(s) | 1.459 | 1.543 | 1.440 | 1.488 | 1.399 | 1.4658 |
user(s) | 4.399 | 4.645 | 4.282 | 4.446 | 4.163 | 4.387 |
sys(s) | 0.164 | 0.175 | 0.168 | 0.172 | 0.162 | 0.1682 |
Listによる実装
項目 | 1回目 | 2回目 | 3回目 | 4回目 | 5回目 | 平均 |
---|---|---|---|---|---|---|
real(s) | 2.854 | 2.844 | 2.929 | 3.029 | 2.915 | 2.9142 |
user(s) | 9.459 | 9.455 | 9.780 | 10.188 | 9.638 | 9.704 |
sys(s) | 0.228 | 0.227 | 0.226 | 0.235 | 0.232 | 0.2296 |
ListBufferによる実装
項目 | 1回目 | 2回目 | 3回目 | 4回目 | 5回目 | 平均 |
---|---|---|---|---|---|---|
real(s) | 2.358 | 2.360 | 2.313 | 2.962 | 2.362 | 2.471 |
user(s) | 7.387 | 7.780 | 7.447 | 9.938 | 7.765 | 8.0634 |
sys(s) | 0.221 | 0.199 | 0.206 | 0.216 | 0.201 | 0.2086 |
ListBufferによる実装(fizzbuzzしながら)
項目 | 1回目 | 2回目 | 3回目 | 4回目 | 5回目 | 平均 |
---|---|---|---|---|---|---|
real(s) | 1.956 | 1.958 | 2.027 | 1.973 | 1.980 | 1.9788 |
user(s) | 6.261 | 6.164 | 6.493 | 6.312 | 6.291 | 6.3042 |
sys(s) | 0.171 | 0.170 | 0.173 | 0.167 | 0.166 | 0.1694 |