FizzBuzz の判定部分

生田 陸人
LuaGate エンジニア / 現役エンジニア
編集 LuaGate編集部

FizzBuzz の判定部分 とは

プログラマー面接の定番問題 FizzBuzz の中核となる判定ロジックを、if-else if-else と剰余演算で組み立てよう。

FizzBuzz ─ プログラマー面接の定番問題に挑む

FizzBuzz という言葉を、プログラミングの世界では一度は聞いたことがあるかもしれません。もともとは英語圏の小学生が遊ぶ数字遊びで、1 から順に数字を言っていき、3 の倍数のときは数字の代わりに Fizz5 の倍数のときは Buzz15 の倍数のときは FizzBuzz と言う、というルールです。これをそのままコードで書いてみよう、というのが FizzBuzz 問題です。

FizzBuzz は 2007 年ごろから「面接で基本的なコードが書けるかを 5 分でふるい分けるための問題」として有名になりました。Jeff Atwood という有名なエンジニアがブログで取り上げたのがきっかけで、現在も世界中の IT 企業で採用試験に使われています。Java Python Ruby どの言語でも書けるよう、自分の得意な言語で 1 度は通しで書けるようにしておくと安心です。

問題自体はとても単純に見えますが、書いてみると if 文の 判定順序else if の使いどころ、% (剰余) 演算子の活用といった、基礎の組み合わせが詰まっています。逆に言えば、FizzBuzz がスラスラ書けるなら、Java の制御構文と算術演算子は身についている、と判断できるわけです。

このレッスンでは、FizzBuzz の中核である 判定部分 だけを取り出して関数化します。次のレッスンで for ループと組み合わせて、1 から 100 まで一気に出力する完全版に進む予定です。まずはひとつの数字 n を渡されたら、ルールに従って正しい文字列を返すメソッドを作りましょう。

課題のルールをもう一度整理する

今回作る fizzbuzz(int n) メソッドのルールは次の通りです。

  • n3 でも 5 でも割り切れる → "FizzBuzz"
  • n3 で割り切れる (5 では割り切れない) → "Fizz"
  • n5 で割り切れる (3 では割り切れない) → "Buzz"
  • それ以外 → n をそのまま文字列にして返す (例えば 7 なら "7")

戻り値は常に String なので、7 のような数字をそのまま返したいときも int ではなく "7" という文字列にして返す必要があります。ここで使うのが String.valueOf(n) です。Integer.toString(n) でも書けますが、本コースでは String.valueOf で統一しておきましょう。

% 演算子で「割り切れるか」を判定する

「割り切れる」を Java で表現するときは、剰余演算子 % を使います。a % b は「ab で割った余り」を返す演算子で、これが 0 なら割り切れたことを意味します。

Java

9 % 3 // 0 ─ 9 は 3 で割り切れる 10 % 5 // 0 ─ 10 は 5 で割り切れる 7 % 3 // 1 ─ 7 を 3 で割ると余り 1 15 % 15 // 0 ─ 15 は 15 で割り切れる

つまり「n3 で割り切れる」は n % 3 == 0、「n5 で割り切れる」は n % 5 == 0 と書けます。これを if の条件式の中に入れて、ルールに沿った分岐を作っていきます。

boolean を返す式は、頭の中で「これは true / false の問いかけだ」と読み替えるとスッキリ理解できます。n % 3 == 0 は「n3 で割った余りはゼロですか?」という質問で、答えが Yes なら trueNo なら false です。

判定順序が一番のポイント

FizzBuzz でもっとも大事なのは 判定の順番 です。なぜなら、15 のような数字は 3 でも 5 でも割り切れる からです。もし最初に n % 3 == 0 を判定してしまうと、15 のときも先に "Fizz" が返ってしまい、"FizzBuzz" までたどり着けません。

正しい判定順序は次のとおりです。

  1. まず FizzBuzz (3 でも 5 でも割り切れる) を判定する
  2. 次に Fizz (3 で割り切れる) を判定する
  3. 次に Buzz (5 で割り切れる) を判定する
  4. どれにも当てはまらなければ n を文字列にして返す

図にすると次のような流れになります。else if を使って、上から条件を順番にふるい落としていくイメージです。

diagram (will load when visible)

この順序を 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 でそれ以外を受け止める。Javaif-else if-else の使い方の ど真ん中 の例だといえます。

return 文に到達した瞬間、メソッドはそこで終了します。return "FizzBuzz"; が実行されると、その下の else if ブロックは一切評価されません。だから上から順に絞り込んでいけば、安心して次の条件に進めるのです。

もうひとつの書き方 ─ % 15 を使う

n % 3 == 0 && n % 5 == 0n % 15 == 0 とまったく同じ意味です。なぜなら、35 の最小公倍数は 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 のほうが上、という意見もあります。チームの好みやコーディング規約に合わせて使い分ければ大丈夫です。

一般化すると、ab が互いに素 (共通の約数が 1 だけ) のときは n % a == 0 && n % b == 0n % (a * b) == 0 が同値になります。35 はまさに互いに素な関係なので、こうしたショートカットが使えます。数学の知識がコードの簡潔さにつながる、よい例です。

動きを追ってみる

試しに fizzbuzz(15) を呼び出したときの流れを順番に追ってみましょう。

  1. n15 が渡される
  2. n % 3 == 0 && n % 5 == 0 を評価 → 15 % 3015 % 50、両方 true なので全体は true
  3. return "FizzBuzz"; が実行され、メソッド終了

今度は fizzbuzz(7) の場合です。

  1. n7 が渡される
  2. 7 % 3 == 0 && 7 % 5 == 07 % 31 なので最初の条件は false
  3. 7 % 3 == 0false
  4. 7 % 5 == 07 % 52 なので false
  5. すべての条件が 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; と書いてしまうと「intString として返せません」というコンパイルエラーになります。整数を文字列にしたいときは 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 連発で書いてもテストは通ります。ただし、最初の ifreturn するから次の if には行かない、というロジックを頭の中で追わないといけなくなり、読みづらくなります。条件が排他的に並ぶときは else if を使う という意識を持っておくと、保守しやすいコードになります。

コードレビューでよく言われるのが「if-else if を使うとロジックの意図がはっきりする」というアドバイスです。else if は「ここまでの条件はどれも false だった」という情報を読み手に伝えてくれます。if を独立に並べると、その情報は読み手が頭の中で再構築しないといけなくなります。

次のレッスンに向けて

今回はひとつの数字 n を判定するだけの関数を作りましたが、本来の FizzBuzz 問題は 1 から 100 まで全部出力します。次のレッスンでは、ここで作った fizzbuzzfor ループの中で 1 から 100 まで呼び出して、本物の FizzBuzz を完成させます。

まずはこの判定ロジックを身体に染み込ませてください。fizzbuzz(15) fizzbuzz(9) fizzbuzz(10) fizzbuzz(7) のような単体テストがスラスラ書けるようになれば、ループと組み合わせるのはとても簡単です。

やってみよう

それでは課題に挑戦してみましょう。

  1. 右側のエディタを開く
  2. if (n % 3 == 0 && n % 5 == 0) の中で return "FizzBuzz"; を書く
  3. else if (n % 3 == 0) の中で return "Fizz"; を書く
  4. else if (n % 5 == 0) の中で return "Buzz"; を書く
  5. 最後の else の中で return String.valueOf(n); を書く
  6. 「実行」ボタンを押して、テストが緑になることを確認する

テストでは 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 つができることを確認しましょう。

  1. FizzBuzz 判定 の要点を自分の言葉で説明できる
  2. このレッスンの最小コード (または操作手順) を見ずに書ける
  3. 練習問題やクイズで間違えた箇所を読み直して理解した

理解度チェック (30 秒)

Q. FizzBuzz 判定 とは何か、1 文で説明してください。

この章のポイント

A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。

関連レッスン

要件

  1. クラス名は Solution、メソッド名は fizzbuzz、引数は int n 1 つ、戻り値の型は String
  2. 判定は FizzBuzzFizzBuzz → それ以外、の順で行うこと (FizzBuzz を最初に判定しないと 15 のときに Fizz が返ってしまう)
  3. どれにも当てはまらないときは 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"

ヒント

main.java
main.java
学習モード

メモ

FizzBuzz の判定部分

⌘S で保存