二重ループ

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

二重ループ とは

ループの中にループを入れた二重ループの考え方を学び、ピラミッド型の文字列を組み立ててみよう。本レッスンでは、二重ループ の基本から実際の使いどころまでを整理し、現場で迷わず使える形に落とし込みます。

ループの中にループを入れる二重ループ

ここまでのレッスンで for 文や while 文を使った繰り返しを学んできました。今回はその応用編として、ループの中にもうひとつループを書く「二重ループ (nested loop)」というテクニックを取り上げます。日本語では「入れ子ループ」と呼ばれることもあります。表を作るような縦横の処理、* * * のような図形描画、9 x 9 のかけ算九九など、世の中の繰り返し処理はだいたい二重ループの仲間として表現できるので、ここで一気に慣れてしまいましょう。

ループは 1 つだと「一列に並んだものを処理する」道具でしたが、2 つ重ねると「縦と横の二次元に並んだものを処理する」道具に変わります。表計算ソフトのセルを上から下、左から右にひとつずつ見ていくイメージです。

二重ループは強力ですが、書き方を間違えるとプログラムが想像以上に重くなったり、出力がぐちゃぐちゃになったりします。このレッスンではまず文法を押さえて、Mermaid 図で実行順序をしっかりイメージしてから、ピラミッドを描く課題に挑戦します。

二重ループの文法

基本の形は次のとおりです。for を 2 段重ねて書きます。

Java

for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) { System.out.println("i=" + i + ", j=" + j); } }

外側の for行 (row) を担当し、内側の for列 (column) や中身 を担当する、と分担を決めるのが定番です。Java では慣習として外側のループ変数を i、内側を j と名付けます。次のレッスンでさらに外側があれば kl と続いていきます。

変数名は自由ですが、i j k の順は世界中の Java プログラマで共通の合言葉です。意味は index (添え字) の頭文字。意味のない名前に見えますが、二重ループであることを一瞬で読み取れる効果があります。

上のコードを実行すると、i=0, j=0 から i=0, j=3 まで内側がぐるっと回り、外側が i=1 に進むとまた内側が j=0 から動き出す、という順番で出力されます。

プレーンテキスト

i=0, j=0 i=0, j=1 i=0, j=2 i=0, j=3 i=1, j=0 i=1, j=1 ...

Mermaid で実行順序を追う

文字だけだとピンと来ないので、内側と外側のループがどんな順番で動くかを Mermaidflowchart で見てみましょう。

diagram (will load when visible)

ポイントは、内側ループが j を最後まで回し切るまで外側の i は止まったまま、という点です。i が 1 つ進むためには、内側の j がぐるっと一周しないといけません。

「外側が 1 進むあいだに、内側は最初から最後まで全部回る」。これだけ覚えれば二重ループの実行順序の 9 割は理解できたようなものです。

行と列に分けて考える

二重ループを書くときは、「外側で 1 行ぶん、内側でその行の中身」と段階を分けて設計すると迷いません。たとえば次のような 3 x 4 の表を作りたいとします。

プレーンテキスト

* * * * * * * * * * * *

これを Java で組み立てると次のような形になります。

Java

StringBuilder sb = new StringBuilder(); for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) { sb.append("*"); } sb.append("\n"); } String result = sb.toString();

外側のループが「行を 3 回繰り返す」を担当し、内側のループが「* を 4 個並べる」を担当しています。内側ループが終わったタイミングで改行 \n を追加するところがコツです。改行を内側のループに入れてしまうと毎マスごとに改行されてしまい、縦に長くなります。

String+ でくっつけても動きますが、ループの中で文字列を連結すると毎回新しいオブジェクトが作られて遅くなりがちです。文字列をたくさん組み立てるときは StringBuilder を使うのが Java の作法、と覚えておきましょう。

ピラミッドを描いてみる

今回の課題は 3 x 4 のような四角ではなく、行ごとに * の数が変わる ピラミッド です。pyramid(3) を呼ぶと次のような結果が返ってきます。

プレーンテキスト

* ** ***

pyramid(5) ならこうです。

プレーンテキスト

* ** *** **** *****

つまり i 行目には i + 1 個の * が並びます。内側ループの終わり条件を j <= i または j < i + 1 のように i に連動させるのがポイントです。さっきの四角形の例と違って、内側ループの上限が外側の i に依存します。

Java

public class Solution { public static String pyramid(int n) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < n; i++) { for (int j = 0; j <= i; j++) { sb.append("*"); } if (i < n - 1) { sb.append("\n"); } } return sb.toString(); } }

最後の行のあとに改行を付けない、という細かい仕様にも注意してください。pyramid(3) の期待値は "*\n**\n***" であって "*\n**\n***\n" ではありません。末尾余分な改行はテストが fail する典型パターンです。

二重ループと計算量の話

二重ループは便利ですが、油断するとプログラムがすぐに重くなります。外側が n 回、内側も n 回回ると、本体は n * n 回実行されることになります。n = 10 なら 100 回、n = 1000 なら 1,000,000 回、n = 100000 なら 10,000,000,000 回です。三重ループにすると n * n * n で、もはや桁が違います。

厳密には「指数的」ではなく「多項式的」な増え方ですが、感覚としては「n を 10 倍にすると仕事量が 100 倍、1000 倍になる」というくらい急に重くなる、と思っておくと安全です。アルゴリズムの世界ではこの増え方を O(n^2) と書いて表現します。

小さな n なら問題になりませんが、業務で大きなデータを扱うときは「二重ループで本当に間に合うか?」と一度立ち止まる癖をつけましょう。今回のピラミッドのように n が小さい問題なら全く問題ありません。

よくある間違い

初めて二重ループを書くと、ほぼ全員がどこかでつまずきます。代表的なものを 3 つ紹介します。

  • ij を混同する — 内側で書きたいのに i++ と書いてしまうと、外側のカウンタを勝手にいじることになり、無限ループや想定外の動作になります。「外側 i、内側 j」と最初に決めてから書きましょう
  • 内側ループ後の改行を忘れる — 1 行ぶんの * を並べたあとに \n を追加しないと、全部が 1 行につながって ********* のような出力になります。改行は内側ループの「外」、外側ループの「中」が定位置です
  • 末尾に余計な改行が付くpyramid(3) の期待値は "*\n**\n***" で、末尾の \n はありません。毎回改行を付けてしまうと最後にも 1 つ余分に付き、テストが fail します。if (i < n - 1) で最後の行だけ改行をスキップするか、改行を行の「先頭」に置いて 1 行目だけスキップするテクニックを使いましょう

もうひとつ、地味だけど多いのが「内側のループ変数を外側のループの後でも使ってしまう」というミスです。j は内側ループの中だけで生きている変数なので、内側ループを抜けたら基本的に存在しないものとして扱いましょう。

エラーが出たら、まず「外側と内側、それぞれ何回回るはず?」を紙に書き出してみてください。i = 0, 1, 2 のそれぞれで j がどう動くか手で追うだけで、ほとんどのバグは見つかります。

やってみよう

今回の課題は Solution.pyramid(int n) を完成させて、n 段のピラミッドを * で描くメソッドを作ることです。各行は \n で連結し、末尾には改行を付けません。

  1. 外側の for ループで i0 から n - 1 まで回す
  2. 内側の for ループで j0 から i まで回し、*StringBuilder に追加する
  3. 内側ループを抜けたら、最後の行でなければ \n を追加する
  4. 外側ループを抜けたら StringBuilderString に変換して return する

慣れてきたら、ピラミッドを左右反転させた 右寄せピラミッド や、上下に並んだ ひし形 (diamond) も自力で書いてみましょう。二重ループは「2 つのループの境目」を意識すれば、応用範囲がぐっと広がります。次のレッスンでさらに発展した使い方を見ていきます。

よくある質問

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 標準を固めるのが効率的です。

次のレッスン

次は break と continue で、ループの中にループを入れた二重ループの考え方を学び、ピラミッド型の文字列を組み立ててみよう を学びます。

事前確認 — 進む前に次の 3 つができることを確認しましょう。

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

理解度チェック (30 秒)

Q. 二重ループ とは何か、1 文で説明してください。

この章のポイント

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

関連レッスン

要件

  1. クラス名は Solution、メソッド名は pyramid、引数は int n 1 つ、戻り値の型は String にすること
  2. 外側ループで行、内側ループで * の個数を回す二重ループで組み立てること
  3. 各行は \n で連結し、末尾には余計な改行を付けないこと (pyramid(3) の期待値は "*\n**\n***")

入出力例

test-cases.txt

pyramid(3) → "* ** ***" pyramid(5) → "* ** *** **** *****" pyramid(1)"*"

ヒント

main.java
main.java
学習モード

メモ

二重ループ

⌘S で保存