FizzBuzz問題・解答編
まず断っておく。
これしかない、という解は存在しない。
プログラム言語によっても、見る人によっても、最適解が違うであろうから。
趣旨は Short Coding でも、実行速度でも、可読性の高さ、のどれとも定義しない、ということだ。
そのため、この記事は、私自身が考えた解、多々FizzBuzz問題を取り上げているサイトなどを調べた結果について記載するものである。
まず、“FizzBuzz”をキーワードとしてぐぐるだけで記事投稿日現在で476,000件がHitする。
情報源はいくらでもありそうだ。
FizzBuzz問題の情報元や定義について記載されていると思われる上位Hitサイトは以下。
どうしてプログラマに…プログラムが書けないのか?
http://www.aoky.net/articles/jeff_atwood/why_cant_programmers_program.htm
FizzBuzz(はてなダイアリーキーワード)
http://d.hatena.ne.jp/keyword/FizzBuzz
Fizz Buzz - Wikipedia:
http://ja.wikipedia.org/wiki/Fizz_Buzz
さて、それではいくつかの解を検討及び検証していく。
設問記事ではC#での解は示す、と書いた。
せっかくだから VB.NET / PHP 辺りも考えてみることにする。
また、その他いくつか面白い解や興味のある解も紹介してみよう。
設問を再度提起しておく。
【問題】
1から100までの数を表示するプログラムを書け。
ただし3の倍数のときは数の代わりに「Fizz」と表示。
5の倍数のときは「Buzz」と表示。
3と5両方の倍数の場合には「FizzBuzz」と表示すること。
まずは、私自身が考えたモノ。
C# 一発目。
単純な for ループと4分岐がまず思い浮かんだので。
Write は WriteLine でも良かったんだけど、出力が全部見渡せないのが気に食わなかっただけ。
namespace fizzbuzz { class Program { static void Main(string[] args) { for (int i = 1; i <= 100; i++) { if(i % 15 == 0) Console.Write("FizzBuzz "); else if(i % 5 == 0) Console.Write("Buzz "); else if(i % 3 == 0) Console.Write("Fizz "); else Console.Write("{0} ", i); } } } }
C# 2発目。
3剰余と5剰余を判断するのに、15剰余も見てる。
Fizz、Buzz、それぞれの表示ロジックが2つずつある。
この2点が気に食わない。
namespace fizzbuzz { class Program { static void Main(string[] args) { for (int i = 1; i <= 100; i++) { if (i % 3 == 0) Console.Write("Fizz"); if (i % 5 == 0) Console.Write("Buzz"); if (i%3!=0 && i%5!=0) Console.Write(i); Console.Write(" "); } } } }
Fizz/Buzzの出力はまとめられた。
でも結局3と5の剰余を2回ずつ求めてるじゃん。
イケてない。
んー、とりあえず、ifまでも気に食わなくなってきた。
三項演算子でやってみまんた、と思ったら、Jittaさんが既にやってる。
何となく Blog by Jitta
http://blogs.wankuma.com/jitta/archive/2007/11/07/106683.aspx
考え方は一緒でしたが、れび版も一応。
C# 三発目。
namespace fizzbuzz { class Program { static void Main(string[] args) { for (int i = 1; i <= 100; i++) { Console.Write(i%15==0?"FizzBuzz ":i%5==0?"Buzz ":i%3==0?"Fizz ":i.ToString()+" "); } } } }
別に1行で書かなくてもいいんだけど。
(Short Coding 的趣旨じゃないと言いつつも…
三項演算子だとなんか1行で書きたくなったり。
なんか短く仕上がったので、満足しておくことにして。
他のプログラム言語でもやってみるとしよう。
VB.NET でもやってみます。
C# 一発目と題したものの代替。
Module Module1 Sub Main() Dim i As Integer For i = 1 To 100 If i Mod 15 = 0 Then Console.Write("FizzBuzz ") ElseIf i Mod 5 = 0 Then Console.Write("Buzz ") ElseIf i Mod 3 = 0 Then Console.Write("Fizz ") Else Console.Write(i & " ") End If Next End Sub End Module
VB.NET は、ほとんど使ったことがない。
ので、時間かかりそうだから簡単な移植に留めておく。
C# と違い、三項演算子(または相当するもの)がないので、この辺りが関の山か?
(あとで“かるあ”さんのラムダを発見して喜びましたが♪
本記事右上掲載画像は、私が書いた C#/VB.NET の結果。
なんだけど、妙にちっちゃくなっちゃった。。。ゴメンナサイ。
もういっちょ別言語で行ってみよう。
お次はPHP。
これまでコンソールアプリで事足りていたが、PHPは手元の環境にセットアップ済みなので、ブラウザに表示させちゃおう。
http://localhost/fizzbuzz.php って感じでアクセスすることをイメージ。
PHP 一発目。
<?php for($i=1;$i<=100;$i++){ if( $i%15==0 ){ echo 'FizzBuzz '; } else { if( $i%5==0 ){ echo 'Buzz '; } else { echo $i%3==0?'Fizz ':$i." "; } } } ?>
驚愕の事実が判明しちゃいました。
php 5.2.2 Win だけかどうかわかりませんが、一処理に三項演算子を三つ以上重ねると最後尾の二つしか効かない。
<?php for($i=1;$i<=100;$i++){ echo $i%15==0?'FizzBuzz ':$i%5==0?'Buzz ':$i%3==0?'Fizz ':$i.' '; /* 整数値かFizzしか表示しない… */ } ?>
こんなのあり?
ていうか PHP のバグ!?
てことで、if/else + 三項演算子、という変わり種になっちゃいました。
あまりにも不細工だったので、もう少しだけ捻る。
PHP 二発目。
<?php for($i=1;$i<=100;$i++){ if ($i%3==0) echo 'Fizz'; if ($i%5==0) echo 'Buzz'; if ($i%3>0&&$i%5>0) echo $i; echo ' '; } ?>
もう少し検討した方がスマートになりそうだけど、時間の都合上、ということでご勘弁を。
以降は、参考サイトから見つけたモノ。
C# KeyValuePair + delegate 版:
επιστημηさんのBlogより。
http://blogs.wankuma.com/episteme/archive/2007/11/08/106821.aspx
using System; using System.Collections.Generic; delegate bool Predicate(int n); // この条件が満たされたら delegate void Action(int n); // こぉします。 class Program { public static void Main() { KeyValuePair<Predicate,Action>[] table = { new KeyValuePair<Predicate,Action>( delegate(int x) { return x%3!=0 && x%5!=0; }, delegate(int x) { Console.WriteLine(x); }), new KeyValuePair<Predicate,Action>( delegate(int x) { return x%3==0 && x%5!=0; }, delegate(int x) { Console.WriteLine("Fizz"); }), new KeyValuePair<Predicate,Action>( delegate(int x) { return x%3!=0 && x%5==0; }, delegate(int x) { Console.WriteLine("Buzz"); }), new KeyValuePair<Predicate,Action>( delegate(int x) { return x%3==0 && x%5==0; }, delegate(int x) { Console.WriteLine("FizzBuzz"); }), }; for ( int i = 1; i <= 100; ++i ) { foreach ( KeyValuePair<Predicate,Action> kv in table ) if ( kv.Key(i) ) kv.Value(i); } } }
ちょうど delegete とか、yield return とか、うまく活用したパターンも書きたいなぁ、と思っていた。
一般化に特化して書かれたようだけど、確かにメンテナンス性は良いのではないかと。
他にも何らかのパターンで書いてみたいが、例によって時間の都合上割愛。
VB.NET ラムダ版:
かるあさんのBlogより。
http://karua.at.webry.info/200711/article_9.html
Dim query = Enumerable.Range(1, 100). _ Select( _ Function(x As Integer) _ If((x Mod 5 + x Mod 3) = 0, "FizzBuzz", _ If((x Mod 3) = 0, "Fizz", _ If((x Mod 5) = 0, "Buzz", x.ToString()) _ ) _ ) _ )
javascript:x=i='';while(++i<101)x+=(i%5|i%3?i%3?i%5?i:'Buzz':'Fizz':'FizzBuzz')+'%20';x
ブラウザのアドレスバーに上記1行をコピペするだけ。
参考サイト捜索中に出てきたもの。
個人的に JavaScript が得意でない私としては「なるほどなぁ」って感じ。
最後に。
笑ったのがコレ。
print("1\n2\nFizz\n4\nBuzz\nFizz\n7\n8\nFizz\nBuzz\n11\nFizz\n13\n14\nFizzBuzz\n16\n17\nFizz\n19\nBuzz\nFizz\n22\n23\nFizz\nBuzz\n26\nFizz\n28\n29\nFizzBuzz\n31\n32\nFizz\n34\nBuzz\nFizz\n37\n38\nFizz\nBuzz\n41\nFizz\n43\n44\nFizzBuzz\n46\n47\nFizz\n49\nBuzz\nFizz\n52\n53\nFizz\nBuzz\n56\nFizz\n58\n59\nFizzBuzz\n61\n62\nFizz\n64\nBuzz\nFizz\n67\n68\nFizz\nBuzz\n71\nFizz\n73\n74\nFizzBuzz\n76\n77\nFizz\n79\nBuzz\nFizz\n82\n83\nFizz\nBuzz\n86\nFizz\n88\n89\nFizzBuzz\n91\n92\nFizz\n94\nBuzz\nFizz\n97\n98\nFizz\nBuzz\n");
言語がどう、とかじゃなく発想がね。
もちろん“FizzBuzz問題”を提起した側からすれば迷わずアウトとなる解ですが、もちろんネタでしょう(笑
P.S. 前記事にコメントいただいた偏屈さんへ。
今回は Equals を使わなくても単純な演算子でOKではないかと。
また、VB の Select/Case は、C# では Switch/Case でほぼ同様のコードが組めます。