2011年3月2日水曜日

ScalaでFizzBuzz 無限リストで4 クラスにする

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク
前回ではdefでFizzBuzzの処理を整理してましたが、どうもmapした後のタプルの処理が汚いなー
という事でそこだけ吸収出来そうな仕組みを考えてみました。

Scala的な要素

  • Implicit Conversion
  • 無限リスト
  • Extractor Patternsによるパターンマッチ

ソース

FizzBuzzクラスを作成し、FizzBuzz#applyでどの数までやるか貰っています。
中では前回同様
3つ置きで"Fizz"が出てくるcycle
5つ置きで"Buzz"が出てくるcycle
1から始まるincrement
をzipp(拡張したメソッド)して、mapしてFBでマッチさせて、出す。感じです。

zippは内部的にzipとmapをしていて、単純にTuple2の中身をtoStringして連結しています。
これによって3つのリストをzippで連結した結果は
1, 2, Fizz3, 4, Buzz5, Fizz6, 7・・・
といったリストになってます。
FBオブジェクトのunapplyの中で、要素にFizzかBuzzが含まれるか判定しています。
マッチする場合は
case FB(x) => x.replaceAll("[0-9]","")
が実行され、数字が除去された文字列になります。

class FizzBuzz {
    //Seq系のクラスを拡張する。zippメソッドを追加している。
class Zipper[T](val l : Seq[T]){
def zipp[T](l1:Seq[T]):Seq[Any] ={
l.zip(l1).map(p=>p._1.toString + p._2.toString);
}
    }
    //implicit conversionでSeqをZipperに暗黙的に変換する
implicit def zipper[T](l:Seq[T]) = new Zipper(l)
    
    //指定したIterableを繰り返す無限リスト
def cycle[T](i:Iterable[T]) = Stream.continually(i).flatMap(v=>v) 

    //指定した値からインクリメントしていく無限リスト
def increment(begin:Int):Stream[Int] = Stream.cons(begin, increment(begin+1))

    //Extractor Patterns
object FB {
  def unapply(x: String) = if ((x.indexOf("Fizz")> (-1))||(x.indexOf("Buzz")>(-1))) Some(x) else None   
}

    //実行
def apply(i:Int)={
cycle(List("","","Fizz")).zipp(
cycle(List("","","","","Buzz"))).zipp(
increment(1))
.take(i)
.map(p => p match{
case FB(x) => x.replaceAll("[0-9]", "") 
case x => x
})
}
}
for(i <- new FizzBuzz()(100)) println(i)


課題

  • zippが問答無用でタプルをtoString連結してる
  • map内部の処理が結構無理やり

おわり

コードはSimply Scalaで試せます。
Scalaは面白いなーとつくづく感じました。まさにDSLですね。
FizzBuzzの為だけに用意された拡張。
これだけ色々出来ると、それぞれの担当がオレオレDSLを作ってカオスな感じになってしまいそう。
Scalaをチームでやる時は特にその辺のルール決めが重要だなぁとかなんとか感じました。
あと、可読性が微妙。Scalaのscaladocから実装を眺めてみると、大体いつも挫折します。
謎過ぎて。その辺は慣れなんですが、普及の妨げにはなるだろうなぁという印象です。
でも面白いよ!

おわり。

0 件のコメント:

コメントを投稿