エセコボラーが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で割り切れた時) ここを通ります。
}
改めて、条件分岐について復習。
条件分岐には if と else があります。
ifはもしもTrue の場合、 elseはFalseの場合に動作します。
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);
}
そして、役割を明確にして、名前を丁寧につけることは、構造化プログラミングの一歩。