FizzBuzz問題
本記事では、プログラミングにおけるアルゴリズムの定番問題である「FizzBuzz問題」について解説します。
1. FizzBuzz問題の定義
まずFizzBuzz問題が何かを定義しておきます。 FizzBuzz問題のルールはいたって単純で、1から任意の整数 までの数値を順に出力する時、以下の規則に従って数値を文字列に変換します。
FizzBuzz問題のルール
- 3の倍数である場合は、数値の代わりに “Fizz” と出力する。
- 5の倍数である場合は、数値の代わりに “Buzz” と出力する。
- 3の倍数かつ5の倍数(すなわち15の倍数)である場合は、数値の代わりに “FizzBuzz” と出力する。
- 上記のいずれの条件も満たさない場合は、数値をそのまま出力する。
2. 条件分岐内での優先順位
if 文を上から順番に書いていけば、簡単に実装できそうじゃないですか?
例えば、Pythonで以下のような順序で条件分岐を組んだとします。
for i in range(1, 16):
if i % 3 == 0:
print("Fizz")
elif i % 5 == 0:
print("Buzz")
elif i % 15 == 0:
print("FizzBuzz")
else:
print(i)
一見するとルールをすべて網羅しているように見えますが、このプログラムを実行して 15 に到達したとき、期待される “FizzBuzz” ではなく “Fizz” が出力されてしまいます。
if の条件に引っかかって、下の elif まで辿り着かないですね。
数学的に考えると、3の倍数の集合を 、5の倍数の集合を としたとき、15の倍数は共通部分 です。
Pythonの if-elif-else 構文は「真になったブロックを実行した時点でそれ以降の判定をスキップする」という性質を持つため、最も狭い範囲である の判定を最優先に行う必要があります。
3. 正しい実装例
(実装例1):15の倍数の判定を最優先する
最も直感的かつ標準的なやりかたとして、共通部分である15の倍数の判定を先頭に配置する構成です。
for i in range(1, 16):
if i % 15 == 0:
print("FizzBuzz")
elif i % 3 == 0:
print("Fizz")
elif i % 5 == 0:
print("Buzz")
else:
print(i)
(実装例2):文字列の結合を利用する
もう一つのやりかたは、条件を独立させて文字列の結合を利用する方法です。この方法であれば、15の倍数という条件をわざわざ書く必要がなくなります。
for i in range(1, 16):
output = ""
if i % 3 == 0:
output += "Fizz"
if i % 5 == 0:
output += "Buzz"
print(output or i)
print(output or i) はどういう意味ですか?
(実装例3):条件文なしの解法
さらにコードをスマートに、かつ極限まで短くする方法として、Pythonの文字列スライスの仕様を逆手に取った、条件分岐を一切使わない記述方法があります。
for i in range(1, 16):
print("Fizz"[i % 3 * 4 :] + "Buzz"[i % 5 * 4 :] or i)
ポイントは “Fizz”[i % 3 * 4 :]の部分です。Pythonでは、スライスの開始インデックスが文字列の長さを超えていると、エラーにならずに空文字列 "" を返す仕様(スライスハック)があります。
iが3の倍数のとき:i % 3は0になるため “Fizz”[0:]となり、“Fizz” がそのまま残ります。iが3の倍数でないとき:i % 3は1か2になります。それを4倍すると 4 か 8 です。 “Fizz”は4文字しか存在しないため、4文字目以降をスライスしようとすると空文字列 "" になります。
これを “Buzz”でも同時に行い、最後に or i でルールに引っかからない数字に対応しています。
(実装例4):リストの周期性を利用した割り算の演算を使わない解法
3の倍数と5の倍数は、15番周期で同じパターンを繰り返すという最小公倍数の周期性を利用すれば、ループの中で毎回 i % 3 などの割り算をする必要がなくなります。
pattern = [
"", "", "Fizz", "", "Buzz",
"Fizz", "", "", "Fizz", "Buzz",
"", "Fizz", "", "", "FizzBuzz"
]
for i in range(1, 16):
print(pattern[(i - 1) % 15] or i)
ループが回るたびに (i - 1) % 15 を計算することで、配列のインデックスが 0, 1, 2 ... 14, 0, 1 ... と綺麗にループします。該当する要素が空文字列 "" であれば、やはり or i の評価によってその数値がそのまま出力されます。
まとめ
FizzBuzz問題は単純な問題ですが、多角的な視点からこの問題を解いていくことで、様々な力を身に着けられる良問といえるでしょう。