質問
別のループ内にネストされたforループがある場合、可能な限り迅速に両方のループ(内側と外側)から効率的に抜け出すにはどうすればよいですか?
ブール値を使用して別のメソッドに移動する必要はありませんが、外側のループの後にコードの最初の行を実行するだけです。
これをすばやく簡単に行う方法は何ですか?
ありがとう
例外は安くない/真に例外的な状況などでのみスローされるべきだと考えていました。したがって、このソリューションはパフォーマンスの観点からは良いとは思いません。
.NET(anonメソッド)の新しい機能を利用して、非常に基本的なことを行うのは正しいとは思わない。
そのため、tvon(申し訳ありませんが完全なユーザー名を綴ることはできません!)には良い解決策があります。
Marc:anonメソッドの素晴らしい使用法。これも素晴らしいですが、anonメソッドをサポートするバージョンの.NET / C#を使用しない仕事に就く可能性があるため、従来のアプローチも知っておく必要があります。 。
解決
まあ、 goto
ですが、それは見苦しく、常に可能とは限りません。ループをメソッド(または非メソッド)に配置し、 return
を使用して終了してメインコードに戻ることもできます。
// goto
for (int i = 0; i < 100; i++)
{
for (int j = 0; j < 100; j++)
{
goto Foo; // yeuck!
}
}
Foo:
Console.WriteLine("Hi");
vs:
// anon-method
Action work = delegate
{
for (int x = 0; x < 100; x++)
{
for (int y = 0; y < 100; y++)
{
return; // exits anon-method
}
}
};
work(); // execute anon-method
Console.WriteLine("Hi");
C#7では、「ローカル関数」を取得する必要があることに注意してください。これは(構文tbdなど)、次のように動作することを意味します。
// local function (declared **inside** another method)
void Work()
{
for (int x = 0; x < 100; x++)
{
for (int y = 0; y < 100; y++)
{
return; // exits local function
}
}
};
Work(); // execute local function
Console.WriteLine("Hi");
他のヒント
Cでよく使用されるアプローチのC#適応-外側のループの変数の値をループ条件の外側に設定します(つまり、int変数 INT_MAX -1
を使用するforループが良い選択です):
for (int i = 0; i < 100; i++)
{
for (int j = 0; j < 100; j++)
{
if (exit_condition)
{
// cause the outer loop to break:
// use i = INT_MAX - 1; otherwise i++ == INT_MIN < 100 and loop will continue
i = int.MaxValue - 1;
Console.WriteLine("Hi");
// break the inner loop
break;
}
}
// if you have code in outer loop it will execute after break from inner loop
}
コードの注記にあるように、 break
は外側のループの次の反復に魔法のようにジャンプしません。したがって、内側のループの外側にコードがある場合、このアプローチはより多くのチェックを必要とします。そのような場合は他のソリューションを検討してください。
このアプローチは、 for
および while
ループで機能しますが、 foreach
では機能しません。 foreach
の場合、非表示の列挙子へのコードアクセスがないため、変更できません(できたとしても、 IEnumerator
には&quot; MoveToEnd&quot;メソッド)。
インラインコメントの著者への謝辞:
i = INT_MAX-1
メタによる提案
ygoe による
for
/ foreach
コメント。
適切な IntMax
の jmbpiano
blizpasta による内部ループ後のコードに関するコメント
この解決策はC#には適用されません
他の言語でこの質問を見つけた人のために、 Javascript、Java、およびDでは、ラベル付きブレークと継続が許可されています:
outer: while(fn1())
{
while(fn2())
{
if(fn3()) continue outer;
if(fn4()) break outer;
}
}
外側のループに適切なガードを使用します。ブレークする前に、内側のループにガードを設定します。
bool exitedInner = false;
for (int i = 0; i < N && !exitedInner; ++i) {
.... some outer loop stuff
for (int j = 0; j < M; ++j) {
if (sometest) {
exitedInner = true;
break;
}
}
if (!exitedInner) {
... more outer loop stuff
}
}
さらに良いことに、内部ループをメソッドに抽象化し、falseが返されたら外部ループを終了します。
for (int i = 0; i < N; ++i) {
.... some outer loop stuff
if (!doInner(i, N, M)) {
break;
}
... more outer loop stuff
}
これについては引用しないでください。ただし、 goto はMSDNで提案されています。両方のループの各反復でチェックされるフラグを含めるなど、他のソリューションがあります。最後に、例外を本当に重い解決策として問題に使用できます。
GOTO:
for ( int i = 0; i < 10; ++i ) {
for ( int j = 0; j < 10; ++j ) {
// code
if ( break_condition ) goto End;
// more code
}
}
End: ;
条件:
bool exit = false;
for ( int i = 0; i < 10 && !exit; ++i ) {
for ( int j = 0; j < 10 && !exit; ++j ) {
// code
if ( break_condition ) {
exit = true;
break; // or continue
}
// more code
}
}
例外:
try {
for ( int i = 0; i < 10 && !exit; ++i ) {
for ( int j = 0; j < 10 && !exit; ++j ) {
// code
if ( break_condition ) {
throw new Exception()
}
// more code
}
}
catch ( Exception e ) {}
ネストされたforループをプライベートメソッドにリファクタリングすることは可能ですか?そうすれば、メソッドから単に「戻る」ことでループを終了できます。
goto
ステートメントを嫌う人が多いように思えるので、これを少し修正する必要があると感じました。
人々が goto
について持っている「感情」は、最終的にはコードの理解と(潜在的なパフォーマンスへの影響についての)(誤解)に要約されると思います。そのため、質問に答える前に、まずコンパイル方法の詳細について説明します。
ご存じのとおり、C#はILにコンパイルされ、SSAコンパイラーを使用してアセンブラーにコンパイルされます。これがどのように機能するかについて少し洞察し、質問自体に答えてみます。
C#からILへ
最初に、C#コードが必要です。簡単に始めましょう:
foreach (var item in array)
{
// ...
break;
// ...
}
この手順を段階的に実行して、内部で何が起こるかについての良いアイデアを提供します。
最初の翻訳: foreach
から同等の for
ループへ(注:ここでは配列を使用しています。IDisposableの詳細を取得したくないためです。 -その場合、IEnumerableも使用する必要があります):
for (int i=0; i<array.Length; ++i)
{
var item = array[i];
// ...
break;
// ...
}
2番目の翻訳: for
および break
は、より簡単な同等のものに翻訳されます:
int i=0;
while (i < array.Length)
{
var item = array[i];
// ...
break;
// ...
++i;
}
3番目の翻訳(これはILコードに相当): break
および while
をブランチに変更します:
int i=0; // for initialization
startLoop:
if (i >= array.Length) // for condition
{
goto exitLoop;
}
var item = array[i];
// ...
goto exitLoop; // break
// ...
++i; // for post-expression
goto startLoop;
コンパイラーはこれらのことを1つのステップで実行しますが、プロセスの洞察を提供します。 C#プログラムから発展したILコードは、最後のC#コードのリテラル翻訳です。ここで確認できます: https://dotnetfiddle.net/QaiLRz (「ILを表示」をクリック)
ここで確認したことの1つは、プロセス中にコードがより複雑になることです。これを観察する最も簡単な方法は、同じことを確認するためにより多くのコードが必要になるという事実です。また、 foreach
、 for
、 while
、および break
は実際には gotoの省略形であると主張するかもしれません。
、これは部分的に正しい。
ILからアセンブラーまで
.NET JITコンパイラはSSAコンパイラです。ここでは、SSAフォームの詳細と最適化コンパイラの作成方法については詳しく説明しませんが、多すぎますが、何が起こるかについての基本的な理解を与えることができます。理解を深めるために、コンパイラの最適化について読み始めることをお勧めします(簡単な紹介としてこの本が好きです: http://ssabook.gforge.inria.fr/latest/book.pdf )およびLLVM(llvm.org)。
すべての最適化コンパイラは、コードが簡単であり、予測可能なパターンに従うという事実に依存しています。 FORループの場合、グラフ理論を使用して分岐を分析し、分岐内のcycliなどを最適化します(逆方向の分岐など)。
ただし、ループを実装するための前方分岐があります。ご想像のとおり、これは実際には次のようにJITが修正する最初のステップの1つです。
int i=0; // for initialization
if (i >= array.Length) // for condition
{
goto endOfLoop;
}
startLoop:
var item = array[i];
// ...
goto endOfLoop; // break
// ...
++i; // for post-expression
if (i >= array.Length) // for condition
{
goto startLoop;
}
endOfLoop:
// ...
ご覧のとおり、後方ループがあり、これが小さなループです。ここでまだ厄介なのは、 break
ステートメントのために終わったブランチだけです。場合によっては、これを同じ方法で移動できますが、別の場合はそこにとどまります。
では、なぜコンパイラはこれを行うのですか?ループを展開できれば、ベクトル化できるかもしれません。定数が追加されていることを証明できる場合もあります。つまり、ループ全体が空中に消えることがあります。要約すると、パターンを予測可能にする(分岐を予測可能にする)ことにより、特定の条件がループ内で保持されることを証明できます。つまり、JIT最適化中に魔法をかけることができます。
ただし、ブランチはこれらの優れた予測可能なパターンを壊す傾向があり、オプティマイザは一種の嫌いなものです。休憩、続行、goto-これらはすべてこれらの予測可能なパターンを破るつもりであるため、実際には「いい」わけではありません。
この時点で、単純な foreach
の方が、あちこちにある一連の goto
ステートメントよりも予測しやすいことも理解しておく必要があります。 (1)読みやすさと(2)オプティマイザーの観点から見ると、どちらも優れたソリューションです。
言及する価値のあるもう1つのことは、コンパイラーを最適化して変数にレジスターを割り当てることと非常に関連があることです( register allocation と呼ばれるプロセス)。ご存知かもしれませんが、CPUには限られた数のレジスタしかなく、それらはハードウェアの中で最も高速なメモリです。最も内側のループにあるコードで使用される変数は、レジスタが割り当てられる可能性が高くなりますが、ループ外の変数はそれほど重要ではありません(おそらく、このコードはヒットが少ないためです)。
ヘルプ、複雑すぎます...どうすればいいですか?
一番下の行は、自由に使用できる言語構成体を常に使用する必要があることです。これにより、通常(暗黙的に)コンパイラーの予測可能なパターンが構築されます。可能であれば、奇妙な分岐を避けてください(具体的には、 break
、 continue
、 goto
、または return
を途中で何もありません)。
ここでの良いニュースは、これらの予測可能なパターンは、読みやすく(人間にとって)、見つけやすい(コンパイラにとって)の両方です。
これらのパターンの1つはSESEと呼ばれ、単一エントリ単一出口を表します。
そして、実際の質問に行き着きました。
次のようなものがあると想像してください:
// a is a variable.
for (int i=0; i<100; ++i)
{
for (int j=0; j<100; ++j)
{
// ...
if (i*j > a)
{
// break everything
}
}
}
これを予測可能なパターンにする最も簡単な方法は、単に if
を完全に削除することです:
int i, j;
for (i=0; i<100 && i*j <= a; ++i)
{
for (j=0; j<100 && i*j <= a; ++j)
{
// ...
}
}
他の場合には、メソッドを2つのメソッドに分割することもできます:
// Outer loop in method 1:
for (i=0; i<100 && processInner(i); ++i)
{
}
private bool processInner(int i)
{
int j;
for (j=0; j<100 && i*j <= a; ++j)
{
// ...
}
return i*j<=a;
}
一時変数?良い、悪い、またはい?
ループ内からブール値を返すこともできます(ただし、私は個人的にSESEフォームの方が好みです。コンパイラがそれを見る方法であり、読みやすいと思うからです)。
一部の人々は、一時変数を使用する方がクリーンだと考え、次のような解決策を提案します。
bool more = true;
for (int i=0; i<100; ++i)
{
for (int j=0; j<100; ++j)
{
// ...
if (i*j > a) { more = false; break; } // yuck.
// ...
}
if (!more) { break; } // yuck.
// ...
}
// ...
私は個人的にこのアプローチに反対しています。コードのコンパイル方法をもう一度見てください。次に、これらの素敵で予測可能なパターンでこれがどうなるかを考えてください。写真をゲット?
そうですね、つづりましょう。起こることは:
- コンパイラはすべてをブランチとして書き出します。
- 最適化ステップとして、コンパイラーは、制御フローでしか使用されない奇妙な
more
変数を削除しようとして、データフロー分析を行います。 - 成功した場合、変数
more
はプログラムから削除され、ブランチのみが残ります。これらのブランチは最適化されるため、内部ループからブランチを1つだけ取得します。 - 失敗した場合、変数
more
は最も内側のループで確実に使用されるため、コンパイラーがそれを最適化しないと、レジスターに割り当てられる可能性が高くなります(これは貴重なレジスタメモリを使い果たします)。
つまり、要約すると、コンパイラーのオプティマイザーは、 more
が制御フローにのみ使用され、ケースシナリオは、外側のforループの外側の単一のブランチに変換します。
言い換えれば、最良のシナリオは、これと同等の結果になるということです:
for (int i=0; i<100; ++i)
{
for (int j=0; j<100; ++j)
{
// ...
if (i*j > a) { goto exitLoop; } // perhaps add a comment
// ...
}
// ...
}
exitLoop:
// ...
これに関する私の個人的な意見は非常に単純です。これが私たちがずっと意図していたものであるなら、コンパイラと読みやすさの両方のために世界をより簡単にし、それをすぐに書きましょう。
tl; dr:
下の行:
- 可能であれば、forループで単純条件を使用します。あなたが持っている高レベルの言語構成に固執する
要素を関数/メソッドに組み込み、早期復帰を使用するか、ループをwhile節に再配置します。 goto / exceptions / whateverは確かにここでは適切ではありません。
def do_until_equal():
foreach a:
foreach b:
if a==b: return
クイック、ナイス、ブール値の使用なし、gotoの使用なし、C#の組み合わせを要求しました。あなたはあなたが望むことをするすべての可能な方法を除外しました。
最も迅速でleastい方法は、gotoを使用することです。
コードを独自の関数に抽象化して、早期復帰を使用するよりもいい場合があります-早期復帰は悪です:)
public void GetIndexOf(Transform transform, out int outX, out int outY)
{
outX = -1;
outY = -1;
for (int x = 0; x < Columns.Length; x++)
{
var column = Columns[x];
for (int y = 0; y < column.Transforms.Length; y++)
{
if(column.Transforms[y] == transform)
{
outX = x;
outY = y;
return;
}
}
}
}
状況によっては、内部ループの後でコードを実行しない場合にのみ、これを実行できる場合があります。
for (int i = 0; i < 100; i++)
{
for (int j = 0; j < 100; j++)
{
i = 100;
break;
}
}
これは特別なものではありませんが、問題によっては最も簡単な解決策かもしれません。
&quot; break&quot;を使用する例を見てきました。ただし、「続行」を使用するものはありません。
それでも内部ループに何らかのフラグが必要です:
while( some_condition )
{
// outer loop stuff
...
bool get_out = false;
for(...)
{
// inner loop stuff
...
get_out = true;
break;
}
if( get_out )
{
some_condition=false;
continue;
}
// more out loop stuff
...
}
Cで数十年前に break
を初めて見たので、この問題に悩まされました。私はいくつかの言語拡張が壊れる拡張機能を持ち、それがこうして機能することを望んでいた:
break; // our trusty friend, breaks out of current looping construct.
break 2; // breaks out of the current and it's parent looping construct.
break 3; // breaks out of 3 looping constructs.
break all; // totally decimates any looping constructs in force.
学生時代から、gotoなしでコードで何でもできることが数学的に証明可能であると言われたことを覚えています(つまり、gotoが唯一の答えである状況はありません)。したがって、gotoを使用することはありません(個人的な好みだけで、自分が正しいか間違っているかを示唆するものではありません)
とにかく、ネストされたループから抜け出すには、次のようにします:
var isDone = false;
for (var x in collectionX) {
for (var y in collectionY) {
for (var z in collectionZ) {
if (conditionMet) {
// some code
isDone = true;
}
if (isDone)
break;
}
if (isDone)
break;
}
if (isDone)
break;
}
...私のような人たちが反ゴトの「ファンボーイズ」であることを願っています。 :)
それが私がやった方法です。まだ回避策です。
foreach (var substring in substrings) {
//To be used to break from 1st loop.
int breaker=1;
foreach (char c in substring) {
if (char.IsLetter(c)) {
Console.WriteLine(line.IndexOf(c));
\\setting condition to break from 1st loop.
breaker=9;
break;
}
}
if (breaker==9) {
break;
}
}
ループ外に出るカスタム例外をスローします。
for
、 foreach
、 while
、またはあらゆる種類のループと、キャッチ例外を使用する言語
で機能しますブロック
try
{
foreach (object o in list)
{
foreach (object another in otherList)
{
// ... some stuff here
if (condition)
{
throw new CustomExcpetion();
}
}
}
}
catch (CustomException)
{
// log
}
bool breakInnerLoop=false
for(int i=0;i<=10;i++)
{
for(int J=0;i<=10;i++)
{
if(i<=j)
{
breakInnerLoop=true;
break;
}
}
if(breakInnerLoop)
{
continue
}
}
私はあなたが人があなたにgotoステートメントを参照するという答えを受け入れたので、現代のプログラミングと専門家の意見ではgotoはキラーです、私たちはそれをいくつかの特定の理由があるプログラミングのキラーと呼びますこの時点でそれはここにありますが、あなたの質問の解決策は非常に簡単です、あなたは私の例でそれを示すように、この種のシナリオでブールフラグを使用できます:
for (; j < 10; j++)
{
//solution
bool breakme = false;
for (int k = 1; k < 10; k++)
{
//place the condition where you want to stop it
if ()
{
breakme = true;
break;
}
}
if(breakme)
break;
}
シンプルかつプレーン。 :)
break
キーワードも見ましたか? O.o
これは単なる擬似コードですが、私が言っていることを見ることができるはずです:
<?php
for(...) {
while(...) {
foreach(...) {
break 3;
}
}
}
break
が break()
のような関数であると考える場合、そのパラメーターは、抜けるループの数になります。ここのコードの3番目のループにいるので、3つすべてから抜け出すことができます。
マニュアル: http://php.net/break
「ブールのこと」をやりたくないなら唯一の解決策は実際に投げることです。明らかにすべきではないことです..!