No Programming, No Life

プログラミング関連の話題や雑記

Groovyで関数型を意識したFizzBuzzを書いてみた

はじめに

なるべく関数型を意識して書いてみました。

なんとか140文字に収めることができました。

ノート

ごちゃごちゃしてて分かりにくいので、読みやすくなるように軽く解説。

aクロージャ
a={n,s->['']*n+s}

まずはこれですが、n(数値を想定)とs(文字列を想定)の2引数を受け取るクロージャをaという変数に格納しています。

[''] * n + s

の結果がこのクロージャの値になりますが、これは例えばn3で、s'fizz'だった場合、

['', '', '', 'fizz']

というリストになります。

fクロージャ
f={n->[0..n,a(3,'fizz')*n,a(5,'buzz')*n].transpose().collect{i,f,b->[f,b].any()?f+b:i}}

さて、次は長いですが、順番に見て行きましょう。
まずは、transposeですが、これは複数指定されたリストの縦を横を組み替えたリストにするメソッドです。たとえば、こんな感じです。

assert [['a', 'b', 'c'], [1, 2, 3]].transpose() == [['a', 1], ['b', 2], ['c', 3]]

注意点としては、長過ぎた要素は無視されることでしょうか。

assert [['a'],['b','b'],['c','c','c']].transpose() == [['a', 'b', 'c']]

さて、fクロージャに戻って、transpose()している部分を見てみましょう。

[0..n, a(3,'fizz') * n, a(5,'buzz') * n].transpose()

これは、

  • 第1要素が0〜nまでの数列
  • 第2要素が、先ほどのaクロージャで作った、['', '', '', 'fizz'] 配列を n 回繰り返した配列
  • 第3要素が、も同じく 'buzz'配列の n 回繰り返した配列

となりまして、それをtranspose()するわけですから、結果としては、以下のような配列となります。

[[0, '', ''], [1, '', ''], [2, '', ''], [3, 'fizz', ''], [4, '', ''], [5, '', 'buzz'], [6, '', ''], ...]

出来上がる配列の数は一番数が少ない 0..n に合わせられるのがミソですね。

さて、お次はcollectしている部分です。collectに渡しているクロージャはこんな感じになっています。

{ i, f, b -> [f, b].any() ? f+b : i}

これは、引数を3つ取り(それぞれ、数、fizz文字列、buzz文字列を想定)、結果の文字列へと加工している部分です。

たとえば、このクロージャに配列の [15, 'fizz', 'buzz'] を引数として渡すと、'fizzbuzz' という文字列が返却されます。

def toFizzBuzz = { i, f, b -> [f, b].any() ? f+b : i}
assert 'fizzbuzz' == toFizzBuzz([15, 'fizz', 'buzz'])

Groovyでは引数として配列を渡すと展開されて適用されますので、第1要素がi、第2要素がf、第3要素がbといったようにバインドされるイメージです。

[f, b].any() ? となっていますので、 'fizz' もしくは 'buzz' 文字列がある場合のみ、文字列を結合した結果とし、そうでなければ、数値の i を返却します。

fクロージャの戻り値

ここまでで、fクロージャはfizzbuzzの配列を返却できています。これでほぼ完成なのですが、あとは出力処理を行うのみとなります。

fクロージャを利用して結果出力
f(100).drop(1).each{println(it)}

ここでは、fクロージャに引数として100を与え、0〜100までのfizzbuzzリストを取得しています。
ただし、fizzbuzzは1〜nのという条件ですので、drop(1)で、1つ要素を取り覗いています。
あとはおなじみ、eachで回して出力しているだけです。

おわりに

Groovyでも書こうと思えばそれなりに関数型っぽく書けました。
以上、誰かの何かの参考になれば幸いです。

関連記事