FizzBuzz 完成
FizzBuzz 完成 とは
前章の FizzBuzz 判定と for ループを組み合わせて、1 から n までの FizzBuzz を空白区切りで返す完成版を作ろう。
FizzBuzz 完成 ─ for と判定を組み合わせる
前のセクションで作った fizzbuzz(int n) は、ひとつの数字を受け取って "Fizz" "Buzz" "FizzBuzz" "7" のような文字列を返すだけのメソッドでした。今回はそこに for ループを足して、1 から n までを一気に処理する 本物の FizzBuzz を完成させます。これが書けると、面接の定番問題が文字どおり「完答」できる状態になります。
前章の
fizzbuzzは 判定 だけを担当する純粋な関数でした。今回作るfizzbuzzListは、その判定をforループでn回呼び出して、結果をひとつの文字列につないでいきます。「小さい関数を組み合わせて大きい関数を作る」というのは、Javaに限らずあらゆる言語に共通する基本姿勢です。
仕様を確認しておきましょう。fizzbuzzList(int n) は 1 から n までを FizzBuzz ルールで変換した文字列を、半角スペース区切り で返します。具体例は次の通りです。
fizzbuzzList(5)は"1 2 Fizz 4 Buzz"fizzbuzzList(15)は"1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz"fizzbuzzList(3)は"1 2 Fizz"fizzbuzzList(1)は"1"(要素 1 個なので空白なし)
全体像をつかむ
処理の流れはとてもシンプルです。空っぽの文字列を用意して、i を 1 から n まで動かしながら、その i に対応する FizzBuzz 文字列を末尾に足していくだけです。
ポイントは「for の中で前章の判定ロジックを呼び出す」「result という変数に少しずつ追加していく」「最後の要素のあとにスペースを残さない」の 3 つです。順番に見ていきましょう。
シンプル版 ─ 文字列連結で書いてみる
まずは一番素直な書き方です。String を += でつないでいき、最後に trim() で末尾の余分なスペースを取り除きます。
Java
public class Solution {
public static String fizzbuzzList(int n) {
String result = "";
for (int i = 1; i <= n; i++) {
if (i % 15 == 0) {
result += "FizzBuzz";
} else if (i % 3 == 0) {
result += "Fizz";
} else if (i % 5 == 0) {
result += "Buzz";
} else {
result += i;
}
result += " ";
}
return result.trim();
}
}for (int i = 1; i <= n; i++) で i が 1, 2, 3, ..., n と動きます。条件式が i < n ではなく i <= n であることに注意してください。n まで 含めて 処理したいので、ここは <= が正解です。
判定の中身は前章とまったく同じです。i % 15 == 0 で FizzBuzz を最初に判定し、その次に Fizz、最後に Buzz、どれにも当てはまらなければ i を文字列として追加します。result += i; のように int をそのまま足しても、Java が自動で文字列に変換してくれます。
String result = "";の""は 空の文字列 であって、nullではありません。String result = null;から始めるとresult += "...";のときに"null..."という文字列ができてしまうので注意してください。初期値は必ず""です。
各要素を追加したあとに result += " "; で半角スペースを足しています。これだと最終ループでも末尾にスペースが残るので、最後に result.trim() で前後の空白文字をまとめて削除しています。trim() は String クラスに最初から用意されているメソッドで、両端の半角スペース・タブ・改行を取り除いてくれます。
Mermaid でループの動きをもう一度見る
for ループの内部を別の角度で書くと、判定 → 追加 → スペース追加、をひたすら繰り返している構造がよく見えます。
for の 3 つの部分 (初期化 条件式 更新式) は、それぞれ「最初に 1 回だけ」「毎回チェック」「毎回最後に」走ります。この構造はあらゆる for ループに共通するので、頭の中で必ずこの図が再生できるようになっておくと安心です。
大きい n では StringBuilder を使うべき理由
シンプル版の result += "..."; はとても読みやすい一方で、n が大きくなると一気に遅くなる という欠点があります。なぜでしょうか?
Java の String は イミュータブル (一度作ったら変更できない) です。つまり result += "Fizz"; は、内部では「新しい String を作ってその中に result と "Fizz" を全部コピーしてから、result をその新しい String に置き換える」という処理が走っています。n が 100 くらいなら気になりませんが、10000 100000 と増えてくると、毎回コピーが走るのでループ回数の 2 乗に比例して遅くなります。
そこで登場するのが StringBuilder です。StringBuilder は 可変 な文字列バッファで、append で末尾に追加するときに毎回コピーを取らずに済みます。書き換えた版は次のとおりです。
Java
public class Solution {
public static String fizzbuzzList(int n) {
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= n; i++) {
if (i > 1) {
sb.append(" ");
}
if (i % 15 == 0) {
sb.append("FizzBuzz");
} else if (i % 3 == 0) {
sb.append("Fizz");
} else if (i % 5 == 0) {
sb.append("Buzz");
} else {
sb.append(i);
}
}
return sb.toString();
}
}ここでは末尾スペースを取らなくて済むように、先頭判定 という別のテクニックを使っています。「i が 1 のときはスペースを足さない、i が 2 以上のときは要素の 前に スペースを足す」というロジックです。if (i > 1) sb.append(" "); の 1 行がそれを実現しています。最後に sb.toString() で String に変換すれば完成です。
どちらの書き方でもテストは通りますが、
nが1万以上になることがわかっているならStringBuilder一択 です。実務でも「文字列連結をforで大量に行うところはStringBuilderに置き換える」というのは定番の最適化テクニックなので、頭の隅に置いておきましょう。
末尾のスペースを取り除く 2 つのアプローチ
気付いた人もいるかもしれませんが、この課題の地味なポイントは 末尾の空白問題 です。"1 2 Fizz 4 Buzz " のように最後にスペースが残ると、テストの期待値 "1 2 Fizz 4 Buzz" と一致せず fail します。アプローチは大きく 2 つに分かれます。
- 後始末派 ─ 毎回末尾にスペースを足しておいて、最後に
result.trim()でまとめて取り除く。シンプル版で使った書き方 - 先頭判定派 ─ 2 番目以降の要素の 前 にスペースを足す。
StringBuilder版で使った書き方
どちらも有効ですが、StringBuilder のときは trim() で内部バッファ全体を走査するコストがあるので、先頭判定 のほうが効率的だ、というのが一般的な意見です。String の += でやるときは、もうコピーが大量に走っているので trim() の差は誤差なので気にしなくて大丈夫です。
動きを追ってみる ─ fizzbuzzList(5)
試しに fizzbuzzList(5) を呼び出したときの動きを、シンプル版で追ってみましょう。
result = ""でスタートi = 1どの条件にも当てはまらず →result = "1 "i = 2どの条件にも当てはまらず →result = "1 2 "i = 33 % 15は0ではなく、3 % 3は0なのでFizz→result = "1 2 Fizz "i = 4どの条件にも当てはまらず →result = "1 2 Fizz 4 "i = 55 % 15は0ではなく、5 % 3も0ではなく、5 % 5は0なのでBuzz→result = "1 2 Fizz 4 Buzz "- ループ終了。
result.trim()で末尾スペースを取って"1 2 Fizz 4 Buzz"を返す
判定ロジックが前章で身についていれば、追加で考えないといけないのは「for でどう回すか」「区切り文字をどう扱うか」のたった 2 点だけです。
よくある間違い
FizzBuzz の完成版を書くときに、ほぼ全員が一度はハマる落とし穴を 3 つ紹介します。
- 判定順序を逆にする ─ 前章でも触れた話ですが、
if (i % 3 == 0)を最初に書いてしまうと15のときも"Fizz"が返ってしまい、"FizzBuzz"が一度も出てきません。必ずi % 15 == 0を最初に判定する か、i % 3 == 0 && i % 5 == 0を最初に書きましょう - 末尾のスペースを取り忘れる ─
"1 2 Fizz 4 Buzz "のように末尾にスペースが残ると、テストの期待値"1 2 Fizz 4 Buzz"と異なり fail します。trim()で取り除くか、最初から先頭判定方式で書きましょう nが0のときに何が返るかを考えていない ─ もしfizzbuzzList(0)が呼ばれたら、forは 1 回も回らずresultは""のままtrim()を経て空文字列が返ります。今回のテストではn >= 1しか使いませんが、業務コードなら エッジケース として「0 のときに何を返すべきか」を意識しておくとバグが減ります
もうひとつ、ループの条件を i < n と書き間違える人がたまにいます。これは n 自身が処理されない バグで、fizzbuzzList(5) が "1 2 Fizz 4" で止まるので、テストの 2 ケース目以降がガッツリ fail します。<= か < か、書く前に「n を含めるかどうか」を必ず意識しましょう。
ループの境界条件 (
fencepost error) は、ベテランプログラマでもうっかり間違えるバグの代表格です。forを書いたら必ず「最初のiは?」「最後のiは?」を声に出して確認するクセを付けると、99% のオフバイワンエラーは防げます。
やってみよう
それでは課題に挑戦してみましょう。
- 右側のエディタを開く
String result = "";で結果用の変数を初期化するfor (int i = 1; i <= n; i++)で1からnまでループを回す- ループの中で
i % 15i % 3i % 5の順番で判定し、対応する文字列をresultに足していく - 各要素のあとに半角スペースを足す
- 最後に
result.trim()をreturnする
テストでは fizzbuzzList(5) fizzbuzzList(15) fizzbuzzList(3) fizzbuzzList(1) のような複数のケースを実行します。n = 1 の最小ケースで末尾スペースが残らないこと、n = 15 でちゃんと最後に "FizzBuzz" が登場すること、の両方が大事です。
慣れてきたら、StringBuilder 版に書き換えてみる、trim() を使わない先頭判定方式に書き換えてみる、と挑戦してみてください。同じ仕様を別の書き方で実現できるようになると、コードの引き出しが一気に増えます。これで FizzBuzz 問題は完答です。次は配列やコレクションを使った、もう一段大きな処理に進んでいきましょう。
よくある質問
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 標準を固めるのが効率的です。
次のレッスン
次は 繰り返しクイズ で、前章の FizzBuzz 判定と for ループを組み合わせて、1 から n までの FizzBuzz を空白区切りで返す完成版を作ろう を学びます。
事前確認 — 進む前に次の 3 つができることを確認しましょう。
- FizzBuzz の要点を自分の言葉で説明できる
- このレッスンの最小コード (または操作手順) を見ずに書ける
- 練習問題やクイズで間違えた箇所を読み直して理解した
理解度チェック (30 秒)
Q. FizzBuzz とは何か、1 文で説明してください。
A. 本文の「このレッスンで分かること」または冒頭の説明文を見直し、自分の言葉で要約できれば OK。詰まったら本レッスンの最初の H2 セクションを読み返してみましょう。
関連レッスン
要件
- クラス名は
Solution、メソッド名はfizzbuzzList、引数はint n1 つ、戻り値の型はString 1からnまでをforループで回し、各値を FizzBuzz ルールで変換した文字列を 半角スペース区切り でつなげること- 末尾に余分なスペースを残さないこと (
trim()を使うか、先頭判定方式でスペースを 2 番目以降の前に置く)
入出力例
test-cases.txt
fizzbuzzList(5) → "1 2 Fizz 4 Buzz"
fizzbuzzList(15) → "1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz"
fizzbuzzList(3) → "1 2 Fizz"
fizzbuzzList(1) → "1"
fizzbuzzList(10) → "1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz"