FizzBuzz問題:Dev Basics/Keyword
「FizzBuzz問題」とは、英語圏での言葉遊びであるFizz Buzzをプログラミング言語で記述する行為。さまざまな言語によるさまざまな解法がある。
「FizzBuzz問題」とは、英語圏での言葉遊びであるFizz Buzzをプログラミング言語で記述する行為である。シンプルな記述でも解けるし、言語に固有な機能や構文を使って解いてもよい。問題解決に制約を設けることで、スキルを磨いたり、言語の理解度を向上させたり、もちろんプログラミングの楽しみを得たりといった形で解かれることがよくある。
FizzBuzz問題のルール
FizzBuzz問題では、1から何らかの値までを数え上げて(画面などに出力して)いく。ただし、その際には以下の条件がある。
- その数が3で割り切れる場合には、その数の代わりに「Fizz」を出力する
- その数が5で割り切れる場合には、その数の代わりに「Buzz」を出力する
- その数が3でも5でも割り切れる場合には、その数の代わりに「FizzBuzz」を出力する
1から15の数え上げであれば、出力は「1→2→Fizz→4→Buzz→Fizz→7→8→Fizz→Buzz→11→Fizz→13→14→FizzBuzz」のようになる。
シンプルな解法
上記のルールに沿ったシンプルな解法の例を以下に示す(C#、JavaScript、Python 3)。ただし、以下では上限を「15」に固定している。
class Program
{
static void Main(string[] args)
{
int n = 15;
for (int i = 1; i <= n; i++)
{
if (i % 15 == 0)
{
Console.WriteLine("FizzBuzz");
}
else if (i % 3 == 0)
{
Console.WriteLine("Fizz");
}
else if (i % 5 == 0)
{
Console.WriteLine("Buzz");
}
else
{
Console.WriteLine(i);
}
}
}
}
n = 15;
for (var i = 1; i <= n; i++) {
if (i % 15 == 0) {
console.log("FizzBuzz");
} else if (i % 3 == 0) {
console.log("Fizz");
} else if (i % 5 == 0) {
console.log("Buzz");
} else {
console.log(i);
}
}
n = 15
for i in range(1, n+1):
if i % 15 == 0:
print("FizzBuzz")
elif i % 3 == 0:
print("Fizz")
elif i % 5 == 0:
print("Buzz")
else:
print(i)
上からC#、JavaScript、Python 3のコードとなっている。
ここではC#、JavaScript、Pythonの例を挙げた。どれも基本の構造は同じで、for文によるループ、if文による分岐でFizzBuzz問題を解いている。それでも、言語ごとに異なる部分が見えるのは面白いところだ(例えば、Pythonのfor文とC#、JavaScriptのfor文との差)。出力結果は割愛する。
上記のコードの典型的な改善案としては、「3でも5でも割り切れる場合」をif文を使わずに処理することがある。これにはループ内で空文字列に対して、3で割り切れる場合は文字列「Fizz」を、5で割り切れる場合は文字列「Buzz」を、それ以外の場合はその数(を文字列化したもの)を連結するようにする。Pythonでの実装例を以下に示す。if〜elif〜else文ではなく、各条件を個別にif文としている点に注意しよう。
n = 15
for i in range(1, n+1):
s = ""
if i % 3 == 0:
s += "Fizz"
if i % 5 == 0:
s += "Buzz"
if s == "": # FizzでもBuzzでもFizzBuzzでもない
s += str(i)
print(s)
もう少し凝った解法
次に少し凝った解法を示す。全ての例でリスト(もしくは配列)を使用していることと、C#ではLINQを、JavaScriptではmapメソッドを、Pythonではリストの内包表記と文字列のリストを連結している点が特徴といえる。ただし、コアとなる部分は3つの言語の例全てで同じであり、三項演算子(Pythonでは同様な結果を得る条件式)を使用して、場合分けをしている(なお、ここでは実行速度には気を使っていない。あくまで個々の言語で独特の表現を使ってみることに主眼を置いている)。
static void Main(string[] args)
{
int n = 15;
var list = Enumerable.Range(1, n).Select(i =>
(i % 15 == 0) ? "FizzBuzz" :
(i % 3 == 0) ? "Fizz" :
(i % 5 == 0) ? "Buzz" :
i.ToString()).ToList();
list.ForEach(Console.WriteLine);
Console.ReadKey();
}
var n = 15;
var a = new Array();
for (var i = 1; i<= n; i++) {
a.push(i);
}
a.map(item => console.log(
item % 15 == 0 ? "FizzBuzz" :
item % 3 == 0 ? "Fizz" :
item % 5 == 0 ? "Buzz" :
item.toString())); // item.toString()はitemだけでもよい
n = 15
s = ["FizzBuzz" if x % 15 ==0 else
"Fizz" if x % 3 == 0 else
"Buzz" if x % 5 == 0 else
str(x)
for x in range(1, n+1)]
print("\n".join(s))
上からC#、JavaScript、Python 3のコードになっている。
Pythonのコードでは途中で改行が入っているが、これは非明示的な行継続と呼ばれるものであり、かっこ内に表れる式は途中で改行を入れられる(一方、行末にバックスラッシュを記述することで、明示的に1つの論理行を複数行に分けて記述できる。これを明示的な行継続と呼ぶ)。
JavaScript版のコードでは配列を新規に作成し、これに数値をpushしているが、ECMAScript 2015で追加されたArray.fromメソッドとArray.keysメソッドを使うとより簡便な記述が可能になる。興味のある方は調べてみよう(Stack Overflowの「Create a JavaScript array containing 1…N」などが参考になる)。
ちなみに以下のようなコードになる。上のコードではmapメソッド内でFizzBuzzの判定を行っていたが、以下ではArray.fromメソッドの第2引数(マップ関数=配列作成時に個々の要素に対する処理を行う)を使って、一気にFizzBuzz判定後のリストを作っている。単純に1〜Nを要素とする配列を作りたいのであれば、マップ関数を「item => item + 1」とするとよい(配列のインデックスは0ベースのため、そのままでは先頭要素の値も0になることから、ここではマップ関数を使用して調整をしている)。
var n = 15;
var a = Array.from(Array(n).keys(), function(item) {
item += 1;
return item % 15 == 0 ? "FizzBuzz" :
item % 3 == 0 ? "Fizz" :
item % 5 == 0 ? "Buzz" :
item.toString();
})
a.map(item => console.log(item));
本稿では15までを数え上げるようにしていたが、ユーザー入力を受け取るようにして、入力値の検証を行うようにするといったことも考えられる。こうした点も考慮してFizzBuzz問題を解くコードを書いていくことで、それぞれの言語の機能や構文、コーディングの流儀などさまざまな面に触れることになり、その言語に対する理解度が増していくはずだ。もちろん、FizzBuzz問題には他にも、より短いコードを目指したり、よりトリッキーなコードを目指したりとさまざまな解法があり、実際に手を動かしてコードを書くことで自分のスキルが上がっていくだろう。
Copyright© Digital Advantage Corp. All Rights Reserved.