最終鬼畜FizzBuzz大全

  • 58
    いいね
  • 2
    コメント
この記事は最終更新日から1年以上が経過しています。

2014年になった。いまや職業でプログラマを名乗る全ての人間は、すくなくとも自分の得意な言語でFizzBuzzを解くことができるようになったはずだ。よって、FizzBuzzについて詳細に語ることについては何らの支障もない。

タイトルは釣りだ。この記事にはふつうのFizzBuzzしかない。あと二年も経てば誰だって婆だってFizzBuzzが書けるようになることに疑問はない。

前史

Fizz Buzz とは、元は英語圏における単純なパーティゲームだとのことだ。

このゲームがプログラマに注目されたのは、Jeff AtwoodによるエッセイWhy Can't Programmers.. Program?(日本語訳:どうしてプログラマに・・・プログラムが書けないのか?)による。Jess AtwoodはStack Overflowの創業者のひとりだ。

基本ルール

パーティゲームとしての基本ルールについて、Wikipedia日本語版より引用する。

プレイヤーは円状に座る。最初のプレイヤーは「1」と数字を発言する。次のプレイヤーは直前のプレイヤーの次の数字を発言していく。ただし、3で割り切れる場合は 「Fizz」(Bizz Buzz の場合は 「Bizz」)、5で割り切れる場合は 「Buzz」、両者で割り切れる場合は 「Fizz Buzz」 (Bizz Buzz の場合は 「Bizz Buzz」)を数の代わりに発言しなければならない。発言を間違えた者や、ためらった者は脱落となる。

Fizz Buzz - Wikipedia 日本語版 (2014年3月18日 (火) 06:51の版)‎ より引用

これをコンピュータで表現するために、「どうしてプログラマに・・・プログラムが書けないのか?」では以下のように定義される。

1から100までの数をプリントするプログラムを書け。ただし3の倍数のときは数の代わりに「Fizz」と、5の倍数のときは「Buzz」とプリントし、3と5両方の倍数の場合には「FizzBuzz」とプリントすること。

Jeff Atwood著, 青木靖訳, どうしてプログラマに・・・プログラムが書けないのか? より引用

これらを敷衍して、以下のようにルールを定める。 (これは飽くまで筆者独自の定義である)

FizzBuzz三要素

FizzBuzzの実装には、対象とするプログラミング言語の以下の要素を利用できることを要求する。

  1. 入出力
  2. 反復
  3. 条件分岐

このうち 入出力 はJeff Atwoodの記事では特に重要視されない概念だが、これを明文化することで、この単純な問題に一つの実践的な要素を取り入れることができる。

正則ルール

後述する入出力の分類により、標準入力と標準出力を使ったものを 正則 と呼ぶ。

  1. 標準入力より、十進数0-9によって構成される文字列が入力される
    • 1以上の正の整数であり、不正な形式を想定する必要はない
    • その言語の数値型において整数として利用できる範囲であることを前提としてよい
  2. 1からその数まで以下の規則に従って変換したものを改行文字\n区切りで標準出力に出力する
    • その数が35で割り切れる場合はFizzBuzz
    • その数が3のみで割り切れる場合はFizz
    • その数が5のみで割り切れる場合はBuzz
    • その数が35のどちらでも割り切れない場合は、その数を十進数として
  3. 例外: 標準入力と標準出力が利用できない環境では、これに代るものを利用してよい

シェルスクリプトで実装するとこうなる。後述する分類では 正則+逐次+個別分岐 である。

fizzbuzz.sh
#!/bin/sh

print_fizzbuzz()
{
    expr $1 % 3 = 0 > /dev/null && /bin/echo -n Fizz
    expr $1 % 5 = 0 > /dev/null && /bin/echo -n Buzz
    expr $1 % 3 = 0 \| $1 % 5 = 0 > /dev/null || /bin/echo -n $1
    echo
}

read n; i=1
while [ $n -ge $i ]
do
    print_fizzbuzz $i
    i=`expr $i + 1`
done

3の例外を使った例として、ブラウザにおけるJavaScriptでは出力にconsole.log()document.write()などを利用してもよい。ただし、入出力以外の条件は変更されないので、出力中に<br />などを含めてはいけない。

<!DOCTYPE html>
<title>FizzBuzz</title>
<style>#OUTPUT{height: 50em;}</style>
<input id="INPUT" value="100" />
<textarea id="OUTPUT"></textarea>
<script>
(function(){
    "use strict";

    var n = +(document.getElementById('INPUT').value),
        output = document.getElementById('OUTPUT'),
        print = function (val) {
            return output.value = output.value + val + "\n";
        };

    for (var i = 1; i <= n; i++) {
        var s = "";
        if (i % 3 === 0) { s += "Fizz"; }
        if (i % 5 === 0) { s += "Buzz"; }
        if (s === "")    { s += i; }

        print(s);
    }
}());
</script>

変則ルール

上記の規則に従って変換するが、入出力の形式を自由に定義することができる。

例として、VisualBasicなどでテキストボックスを利用する、ブラウザにおけるJavaScriptでwindow.prompt()window.alert()を利用する、などである。

// 変則+逐次+四分岐
javascript:var i=0,n=prompt();while(n>=++i)alert(i%15?i%3?i%5?i:"Buzz":"Fizz":"FizzBuzz");

制約に束縛されず、あなた自身のニューロンを羽ばたかせよう。多様性!

実装パターン

入出力

この記事では標準入力と標準出力を利用するものを 正則、それ以外の入出力を使ったものを 変則 と分類する。

正則の仕様においては、オプション引数 ではなく 標準入力を利用することに注意されたい。

反復

反復には、ループ内で 逐次 に出力するパターンと、結果を 一括 で出力するパターンがある。

詳細な実装手法には、カウンタ変数を直接参照するもの、リストを再帰的に処理するもの、範囲オブジェクトを使ったもの、イテレータオブジェクトを使ったもの、map/fold(全単射畳み込み)を使ったものがある。

条件分岐

条件分岐には、 FizzとBuzzを 個別 に分岐するもの、FizzBuzz/Fizz/Buzz/その他に 四分岐 するもの、そして 周期 を利用したものに大別できる。四分岐型の中には、さらに 1+3分岐 と呼べる亜種がある。

実装例

以下の実装は筆者が適当に書いたものだ。

Ruby:正則+一括+四分岐

Rubyで素直に書くとこのようになる。

fizzbuzz.rb
#!/usr/bin/env ruby
puts (1..STDIN.read.to_i).map{|i|
  case
  when i % 15 == 0; "FizzBuzz"
  when i %  3 == 0; "Fizz"
  when i %  5 == 0; "Buzz"
  else            ; i.to_s
  end
}.join("\n")

Ruby:正則+逐次+個別分岐

#tapなど、不必要な装飾的記述を使ったコード。

fizzbuzz2.rb
#!/usr/bin/env ruby
(1..STDIN.read.to_i).tap{ |fizzbuzz|
  is_fizz = ->(n){ n % 3 == 0 }
  is_buzz = ->(n){ n % 5 == 0 }
  to_fizzbuzz = ->(n){
    s  = ''
    s += 'Fizz' if is_fizz[n]
    s += 'Buzz' if is_buzz[n]
    s = n.to_s  if s == ''

    return s
  }
  $fizzbuzz = ->(n){ puts to_fizzbuzz[n] }
}.each(&$fizzbuzz)

JavaScript+HTML+CSS:変則+逐次+個別分岐

これは個別分岐の性質を利用した例だ。

fizzbuzz-ol.html
<!DOCTYPE html>
<title>FizzBuzz</title>
<style>
ol#FIZZBUZZ {
    padding-left: 2em;
}
ol#FIZZBUZZ li.fizz, ol#FIZZBUZZ li.buzz {
    list-style-type: none;
    margin-left: -2em;
}
ol#FIZZBUZZ li.fizz:before {
    content: "Fizz"
}
ol#FIZZBUZZ li.buzz:after {
    content: "Buzz"
}
</style>
<ol id="FIZZBUZZ" data-to="100"></ol>
<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<script>
    $(function() {
    var $ol = $('ol#FIZZBUZZ'),
        n   = +$ol.data('to');
    for (var i = 1; i <= n; i++) {
        var $li = $('<li>');
        if (i % 3 === 0) { $li.addClass('fizz'); }
        if (i % 5 === 0) { $li.addClass('buzz'); }

        $ol.append($li);
    }
});
</script>

PHP:正則+逐次+四分岐

PHPはあまり有名ではないかもしれないが、制御構文を持ったテンプレートエンジンだ。なぜか標準入出力をサポートするので正則のFizzBuzzを書くことができる。

このコードの条件分岐は、先述した 四分岐 の亜流の 1+3分岐 にあたる。

fizzbuzz.php
<?php $n = trim(fgets(fopen('php://stdin', 'r')));
for ($i = 1; $i <= $n; $i++) {
    if ($i % 3 === 0) { ?>Fizz<?php }
    if ($i % 5 === 0) { ?>Buzz<?php }
    elseif ($i % 3 !== 0) echo $i;
?>

<?php
}

Python3:正則+一括+四分岐

記述がトリッキーだが、ただの四分岐で、Rubyのバージョンとまったく同じだ。

fizzbuzz3.py
print("\n".join([{1:{1:"FizzBuzz",0:"Fizz"},0:{1:"Buzz",0:str(i)}}[(i%3==0)+0][(i%5==0)+0] for i in range(1, int(input())+1)]))

(註: このコードは初出時にPython2用のコードだったが、つい先日書いたコードを加筆するにあたって、このPython2用のコードが3つ並ぶのも藝がないので、このバージョンをPython3000向けに若干修正した)

Python2:正則+逐次+個別分岐

こちらはPython2.7向けのバージョンだ。Python2.7ではprintが文のため、内包表記中では利用できない。そのため、代用としてsys.stdout.write()を利用することになる。

fizzbuzz.py
import sys;[i for i in range(1,101)if sys.stdout.write((""if i%3 else"Fizz")+(""if i%5 else "Buzz")+(str(i)if i%3 and i%5 else"")+"\n")]

Python2:正則+一括+四分岐

四分岐だが定義の重複を避け、さらに再帰を使ったパターン。

fizzbuzz2.py
def fizzbuzz (last):
    def isfizz (n):
        return n % 3 == 0

    def isbuzz (n):
        return n % 5 == 0

    def isfizzbuzz (n):
        return isfizz(n) and isbuzz(n)

    def tofizzbuzz (n):
        if (isfizzbuzz(n)):
            return "FizzBuzz"
        elif (isfizz(n)):
            return "Fizz"
        elif (isbuzz(n)):
            return "Buzz"
        else:
            return str(n)

    def fizzbuzz_iter(l):
        if (len(l) == 0):
            return []
        else:
            return [tofizzbuzz(l[0])] + fizzbuzz_iter(l[1:])

    return "\n".join(fizzbuzz_iter(range(1, last+1)))

if __name__ == "__main__":
    print fizzbuzz(input())

Perl5:正則+逐次+周期

Perlまともに書いたことないので、同じアプローチでももっと簡潔に書ける気がしてる。

fizzbuzz.pl
f="Fizz";$b="Buzz";$i=0;@l=($f.$b,"","",$f,"",$b,$f,"","",$f,$b,"",$f,"","");$n=<STDIN>;chomp($n);while($i<$n){print (($l[++$i%15] || $i)."\n");}

なでしこ:正則+逐次+四分岐

cnakoモードでビルドすること。

fizzbuzz.nako
32の標準入力取得する。
nはそれ。

iで1からnまで繰り返す。
    もし、(iを15で割った余り)が0と等しいならば、
        「FizzBuzz」と表示する。
    違えば、もし、(iを3で割った余り)が0と等しいならば、
        「Fizz」と表示する。
    違えば、もし、(iを5で割った余り)が0と等しいならば、
        「Buzz」と表示する。
    違えば、iと表示する。

おわり。

その他の実装

過去に筆者が書いたもの

  • FizzBuzz.php: PHPと呼ばれる有名なオブジェクト指向言語で書かれたFizzBuzzイテレータクラス
  • fizzbuzz.html: 本稿で挙げたHTML+CSS+JavaScriptの別バージョン(原型)

その他のFizzBuzz

あとがき

FizzBuzzは前述した三要素のため、初めて触れるプログラミング言語の学習には極めて優れた題材だ。また、この問題を使って足切りのための課題とすることは、これからも絶えないのかもしれない。

あとみんなシェルで動かすスクリプト書くときはもっと標準入力を活用しよう。