Unityでインディゲームを作る!

Unityでのゲーム制作を目指し、それに関わる話題についてのブログ

C#でFizzBuzz問題に挑む!Main内で五行達成。

FizzBuzzとは英語圏における言葉遊びで、複数人で数字を順に言っていくわけですが、3の倍数の時は"fizz"5の倍数の時は"buzz"3と5の公倍数の時には"FizzBuzz"と言うのがルールになっています。

FizzBuzz問題とは、この遊びをプログラミングで再現するというものです。その人が本当にプログラミングが出来るかどうかを試すのにちょうどいい"簡単な問題"とされるもので、面接などで出題されるそうです。あるいはいろんな条件を課して、力試しとして取り組むこともあるようです。

 

きっかけはこちらの記事です。"FizzBuzz"という遊びはなんとなく聞いたことがあった気がしますが、プログラミングでそういうものがあるのは初めて知ったので、興味が出ました。ググってみた所、会社内でコレを使ってコンテスト開いた!というブログ記事なんてのもあったので、それを軽く読んでみたり。

 

どちらの記事もPythonでのコードです。ほぼ経験ナシのPythonとは言え、答えのコードを軽くは見てしまってはいるんですが、せっかくなのである条件を付け加えて、C#でこのfizzbuzz問題のコードを書いてみることにしました。

以下はどのような試行錯誤を経て、最終的なコードへと至ったかの思案の推移をまとめたドキュメントであり、なるべくどのように思考したかを順を追って逐一説明する形で文章化してあります。

 

条件とは?

目標としては可読性や見た目を損なわず、できる限り行の少ないコードにする!ことに設定しました。

1から100までの数字をコンソール画面に表示し、ルール通りに特定の数字の時にはFizzやらBuzzを変わりに表示する、というコードです。

いわゆるウィキペディアなどにあるような模範的なコードではなく、C#として使えるものは全部使って出来るかぎり短いコードにするということです。

 

試験ではないので制限時間もないし、コード実行して結果を見つつ改善していく、ということも特に禁止してません。あくまで自分で考えて、コードを書くという試みです。

 

問題の分割!

さて、この問題は大きく分けて2つの個々の問題に分割することが出来ます。

  • 繰り返し処理
  • 条件分岐

まずは1から100までを繰り返し画面に映す、という問題です。

これはループ文、イテレーションによって実現できます。whileかforのどちらかを選ぶか、ということについては、自分はwhile文の中でincrement(インクリメント)すれば短くできるかも?ということでwhile文を選びました。しかし、それは誤りで普通にfor文の方を使った方が簡単に短く出来るので最終版ではforを選択しました。

 

カウント変数、イテレータをそのままConsole.WriteLineを使って繰り返し画面に表示すれば、ひとまず1から100まで画面に映す、という問題は解決できます。

for(int i =1; i <= 100; i++){

Console.WriteLine( i );

}

 

次に解決すべき問題は、数字を判断して、3の倍数の時はfizzを、5の時はbuzzという風に数字の代わりに文字列を映す、という問題です。

  • 3の倍数            Fizz
  • 5の倍数            Buzz
  • 3と5の公倍数   FizzBuzz
  • それ以外の数   カウント変数を出力 

これは素直にif文でそれぞれ条件判断すればいいわけですが、自分の課題はなるべく行数を少なくすることです。なので三項演算子を使うことにしました。

 

三項演算子とは

(条件式) ? trueの時の値 : falseの時の値

というもの場合によってはif文よりシンプルに(あるいは読みづらく)条件判定が書き表わせられるというものです。

 

これを使えばConsole.WriteLine()のカッコ内、引数として三項演算子の式を入れることが出来ます。これによって行数を少なく出来ます。後で説明しますが、これは一回挫折します。しかし、最終的には三項演算子を使うというアイデアを実現することが出来ました。見当違いではなかったということになります。

Console.WriteLine( a > b ? a : b  );

//aの方が大きいならaがそうでないならbが表示される。

というわけで、この問題を解決するための準備は整いました。

 

解決に向けて その1

自分はwhile文を選んでしまったので、まずwhileの条件式の中でincrement出来るかの確認から始めました。一回試したかったというのもあります。

カウント変数は文字数を減らす意味でもnumberの頭文字のnとしました。

int n = 0;

while( n++ < 100){

Console.WriteLine(n);

}

です。

インクリメントは前置き後置きがあって、演算子や比較子を実行する前に増やすか後に増やすか選ぶことが出来ます。

どちらでも問題はないようです。後置きだと100との比較後にincrementが実行され、その後に、内部の処理が実行される、という流れになります。

ちなみにVisual StudioはConsole.ReadLine()などで受付待ちさせないと、処理終了後すぐ画面が閉じてしまうので付け足していますが、除外して行を数えています。

f:id:miur-us:20170822233900p:plain

f:id:miur-us:20170822233815p:plain

というわけでひとまずは最初の問題は解決です。しかし先ほど書いたように、最終的にはwhile文は捨てて、for文を使います。

 

解決に向けて その2

で、三項演算子です。それぞれの倍数は%(剰余算)を使って表現出来ます。倍数はつまり、その数で割り切れるということであり、%の答えが0になります。

6 % 3 == 0 (6を3で割った余りはゼロ)

 

で最初に思いついたアイディアを形にしたのが、この二行になります。

f:id:miur-us:20170822234305p:plain

キーポイントはConsole.Writeです。これは改行されません。つまり、3,5の倍数の時は続けてbuzzが表示されることで、Console.WriteLine("fizzbuzz")を省略出来ます。これは我ながら良いアイディアだと思いました。つまり3,5の公倍数の時には"FizzBuzz"を出力する!という個別の処理文を書く必要がなくなるので省スペースになります。 

f:id:miur-us:20170822234238p:plain

これを実行すると、Fizzの後ろに3が出力されてしまっていますが、それなりに上手くいっているように思えます。しかし、どうしても3の倍数を出力しない方法が、この時点では思い浮かびませんでした。

 


なので、諦めて素直にif文を使ったコードにすることにしました。

continue文で、その時点でループを強制終了して次のループへ移行します。

f:id:miur-us:20170822234441p:plain

3と5の公倍数を表すのに素直な表現は

if( n % 3== 0 && n % 5 == 0 ){}

になりますが、これは圧縮出来ます。

if( n%3 + n%5 == 0 ){}

どちらも0であるならば、その和も0になるのだから、これでも条件としてはオッケーなはずです。この後は素直にそれぞれの条件を判断し、相応しい数字、文字列を移すようにします。というわけで、完成かと思いましたが、最後の文が三項演算子で省略できると閃きました。上の写真の最後の文は元々は

if( n % 5 == 0 ) Console.WriteLine( "Buzz" );

else Console.WriteLine( n );

でした。

つまり、ここをConsole.WriteLine( n % 5 == 0 ? "buzz" : n.ToString() );としました。

三項演算子の場合は、nをToStringを使って文字列に変換することが求められます。

まさしく最初に思いついたアイデアをそれなりに活かすことが出来て満足でした。

f:id:miur-us:20170822234836p:plain

 

まだ終わってない!!

というわけで、できたー!と満足していたんですが、やはり三項演算子を活用した更に短いコードを諦めきれず、そしてやはりfor文の方が短く済むということで、再度挑戦しました。改めてというか、出来た後一時間後くらいなんですけども。

for ( int i = 1; i <= 100; i++ ){

//判断し、画面に映す処理

}

とりあえず、最初の改善案はfor文に変更することです。こちらの方が分かりやすい!

そして、最初のアイディアの大きな問題はfizzが打ち出された後、そのまま流れでカウント変数がプリントされてしまうことでした。

 

しかし、continueを使って次のループに飛ばす、という技を第二案で使っていたので、これを使えばいけるだろうと考えました。

 

何かもう一行、条件が必要です。fizzの条件を再確認しましょう。3の倍数であり、5の倍数出ない時です。つまり、複数の条件ですから、三項演算子は使えません。そうだ!別にif文使えばいいだけじゃん!と思いつきました。

f:id:miur-us:20170823001506p:plain

三項演算子の間にif文を書いてはいけない、なんていうルールはないです。自分自身の思い込みによって、視野が狭くなっていました。

if( i % 3 == 0 && i % 5 != 0){ Console.WriteLine(); continue;}

fizzは改行されませんが、Console.WriteLine()を実行し改行処理。continue;でイテレーションを飛ばします。これでcontinueも一個に削減。

以上で、FizzBuzz問題のコードをMain内で五行にすることが出来ました。 

 

まとめ!

今回の試みを経験したことで、やはりアイディアは大事だし、一回ダメでもいろいろ試してみると道は開ける、ということを強く実感しました。結果的に最初の三項演算子を使ってコードを圧縮できるかも!?という算段は外れてはいなかったので。

いろいろ勉強になりました! 

応用編として、%を使わない、とかいろんな縛りプレイがあるみたいですね。

そこらへんもいつか挑戦してみたいと思います。

 


と思ったら、記事を書いている間に案を思いつきました。

割り切れる、というのは余りがない、あるいは割った商の小数点以下がないということなので、%(剰余算)を使う代わりに次のように書き表せます。

もし i / 3.0f - i / 3 == 0 なら

3.0fつまりfloatにしないと、商が切り捨てられた整数になってしまいます。逆にi / 3とすることで、小数点以上のみに出来るのでこれを引けば、少数点以下を表せます。

もしiが1の場合、1.0 ÷ 3.0 = 0.33333...で1 ÷ 3は少数点以下切捨てで0でよって、少数点以下が存在するので(0.333... - 0 = 0.333...)1は3の倍数ではない、という風に判断することができます。

iが6の時は、(6.0 ÷ 3.0 = 2) - ( 6÷ 3 ) = 0.0よって、3の倍数である!となります。

あとは先ほどの三項演算子の条件式の中身と取り替えるだけです。

f:id:miur-us:20170822235029p:plain

というわけでFizzBuzz問題の研究を終わります。やっぱ自分で考えて解くと爽快感がありますね。