1. はじめに
まず最初の疑問:なんでわざわざループの中にもう一個ループを入れる必要があるの?って話。実は、データやタスクって一列だけじゃなくて、テーブルとかグリッド、もっと多次元な構造になってることが多いんだ。例えば、九九表を画面に出したいとか、2次元配列を全部回りたいとか、全ての要素ペアの組み合わせを数えたいとか。こういう時はループ一個じゃ足りなくて、ループの中にループが必要になるんだよね。
プログラミングでネストされたループってのは、目覚まし時計が2個あるみたいな感じ。外側のが鳴り始めたら、その中でまたもう一個が鳴り出す。つまり、外側の「1回」の間に、内側は全部回りきる(そして外側が進むたびにまた内側が全部回る)。
いい例が「時」と「分」。時は0から23までの外側ループ、分は0から59までの内側ループ。外側が1つ進むごとに、内側は全部の値を回るってわけ。
2. ネストされたループの書き方
C#だと、ネストされたループの書き方は普通のループと全く同じ。単純にループの中にもう一個ループを書くだけ。forとwhileの例を見てみよう:
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 4; j++)
{
Console.Write($"{i},{j} ");
}
Console.WriteLine();
}
ここでは外側ループがi(0から2まで)、内側ループがj(0から3まで)をコントロールしてる。iの値ごとに、内側ループはj=0からj=3まで全部回る。このコードを実行すると、座標の表がきれいに出てくるよ:
0,0 0,1 0,2 0,3
1,0 1,1 1,2 1,3
2,0 2,1 2,2 2,3
whileを使った同じ例も見てみよう:
int i = 0;
while (i < 3)
{
int j = 0;
while (j < 4)
{
Console.Write($"{i},{j} ");
j++;
}
Console.WriteLine();
i++;
}
ポイント:外側ループの1回ごとに、内側ループの変数(j)は毎回初期化しないと、1行しか出力されなくなるから注意!
3. ネストされたループの使い方例
例1:チェスボード(8x8)の出力
最初の課題は、黒と白のマスでできたクラシックなチェスボードを画面に出すこと(#が黒、_が白としよう)。ネストされたforループでやってみる:
for (int row = 0; row < 8; row++)
{
for (int col = 0; col < 8; col++)
{
if ((row + col) % 2 == 0)
Console.Write("_");
else
Console.Write("#");
}
Console.WriteLine();
}
結果:
_#_#_#_#
#_#_#_#_
_#_#_#_#
#_#_#_#_
_#_#_#_#
#_#_#_#_
_#_#_#_#
#_#_#_#_
大事なポイント:ネストしてるからこそ、各行(row)ごとに全ての列(col)を回れる。ネストがなかったら、1行か1列しか出せないよ。
例2:九九表
ネストされたループの定番!1~9の九九表を出してみよう:
for (int i = 1; i <= 9; i++)
{
for (int j = 1; j <= 9; j++)
{
Console.Write($"{i * j,3} ");
}
Console.WriteLine();
}
${i * j,3}のフォーマットで、表がきれいに揃うようにスペースを入れてるよ。
結果:
1 2 3 4 5 6 7 8 9
2 4 6 8 10 12 14 16 18
3 6 9 12 15 18 21 24 27
4 8 12 16 20 24 28 32 36
...
9 18 27 36 45 54 63 72 81
4. ネストされたループのコントロールと注意点
breakとcontinueのネストループでの影響
ここで初心者がよくハマる!breakやcontinueを内側ループで使うと、そのループだけに効く。外側ループは普通に続くからね。
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5; j++)
{
if (j == 3)
break; // 内側ループだけ抜ける!
Console.Write($"{i},{j} ");
}
Console.WriteLine();
}
結果:
0,0 0,1 0,2
1,0 1,1 1,2
2,0 2,1 2,2
もし2重ループを一気に抜けたい場合(例えば最初に見つかった一致で即終了したい時とか)は、フラグを使ったり、return(全部関数内でやってる場合)みたいなテクニックを使うのが普通。
5. ネストされたループの可視化
ネストされたループの実行順序って、頭の中でイメージしにくいこともあるよね。こんなフローチャートを見てみよう:
表で考えると、iが1から3、jが1から4の時、全部で何回ループが回る?
| i | j(各iごとに回る値) | 内側ループの回数 |
|---|---|---|
| 1 | 1, 2, 3, 4 | 4 |
| 2 | 1, 2, 3, 4 | 4 |
| 3 | 1, 2, 3, 4 | 4 |
| 合計: 3 × 4 = 12 |
6. ネストされたループでのミスや落とし穴
よくあるミスは、内側ループの変数の初期化を間違えること。例えば、外側ループの外で宣言して、毎回リセットしないと、内側ループが全然回らなかったり、変な動きになる。
int j = 0;
for (int i = 0; i < 3; i++)
{
while (j < 4) // あれ?jは最初のループで4になってるかも。
{
Console.Write($"{i},{j} ");
j++;
}
Console.WriteLine();
}
この場合、ループは1回しか動かない。内側ループの変数は外側ループの中で初期化するのを忘れずに!
それから、うっかり同じ変数名で2重ループを書いちゃうと、コンパイラに怒られるよ。「その変数はもう定義されてる」ってね。
for (int i = 0; i < 3; i++)
{
for (int i = 0; i < 3; i++)
{
Console.Write($"{i},{i} ");
}
Console.WriteLine();
}
GO TO FULL VERSION