FizzBuzz の判定部分
FizzBuzz の判定部分 とは
プログラマー面接の定番問題 FizzBuzz の中核となる判定ロジックを、if-else if-else と剰余演算で組み立てよう。
FizzBuzz ─ プログラマー面接の定番問題に挑む
FizzBuzz という言葉を、プログラミングの世界では一度は聞いたことがあるかもしれません。もともとは英語圏の小学生が遊ぶ数字遊びで、1 から順に数字を言っていき、3 の倍数のときは数字の代わりに Fizz、5 の倍数のときは Buzz、15 の倍数のときは FizzBuzz と言う、というルールです。これをそのままコードで書いてみよう、というのが FizzBuzz 問題です。
FizzBuzzは 2007 年ごろから「面接で基本的なコードが書けるかを 5 分でふるい分けるための問題」として有名になりました。Jeff Atwood という有名なエンジニアがブログで取り上げたのがきっかけで、現在も世界中のIT企業で採用試験に使われています。JavaPythonRubyどの言語でも書けるよう、自分の得意な言語で 1 度は通しで書けるようにしておくと安心です。
問題自体はとても単純に見えますが、書いてみると if 文の 判定順序 や else if の使いどころ、% (剰余) 演算子の活用といった、基礎の組み合わせが詰まっています。逆に言えば、FizzBuzz がスラスラ書けるなら、Java の制御構文と算術演算子は身についている、と判断できるわけです。
このレッスンでは、FizzBuzz の中核である 判定部分 だけを取り出して関数化します。次のレッスンで for ループと組み合わせて、1 から 100 まで一気に出力する完全版に進む予定です。まずはひとつの数字 n を渡されたら、ルールに従って正しい文字列を返すメソッドを作りましょう。
課題のルールをもう一度整理する
今回作る fizzbuzz(int n) メソッドのルールは次の通りです。
nが3でも5でも割り切れる →"FizzBuzz"nが3で割り切れる (5 では割り切れない) →"Fizz"nが5で割り切れる (3 では割り切れない) →"Buzz"- それ以外 →
nをそのまま文字列にして返す (例えば7なら"7")
戻り値は常に String なので、7 のような数字をそのまま返したいときも int ではなく "7" という文字列にして返す必要があります。ここで使うのが String.valueOf(n) です。Integer.toString(n) でも書けますが、本コースでは String.valueOf で統一しておきましょう。
% 演算子で「割り切れるか」を判定する
「割り切れる」を Java で表現するときは、剰余演算子 % を使います。a % b は「a を b で割った余り」を返す演算子で、これが 0 なら割り切れたことを意味します。
Java
9 % 3 // 0 ─ 9 は 3 で割り切れる
10 % 5 // 0 ─ 10 は 5 で割り切れる
7 % 3 // 1 ─ 7 を 3 で割ると余り 1
15 % 15 // 0 ─ 15 は 15 で割り切れるつまり「n が 3 で割り切れる」は n % 3 == 0、「n が 5 で割り切れる」は n % 5 == 0 と書けます。これを if の条件式の中に入れて、ルールに沿った分岐を作っていきます。
booleanを返す式は、頭の中で「これは true / false の問いかけだ」と読み替えるとスッキリ理解できます。n % 3 == 0は「nを3で割った余りはゼロですか?」という質問で、答えがYesならtrue、Noならfalseです。
判定順序が一番のポイント
FizzBuzz でもっとも大事なのは 判定の順番 です。なぜなら、15 のような数字は 3 でも 5 でも割り切れる からです。もし最初に n % 3 == 0 を判定してしまうと、15 のときも先に "Fizz" が返ってしまい、"FizzBuzz" までたどり着けません。
正しい判定順序は次のとおりです。
- まず
FizzBuzz(3 でも 5 でも割り切れる) を判定する - 次に
Fizz(3 で割り切れる) を判定する - 次に
Buzz(5 で割り切れる) を判定する - どれにも当てはまらなければ
nを文字列にして返す
図にすると次のような流れになります。else if を使って、上から条件を順番にふるい落としていくイメージです。
この順序を Java のコードに落とすと次のようになります。
Java
public class Solution {
public static String fizzbuzz(int n) {
if (n % 3 == 0 && n % 5 == 0) {
return "FizzBuzz";
} else if (n % 3 == 0) {
return "Fizz";
} else if (n % 5 == 0) {
return "Buzz";
} else {
return String.valueOf(n);
}
}
}if から始まり、else if で 2 つの中間条件をつなぎ、最後に else でそれ以外を受け止める。Java の if-else if-else の使い方の ど真ん中 の例だといえます。
return文に到達した瞬間、メソッドはそこで終了します。return "FizzBuzz";が実行されると、その下のelse ifブロックは一切評価されません。だから上から順に絞り込んでいけば、安心して次の条件に進めるのです。
もうひとつの書き方 ─ % 15 を使う
n % 3 == 0 && n % 5 == 0 は n % 15 == 0 とまったく同じ意味です。なぜなら、3 と 5 の最小公倍数は 15 だからです。これを使うと、最初の条件を少しスッキリ書けます。
Java
public class Solution {
public static String fizzbuzz(int n) {
if (n % 15 == 0) {
return "FizzBuzz";
} else if (n % 3 == 0) {
return "Fizz";
} else if (n % 5 == 0) {
return "Buzz";
} else {
return String.valueOf(n);
}
}
}どちらの書き方も結果は同じです。%% 15 のほうが計算回数は少ないですが、意味のわかりやすさは n %% 3 == 0 && n %% 5 == 0 のほうが上、という意見もあります。チームの好みやコーディング規約に合わせて使い分ければ大丈夫です。
一般化すると、
aとbが互いに素 (共通の約数が 1 だけ) のときはn % a == 0 && n % b == 0とn % (a * b) == 0が同値になります。3と5はまさに互いに素な関係なので、こうしたショートカットが使えます。数学の知識がコードの簡潔さにつながる、よい例です。
動きを追ってみる
試しに fizzbuzz(15) を呼び出したときの流れを順番に追ってみましょう。
nに15が渡されるn % 3 == 0 && n % 5 == 0を評価 →15 % 3は0、15 % 5も0、両方trueなので全体はtruereturn "FizzBuzz";が実行され、メソッド終了
今度は fizzbuzz(7) の場合です。
nに7が渡される7 % 3 == 0 && 7 % 5 == 0→7 % 3は1なので最初の条件はfalse7 % 3 == 0→false7 % 5 == 0→7 % 5は2なのでfalse- すべての条件が
falseなので最後のelseに入り、String.valueOf(7)が"7"を返す
このように、else if の連鎖は「上から順に当てはまる場所を探していき、見つかればそこで終了、見つからなければ最後の else に落ちる」という動きをします。難しいことは何もありません。
よくある間違い
FizzBuzz を初めて書くときに必ず引っかかる落とし穴を 3 つ紹介します。
- 判定順序を逆にする ─
if (n % 3 == 0) return "Fizz";から書き始めてしまうと、15のときも"Fizz"が返ってしまい、"FizzBuzz"の出番がなくなります。FizzBuzz(両方の倍数) を 必ず最初に 判定するのが鉄則です String.valueOfを忘れて型エラー ─ 戻り値の型はStringなのに、return n;と書いてしまうと「intをStringとして返せません」というコンパイルエラーになります。整数を文字列にしたいときはString.valueOf(n)かInteger.toString(n)を使うか、もしくはreturn "" + n;のように空文字と連結する手もあります% 3 && % 5のような略記を使う ─n % 3 && n % 5のように書きたくなる人がいますが、これは文法エラーです。&&の左右にはbooleanが必要なので、必ずn % 3 == 0 && n % 5 == 0のように== 0まで 書いてください
もうひとつ、else を全部省略して if を 4 連発で書いてもテストは通ります。ただし、最初の if で return するから次の if には行かない、というロジックを頭の中で追わないといけなくなり、読みづらくなります。条件が排他的に並ぶときは else if を使う という意識を持っておくと、保守しやすいコードになります。
コードレビューでよく言われるのが「
if-else ifを使うとロジックの意図がはっきりする」というアドバイスです。else ifは「ここまでの条件はどれも false だった」という情報を読み手に伝えてくれます。ifを独立に並べると、その情報は読み手が頭の中で再構築しないといけなくなります。
次のレッスンに向けて
今回はひとつの数字 n を判定するだけの関数を作りましたが、本来の FizzBuzz 問題は 1 から 100 まで全部出力します。次のレッスンでは、ここで作った fizzbuzz を for ループの中で 1 から 100 まで呼び出して、本物の FizzBuzz を完成させます。
まずはこの判定ロジックを身体に染み込ませてください。fizzbuzz(15) fizzbuzz(9) fizzbuzz(10) fizzbuzz(7) のような単体テストがスラスラ書けるようになれば、ループと組み合わせるのはとても簡単です。
やってみよう
それでは課題に挑戦してみましょう。
- 右側のエディタを開く
if (n % 3 == 0 && n % 5 == 0)の中でreturn "FizzBuzz";を書くelse if (n % 3 == 0)の中でreturn "Fizz";を書くelse if (n % 5 == 0)の中でreturn "Buzz";を書く- 最後の
elseの中でreturn String.valueOf(n);を書く - 「実行」ボタンを押して、テストが緑になることを確認する
テストでは fizzbuzz(15) fizzbuzz(9) fizzbuzz(10) fizzbuzz(7) fizzbuzz(1) fizzbuzz(30) の 6 ケースを実行します。1 のような小さな数字、30 のような少し大きめの数字、15 の倍数まで、いろいろなパターンが含まれているので、判定順序が間違っていればどこかで必ず fail します。
慣れてきたら、% 15 を使う書き方に変えてみる、else を省略して if を 4 連発で書いてみる、String.valueOf の代わりに Integer.toString を使ってみる、と書き換えて遊ぶのもおすすめです。同じ動きを別の書き方で表現できるようになると、コードを読む力もぐっと上がります。
よくある質問
Q. Java と他言語の文法はどう違いますか?
A. Java は静的型付けで、変数宣言時に型を明示します(int x = 0;)。中括弧でブロックを表し、文末にセミコロンが必要です。Python の動的型付けや JavaScript の柔軟さに慣れていると最初は窮屈ですが、コンパイル時にバグが発見できる安全性がメリットです。
Q. コードが動かないときに最初に見るべき場所は?
A. コンパイルエラーは行番号とエラー種別(cannot find symbol、incompatible types など)が表示されます。実行時例外はスタックトレースの最初の at ... が原因行です。IDE(IntelliJ / VS Code)の警告も丁寧に潰すと、半分のバグは未然に防げます。
Q. Java の習得後に学ぶべき技術は何ですか?
A. 基本構文を抑えたら java-intermediate(コレクション / ジェネリクス / Stream / 例外)に進み、Spring Boot や Web 開発に展開するのが王道です。クラウド時代は Kotlin / Scala への展開も視野に入りますが、まずは Java 標準を固めるのが効率的です。
次のレッスン
次は 第 3 章 まとめクイズ で、プログラマー面接の定番問題 FizzBuzz の中核となる判定ロジックを、if-else if-else と剰余演算で組み立てよう を学びます。
事前確認 — 進む前に次の 3 つができることを確認しましょう。
- FizzBuzz 判定 の要点を自分の言葉で説明できる
- このレッスンの最小コード (または操作手順) を見ずに書ける
- 練習問題やクイズで間違えた箇所を読み直して理解した
理解度チェック (30 秒)
Q. FizzBuzz 判定 とは何か、1 文で説明してください。
A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。
関連レッスン
要件
- クラス名は
Solution、メソッド名はfizzbuzz、引数はint n1 つ、戻り値の型はString - 判定は
FizzBuzz→Fizz→Buzz→ それ以外、の順で行うこと (FizzBuzzを最初に判定しないと15のときにFizzが返ってしまう) - どれにも当てはまらないときは
String.valueOf(n)のようにnを文字列に変換して返すこと
入出力例
test-cases.txt
fizzbuzz(15) → "FizzBuzz"
fizzbuzz(9) → "Fizz"
fizzbuzz(10) → "Buzz"
fizzbuzz(7) → "7"
fizzbuzz(1) → "1"
fizzbuzz(30) → "FizzBuzz"