B級科学者もどきの憂鬱

とある理系になりきれない奴のつれづれなる活動記

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

多重ループを抜けるための6つの方法

結局、6月中には更新できなかったどころか、
また一ヶ月ほど空いてしまいました。

最近、空いた時間というのは、
いつの間にかあるものじゃなくて、自分で作るものである、
ということを半ば強制的に実感させられています。
7月過ぎたら多少は時間が作れると信じてる!

今回は久々にプログラミングの話です。
まぁ、備忘録的なものですが。

二重三重にも積み重なったループを、
一気に抜けたい時ってありますよね。
ですが、私が知っている限り、プログラミング言語で、
多重ループを抜けるための言語仕様があるものはありません。

これを実現するためには様々な方法がありますが、
代表的と思われるものを6つまとめてみました。
言語は全てC++ですが、他の言語でも応用できるでしょう。

以下のソースコードは、xMax, yMax, zMaxというものが
どこかで宣言されていると思って下さい。
もしかしたらglobal変数かも知れませんし、
#defineで定義された定数かも知れません。


1.gotoを使う

int x, y, z;
for(x=0; x<xMax; x++) {
    for(y=0; y<yMax; y++) {
        for(z=0; z<zMax; z++) {
            if(終了条件) goto for_exit;
            何かしらの処理
        }
    }
}
for_exit:

一番シンプルな方法です。
ループがいくら重なっても同じやり方で出来ますし、
処理コストも非常に少ないです。
たまにgotoを使うのを極端に嫌う人がいますが、
何事も適材適所だと思います。


2.脱出用フラグを使う

int x, y, z;
bool exitFlag=false;
for(x=0; x<xMax; x++) {
    for(y=0; y<yMax; y++) {
        for(z=0; z<zMax; z++) {
            if(終了条件) {
                exitFlag=true;
                break;
            }
            何かしらの処理
        }
        if(exitFlag) break;
    }
    if(exitFlag) break;
}

割とメジャーな方法だと思います。
重ねたループの数だけフラグの判定をする必要があるので、
見た目は多少煩雑になり、処理コストも少し掛かります。

ですが、一番内側のループ以外にも処理がある場合、
例えば上記の場合、zのループ終了後にyループ内で別の処理がある場合、
フラグ判定文の位置を変えるだけで対応できます。
gotoで同じ事をやろうと思うと、スパゲッティになります。


3.ループ終了条件を無理矢理満たすようにする

int x, y, z;
for(x=0; x<xMax; x++) {
    for(y=0; y<yMax; y++) {
        for(z=0; z<zMax; z++) {
            if(終了条件) {
                x=xMax-1;
                y=yMax-1;
                z=zMax-1;
            }
            何かしらの処理
        }
    }
}

脱出用フラグを使う代わりに、
ループ変数自体を変更してしまうものです。
これなら脱出用フラグを使う必要はありません。

ですが、脱出のためにいくつも代入文を書く羽目になるので、
途中で間違える確率は上がります。
ループ上限回数を変更したい場合も、
複数箇所変更しなければならない可能性があります。

ただ、内側のループ2つだけ抜けるのと、3つ全部抜けるのを、
条件によって切り替えたい、などの場合は、
こちらの方法が使えます。

脱出用フラグで同じ事をしようとすると、
抜け方によってそれぞれフラグを用意しなければならず、
ミスも増える可能性が高いです。

それと、if文内部で、ループ変数を上限値から1引いた値にしていますが、
引かなくても大抵の場合は動作します。

しかし、例えばxMaxがINT_MAXだった場合、
x=xMax;など、1を引かない形にしてしまうと、
次のxのループが始まるとき、再設定式x++が行われるので、
桁あふれを起こして、xが負の値になります。

そうなると、継続条件式がtrueになるため、
意図通りにループを脱出できません。


4.ループ部分だけ関数にする

void Loop(void) {
    int x, y, z;
    for(x=0; x<xMax; x++) {
        for(y=0; y<yMax; y++) {
            for(z=0; z<zMax; z++) {
                if(終了条件) return;
                何かしらの処理
            }
        }
    }
}

ループを丸ごと関数にして、
returnで一気に抜ける、という方法です。
gotoを使った場合と同じような特徴があるにも関わらず、
gotoを使わないで済む上に、構造化も図れます。

ただ、このようなループがプログラム内に多数存在する場合、
関数の名前のストックが無くなってくるという、
ものすごく現実的な問題があります。
まぁ、私が直面しているだけかも知れませんが。

適当に名付けるとプログラムの流れが見えにくくなるので、
出来るだけ、何をやる関数なのか分かる名前で作りましょう。


5.例外を利用する

try {
    int x, y, z;
    for(x=0; x<xMax; x++) {
        for(y=0; y<yMax; y++) {
            for(z=0; z<zMax; z++) {
                if(終了条件) throw(-1);
                何かしらの処理
            }
        }
    }
} catch (int) {
    ループ終了時の処理
}

例外を投げた直後に、catchブロックまで
制御が飛んでいくという性質を利用したものです。
これも1.や4.とほぼ同じ特徴を持ちます。

例外をループ脱出に使うのに抵抗のある人もいるかもしれませんが、
プログラムというのは、誰にでも見やすく書けるのなら
基本的に何でもありです。

投げる例外は別に何でもいいですが、
どこかでExitLoop型等を宣言しておいて、
それを投げることにすると、例外の目的がはっきりします。


6.無理矢理ループを一重にする

int x, y, z, xyz, xyzMax;
xyzMax=xMax*yMax*zMax;
for(xyz=0; xyz<xyzMax; xyz++) {
    x=xyz/yMax/zMax;
    y=xyz/zMax%yMax;
    z=xyz%zMax;
    if(終了条件) break;
    何かしらの処理
}

無理矢理一つのループにして、そのループ変数の値から、
本来のループ変数の値を復元する、という力技な方法です。
今までの例の中で一番柔軟性が無いと言えるかも知れません。

四重以上のループとなると、
ループ変数の復元が複雑になるのでまず行われませんが、
二重ループ程度ではそんなに手間ではありません。

こんな強引なことを行うのは、二次元配列として扱うべきものを
プログラム上は一次元配列で格納している、等の特殊な場合が多いです。
行列計算ライブラリなどでたまに見かける実装です。

この方法には、たとえxMax, yMax, zMaxがINT_MAX以下でも、
xyzMaxがINT_MAXを超える可能性があるという問題もあります。
なので、あまりループ回数の多いものは作れません。


以上、6つの方法を紹介しました。
これらの派生形も色々と考えられますので、
形にとらわれず、目的に応じて工夫して使って下さい。
スポンサーサイト

FC2Ad

まとめ

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。