自分なりのFizzBuzzの解説。


エセコボラーがFizzBuzzを解説してみました。

材料

  • 代数演算子
  • 比較演算子
  • 条件分岐
  • 繰返し制御
  • 順次、逐次
  • 改行コード
  • 数値型変数
  • 変数のパース

PHPでFizzBuzz問題を解いてみる。

問題の解決には、問題を分解することが重要。

これは実生活でも同じ。(知らんけど)

僕が初めて、SES契約で常駐した会社の常務さんは、
「我々の仕事は、因数分解なんやで」 ってプロパーの社員さんに話してた気がする。
もう14年前かな。

でもこの話は時間と共に、大きなインパクトを僕の中に植え付けた。(多分、多分)

脱線を戻そう

問題の分割をやってみよう。

  • 1〜100までの数字を出力
  • 3の倍数はFizzを出力
  • 5の倍数はBuzzを出力
  • 3の倍数と5の倍数の場合FizzBuzzを出力

これら4つが分割結果。

1〜100までの数字を出力しよう。

<?php
for($i=1;$i<=100;$i++){
    echo $i;
}

結果は↓となった。

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100

Try and Error

僕の人生はトライアンドエラーの繰返し。(知らんけど)

トライアンドエラーは、和製英語で、試行錯誤という意味らしい。

英語だと、

Trial and error

脱線を戻そう

さっきの出力した結果には、改行が入ってなかったんだ。

なので、改行を入れてみる。

1〜100までの数字を出力(改行コードを入れてみる)

改行とは。改めて行。人生とは行の連続だ。(多分業のことを言っている)

なんて意味のわからないことを言うのはもう辞めよう。多分無理だけど。

改行コードって[CR]とか[LF]とか[CRLF]とかですね。

リテラル文字に表してみると、"\r" "\n"ですね。

リテラル文字って・・・なんのこっちゃ分からない。

気になる人は 改行コード (wikipedia)をみてください。

脱線を戻そう

<?php
for($i=1;$i<=100;$i++){
    echo $i."\n"; // ←文字列結合で数字と改行文字を合体!
}

すると、1〜100を改行付きで出力しましたとさ。
めでたしめでたし。

1
2
3
4
....省略...

ちなみにMacの日本語キーボードだと。

[option]キー + [¥]キーで、[\n]になるよ。

変数のパース

echo $i."\n";

は、

echo "{$i}\n";

でも良いし、

echo "${i}\n";

でも良いんだ。こんな書き方を変数のパースって呼ぶそう。

書き方は自由。つまり言論の自由。

でも、シングルクォテーションで囲った文字は、変数のパースしてくれないから気をつけよう。

脱線を戻そう

Fizzを出力してみよう。

で、次に、Fizzを出力してみる。

出力する数字を3で割ってみて、余りが0なら、3の倍数ってこと。

PHP的には$i % 3で求めます。

今回 % は 代数演算子 です。

$a % $b → (%は剰余) → つまり $a を $b で割った余り

PHPで表すと以下のようになります。

if( $i % 3  == 0 ){
     // $iを3で割った余りが、0の場合(つまり、3で割り切れた時) ここを通ります。
}

改めて、条件分岐について復習。

条件分岐には ifelse があります。

ifはもしもTrue の場合、 elseFalseの場合に動作します。

if( $foo ) {
     //$fooがTrueの場合ここを通ります。
} else {
    //$fooがFalseの場合ここを通ります。
}

※$fooは例えばの変数です。

TrueかFalseは比較演算子の結果

($i % 3  == 0)
// ↑の計算式の通りなら、True。 
// ↑の計算式の通りにならない場合は、False。

この流れを当てはめてみると。

<?php
for($i=1;$i<=100;$i++){
    if($i % 3 == 0 ){
        // $iを3で割った余りが、0の場合(つまり、3で割り切れた時)
        echo "Fizz\n";
    } else {
        // $iを3で割った余りが、0以外の場合(つまり、3で割り切れない場合)
        echo $i."\n";
    }
}

と。なりました。

このプログラムの出力結果は、以下のようになりました。

1
2
Fizz
4
5
Fizz
7
8
...省略...

それっぽいのができた。

目出度目出度。

これメデタシメデタシって読むんだね。

Buzzを出力。

elseifを使う時が出てきました。

Buzzは5で割り切れる場合に出力します。

phpのマニュアルより、elseifをみてみる。

<?php
if ($a > $b) {
    echo "aはbより大きい";
} elseif ($a == $b) {
    echo "aはbと等しい";
} else {
    echo "aはbより小さい";
}

これをBuzzに当てはめてみましょう。

まずはシンプルに。elseif を追加してみます。

<?php
for($i=1;$i<=100;$i++){
    if($i % 3 == 0 ){
        //$iを3で割った余りが、0の場合(つまり、3で割り切れた時)
        echo "Fizz\n";
    } elseif($i % 5 == 0){
        //$iを5で割った余りが、0の場合(つまり、5で割り切れた時)
        echo "Buzz\n";
    } else {
        echo $i."\n";
    }
}

結果は。

1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
Fizz
...省略...

となりました。

15 の場合に、FizzBuzzと出力したいですが、このままだとFizzの出力となります。

この問題を解消するため、単純に、elseif を加えてみます。

<?php
for($i=1;$i<=100;$i++){
    if( $i % 3 == 0 ){
        //$iを3で割った余りが、0の場合(つまり、3で割り切れた時)
        echo "Fizz\n";
    } elseif ( $i % 5 == 0 ){
        //$iを5で割った余りが、0の場合(つまり、5で割り切れた時)
        echo "Buzz\n";
    } elseif ( $i % 3 == 0 && $i % 5 == 0 ){
        //$iを3で割った余りが、0かつ、
        //$iを5で割った余りが、0の場合
        echo "FizzBuzz\n";
    } else {
        echo $i."\n";
    }
}

あy .... 過ち。

このプログラムには間違いがあります。 15の時、Fizzしか出力されません。

3の余りが0かつ、5の余りが0の場合、

は、

必ず、3の余りが0の条件にマッチする

なので。

3の倍数の場合で5の倍数の場合、一番上の echo "Fizz\n"; の部分が動いてしまうの。

FizzBuzzを出力したければ、3の余りが0の条件分岐より、先に、

3の余りが0かつ、5の余りが0 かどうかを判定する必要があります。

結果このようなプログラムに修正されました。


<?php
for($i=1;$i<=100;$i++){
    if ( $i % 3 == 0 && $i % 5 == 0 ){
        //$iを3で割った余りが、0かつ、
        //$iを5で割った余りが、0の場合
        echo "FizzBuzz\n";
    } elseif ( $i % 5 == 0 ){
        //$iを5で割った余りが、0の場合(つまり、5で割り切れた時)
        echo "Buzz\n";
    } elseif( $i % 3 == 0 ){
        //$iを3で割った余りが、0の場合(つまり、3で割り切れた時)
        echo "Fizz\n";
    } else {
        echo $i."\n";
    }
}

結果、うまくいきました。

1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
...省略...

改善案

少し改善する方法を考えてみます。

if ( $i % 3 == 0 && $i % 5 == 0 ){

この部分は、もしかすると、

if ( $i % 15 == 0 ){

に置き換えれるかもしれないな。とか。

その考えを当てはめてみました。


<?php
for($i=1;$i<=100;$i++){
    if ( $i % 15 == 0 ){
        //$iを3で割った余りが、0かつ、
        //$iを5で割った余りが、0の場合
        echo "FizzBuzz\n";
    } elseif ( $i % 5 == 0 ){
        //$iを5で割った余りが、0の場合(つまり、5で割り切れた時)
        echo "Buzz\n";
    } elseif( $i % 3 == 0 ){
        //$iを3で割った余りが、0の場合(つまり、3で割り切れた時)
        echo "Fizz\n";
    } else {
        echo $i."\n";
    }
}

関数を使ってみよう。 (オマケ)

FizzBuzzの出力を関数にしてみよう。

関数って、↓↓↓こんなやつ。

function FizzBuzzEcho($num){
    echo "${num}\n";
}

関数のイメージはこんな感じでやってみる。

関数は、引数が合って、戻り値が合って、って色々ありそうだけど、大事なことは 処理を1つにまとめた部品にしてしまうこと。

今回作る関数は、FizzBuzzEcho関数。

やることは、

  • 渡した数字が15の倍数の場合はFizzBuzzを出力、
  • 渡した数字が3の倍数の場合はFizzを出力、
  • 渡した数字が5の倍数の場合はBuzzを出力、
  • それ以外は、渡した数字を出力する関数。

関数の使い方は簡単。


FizzBuzzEcho(1);
FizzBuzzEcho(3);
FizzBuzzEcho(5);
FizzBuzzEcho(15);

こんな感じで使えます。

結果は、

1
3
5
15

関数を正しい結果になるよう修正してみよう。


<?php

function FizzBuzzEcho($num){
    if ( $num % 15 == 0 ){
        //$iを3で割った余りが、0かつ、
        //$iを5で割った余りが、0の場合
        echo "FizzBuzz\n";
    } elseif ( $num % 5 == 0 ){
        //$iを5で割った余りが、0の場合(つまり、5で割り切れた時)
        echo "Buzz\n";
    } elseif( $num % 3 == 0 ){
        //$iを3で割った余りが、0の場合(つまり、3で割り切れた時)
        echo "Fizz\n";
    } else {
        echo $num."\n";
    }
}

FizzBuzzEcho(1);
FizzBuzzEcho(3);
FizzBuzzEcho(5);
FizzBuzzEcho(15);

結果は。

1
Fizz
Buzz
FizzBuzz

が出力された。問題ないですね。

繰り返しの中で、関数を呼び出してみよう。

こんな感じで呼び出す。

<?php 
for($i=1;$i<=100;$i++){
    FizzBuzzEcho($i);
}

でできたプログラムは↓こんな感じ。

<?php
function FizzBuzzEcho($num){
    if ( $num % 15 == 0 ){
        //$iを3で割った余りが、0かつ、
        //$iを5で割った余りが、0の場合
        echo "FizzBuzz\n";
    } elseif ( $num % 5 == 0 ){
        //$iを5で割った余りが、0の場合(つまり、5で割り切れた時)
        echo "Buzz\n";
    } elseif( $num % 3 == 0 ){
        //$iを3で割った余りが、0の場合(つまり、3で割り切れた時)
        echo "Fizz\n";
    } else {
        echo "${num}\n";
    }
}

for($i=1;$i<=100;$i++){
    FizzBuzzEcho($i);
}

動かした結果は、↓となった。

1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
...省略...

こんな感じ。です。

関数の大事なことは、役割を明確にすること。

名前を丁寧につけること。

名前を丁寧につけて、役割を明確にすると、

やりたいことが伝わりやすくなります。

for($i=1;$i<=100;$i++){
    FizzBuzzEcho($i);
}

そして、役割を明確にして、名前を丁寧につけることは、構造化プログラミングの一歩。

参考: 構造化プログラミング (wikipedia)

以上。