多重ループ

多重ループとは

for文while文などのループ文は、そのループ文の中にさらにループ文を作ることができます。
これを多重ループと言います。


static void Main(string[] args)
{
    for (int i = 1; i < 10; i++)
    {
        Console.Write("{0}の位", i);
        for (int j = 1; j < 10; j++)
        {
            Console.Write("{0,3}", i * j);
        }
        Console.WriteLine(string.Empty);
    }
    Console.ReadLine();
}
1の位  1  2  3  4  5  6  7  8  9
2の位  2  4  6  8 10 12 14 16 18
3の位  3  6  9 12 15 18 21 24 27
4の位  4  8 12 16 20 24 28 32 36
5の位  5 10 15 20 25 30 35 40 45
6の位  6 12 18 24 30 36 42 48 54
7の位  7 14 21 28 35 42 49 56 63
8の位  8 16 24 32 40 48 56 64 72
9の位  9 18 27 36 45 54 63 72 81

これは多重ループを利用して九九の計算結果を出力するコードです。

外側のfor文のループ1回目では、内側のfor文のループが9回行われます。
外側のfor文のループ2回目では、再度内側のfor文のループが9回行われます。
これを9回繰り返し、9×9の計81回のループが行われることになります。

Console.WriteLineメソッドの第一引数についてはConsole入出力の複合書式string.Formatメソッドを参照してください。

多重ループとbreak文、continue文

多重ループでもbreak文continue文は有効ですが、これは「現在のループ」に対してのみ有効です。
例えばbreak文を多重ループの内側のループ内で使用した場合、内側のループを抜けるだけで外側のループは抜けません。

以下は100までの素数を出力するサンプルコードです。


static void Main(string[] args)
{
    //チェックする最大数
    int max = 100;

    //素数の数
    int count = 0;

    Console.WriteLine("{0}までの素数", max);

    for (int x = 2; x <= max; x++)
    {
        bool flag = false;
        for (int y = 2; y < x; y++)
        {
            if (x % y == 0)
            {
                flag = true;
                break;
            }
        }
        if (!flag)
        {
            Console.Write("{0} ", x);
            count++;
        }
    }
    Console.WriteLine("\n{0}個", count);

    Console.ReadLine();
}
100までの素数
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 
25個

素数は「1とその数以外では割り切れない正の自然数(0は含まない)」です。
ある数nが素数か否かを調べるには、2からn-1までのすべての数との剰余(割り算の余り)を求めて、剰余が0になる数が含まれる場合は素数ではない、と言うことになります。

この条件で、外側のfor文は「素数か否かをチェックする数x」、内側のfor文は「xとの余剰を求める数y」をそれぞれセットし、多重ループを利用して素数を求めています。

「x % y」で0が得られればxは素数ではないということなので、それ以降のyの値はチェックする必要はありません。
そのためbreak文でループを抜けるのですが、その前にbool型変数flagにtrueをセットしておきます。
for文にはループをbreak文で抜けたか否かを判断する方法がないので、ループの外側で宣言した変数を利用します。

break文で内側のループを抜けた後、変数flagの状態をチェックします。
変数flagがfalseならばbreak文は実行されなかったということですから、変数xは素数ということになります。
これを指定の数(サンプルでは100)繰り返し、外側のループも終了します。

上記の素数チェック処理は素数の定義をそのままコードで表したものですが、無駄が多いので少し効率化してみます。

ルール1
「2」以降の素数はすべて奇数であることが確実なので、チェックする数は「3~n-1までのすべての奇数」で良いことになります。
なので最初の「2」だけは計算では求めずにそのまま出力します。
これで変数jには常に奇数をセットできるようになり(for文の反復式で2を加算すれば良い)、チェック回数(ループ回数)が半分以下になります。

ルール2
次に、チェックする数値は「その数の半分まで」で足りるはずです。
例えば「79」が素数か否かは「3~39」までの数で割り切れるかを調べれば良いということです。
チェックする数の半分より大きい数で割り切れることはないので、「40」以降の数の剰余のチェックを省くことでさらに効率アップできます。


static void Main(string[] args)
{
    int max = 100;
    int count = 1;

    Console.WriteLine("{0}までの素数", max);

    //「2」は単独で出力
    Console.Write("2 ");

    for (int x = 3; x <= max; x += 2)
    {
        bool flag = false;
        for (int y = 3; y < x / 2; y += 2)
        {
            if(x % y == 0)
            {
                flag = true;
                break;
            }
        }
        if(!flag)
        {
            Console.Write("{0} ", x);
            count++;
        }
    }
    Console.WriteLine("\n{0}個", count);

    Console.ReadLine();
}
100までの素数
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 
25個

さらに効率化することも可能です。
興味のある人は自分なりに書き換えてみてください。

多重ループを一気に抜ける

C#には多重ループを一気に抜けるような機能を持った文はありません。
多重ループを終わらせるには以下のような方法があります。

チェック用の変数を用意する

これは上のサンプルコードのようにループの終了チェック用の変数を用意し、その値に応じて内側ループの終了直後に外側ループでもbreak文を実行する方法です。


static void Main(string[] args)
{
    while(true)
    {
        bool quit = false;
        Console.WriteLine("外側ループ開始");
        while(true)
        {
            Console.WriteLine("内側ループ開始");
            Console.WriteLine("内側ループを抜けるには[b]キーを押して下さい。");
            Console.WriteLine("プログラムを終了するには[q]キーを押して下さい。");
            string str = Console.ReadLine();
            if(str == "b")
            {
                break;
            }
            else if(str == "q")
            {

                quit = true;
                break;
            }
            else
            {
                Console.WriteLine("無効な文字が入力されました。");
            }
        }

        if(quit)
        {
            break;
        }
    }
    Console.WriteLine("プログラムを終了します。");
    Console.ReadLine();
}
外側ループ開始
内側ループ開始
内側ループを抜けるには[b]キーを押して下さい。
プログラムを終了するには[q]キーを押して下さい。
a
無効な文字が入力されました。
内側ループ開始
内側ループを抜けるには[b]キーを押して下さい。
プログラムを終了するには[q]キーを押して下さい。
b
外側ループ開始
内側ループ開始
内側ループを抜けるには[b]キーを押して下さい。
プログラムを終了するには[q]キーを押して下さい。
q
プログラムを終了します。

この方法はループ終了判定用の変数が必要な点と、外側ループでは毎回変数のチェック処理が入る点がややデメリットです。
しかし最も単純で処理もわかりやすいので使用する機会も多いでしょう。

関数化する

関数内ではreturn文が使用できます。
return文はどのような処理であれ、実行すれば直ちにその関数を抜けることができますから、多重ループも一瞬で終了することができます。


static void Main(string[] args)
{
    Func();
    Console.WriteLine("プログラムを終了します。");
    Console.ReadLine();
}

static void Func()
{
    while (true)
    {
        Console.WriteLine("外側ループ開始");
        while (true)
        {
            Console.WriteLine("内側ループ開始");
            Console.WriteLine("内側ループを抜けるには[b]キーを押して下さい。");
            Console.WriteLine("プログラムを終了するには[q]キーを押して下さい。");
            string str = Console.ReadLine();
            if (str == "b")
            {
                break;
            }
            else if (str == "q")
            {
                return;
            }
            else
            {
                Console.WriteLine("無効な文字が入力されました。");
            }
        }
    }
}

実行結果は同じなので省略します。

この方法はわざわざ関数化しなければならないという点が煩わしく、「多重ループを抜ける」という目的だけで用いることは少ないでしょう。
別の理由で関数化するついでに処理を簡略化できるかもしれない、という程度です。

goto文

goto文は、あらかじめ指定した行へ処理をジャンプする文です。


static void Main(string[] args)
{
    while (true)
    {
        Console.WriteLine("外側ループ開始");
        while (true)
        {
            Console.WriteLine("内側ループ開始");
            Console.WriteLine("内側ループを抜けるには[b]キーを押して下さい。");
            Console.WriteLine("プログラムを終了するには[q]キーを押して下さい。");
            string str = Console.ReadLine();
            if (str == "b")
            {
                break;
            }
            else if (str == "q")
            {
                goto LOOPEND;
            }
            else
            {
                Console.WriteLine("無効な文字が入力されました。");
            }
        }
    }
    LOOPEND:
    Console.WriteLine("プログラムを終了します。");
    Console.ReadLine();
}

18行目でgoto文を実行しています。
goto文はラベルというものを使用して行き先を指定します。

26行目の「LOOPEND:」がラベルの指定で、「任意の名前:」という形式で指定します。

つまり、18行目のgoto文が実行されると次に実行される行は26行目ということになります。

goto文は多用すると処理があちこちに飛ぶので、処理の流れが分かりにくくなります。
そのため使用を避ける人が多い文です。

goto文は今回のように多重ループを抜ける場合など、限定的な使用にとどめたほうが良いでしょう。

ループの終了条件判定用の変数をいじくる

ループの終了条件に変数を使用している場合は、その変数の値を書き換えることで多重ループを抜けることができます。


static void Main(string[] args)
{
    for(int i = 1; i < 10; i++)
    {
        for (int j = 1; j < 10; j++)
        {
            Console.WriteLine("プログラムを終了しますか?\ny/n");
            string str = Console.ReadLine();
            if (str == "y")
            {
                i = 10;
                break;
            }
            Console.WriteLine("{0}", i * j);
        }
    }
    Console.WriteLine("プログラムを終了します。");
    Console.ReadLine();
}
プログラムを終了しますか?
y/n
n
1
プログラムを終了しますか?
y/n
y
プログラムを終了します。

内側のループ内で、外側のループ内の終了条件に使用している変数の値を、条件を満たさない値に書き換えてしまいます。
そして即座にbreak文を実行すれば、内側のループを受けてすぐに外側のループも抜けることができます。

ただしこの方法は、内側のループ終了後に外側ループ内に何も処理がない場合にのみ有効です。
外側ループの終了条件判定までに何か処理がある場合は実行されてしまいます。