似非プログラマのうんちく

「似非プログラマの覚え書き」出張版

Scala でとことん FizzBuzz する(その 3)

さぁ、改造を続けよう。

FizzBuzz アルゴリズム再考

ちょっとひねって、次のような写像を考える。

何も書いていないところは空文字列を対応させていると考える。これを利用して、FizzBuzzアルゴリズムを少し変更する。

  1. 何らかの方法で "", "", "Fizz", "", "Buzz", "Fizz", "", "", "Fizz", "Buzz", "", "Fizz", "", "", "FizzBuzz", ... と繰り返される列を用意する。
  2. 1 から順に上記の列と対応させていく(zip)
  3. 上記で得られた列を順に処理していくとき、「数字と空文字列」の組み合わせのときは数字を、「数字と空でない文字列」の組み合わせのときは文字列を返すようにする。

これを Scalaトレイトを利用して抽象クラスとしてあらかじめ作っておく。

package jp.mydns.akanekodou.scala.fizzbuzz

abstract class FizzBuzz {
  protected val fb : Stream[String]

  def fizzbuzz : Unit = {
    1 to 100 zip fb map {
      case (n, "") => n
      case (_, s) => s
    } foreach println
  }
}

Java のインターフェースと違い、Scala のトレイトにはこうした共通処理をあらかじめ実装しておくことが出来る。今回、ついでなので表示するところまでまとめて実装してみた。

2015/04/12 追記 : 今回のケースではトレイトを使う必要はありませんでした。お詫びの上、通常の抽象クラスに訂正させていただきます。

もう一つ、Scala の機能である「暗黙引数」を利用している。一見するとメソッドの引数のデフォルト値をあらかじめ与えるのと似ているが、あちらはあらかじめ引数を初期化しておかないとダメなのに対し、こちらはメソッド呼び出し時までに暗黙引数を初期化・確定させておけば良い。

Java の抽象クラスは「抽象メソッドが含まれるクラス」であるが、Scala の場合は抽象メソッドに加えて上記のような抽象フィールドを含むクラスも抽象クラスとして定義しなければならない。

2015/04/12 追記 2 : MainFizzBuzz 系クラスを継承させる方法が何となく行儀が悪く見えたので止めました。

その他のファイルも適宜書き換えていこう。

package jp.mydns.akanekodou.scala.fizzbuzz

class FirstFizzBuzz extends FizzBuzz {
  def toFizzBuzz(n : Int) : String = {
    if (n % 3 == 0) {
      if (n % 5 == 0)
        "FizzBuzz"
      else
        "Fizz"
    } else if (n % 5 == 0) {
      "Buzz"
    } else {
      ""
    }
  }

  val fb = Stream.from(1).map(toFizzBuzz)
}

toFizzBuzz は最早 String 型しか返さないことが明白になった。

package jp.mydns.akanekodou.scala

import jp.mydns.akanekodou.scala.fizzbuzz._

object Main extends App {
  (new FirstFizzBuzz).fizzbuzz
}

Main.scala がだいぶすっきりした。

いよいよ本格的に改造していくぞ !

続きを読む