打破嵌套循环

如果我有一个嵌套在另一个循环中的for循环,我如何才能以最快的方式有效地从两个循环(内部和外部)中出来?

我不想必须使用布尔值然后必须说转到另一个方法,而只是在外部循环之后执行第一行代码。

有什么又快又好的方法呢?

我在想异常并不便宜/只应该在真正异常的情况下抛出等等。因此,从性能的角度来看,我不认为这个解决方案是好的。

我不认为利用。net中的新特性(anon方法)来做一些非常基础的事情是正确的。

227695 次浏览

我认为除非你想做“布尔的事情”,否则唯一的解决方案实际上是抛出。你显然不应该这么做!

好吧,goto,但这是丑陋的,并不总是可能的。也可以将循环放入方法(或anon-method)中,并使用return退出回主代码。

    // goto
for (int i = 0; i < 100; i++)
{
for (int j = 0; j < 100; j++)
{
goto Foo; // yeuck!
}
}
Foo:
Console.WriteLine("Hi");

与:

// 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");

是否有可能将嵌套的for循环重构为私有方法?这样你就可以简单地从方法中“返回”以退出循环。

不要引用我的话,但你可以在MSDN中使用转到。还有其他解决方案,比如在两个循环的每次迭代中都检查一个标志。最后,您可以使用异常作为问题的重量级解决方案。

转到:

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 ) {}

在外环使用合适的防护。在你破坏之前,在内环设置保护。

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
}

考虑到函数/方法并使用早期返回,或将循环重新安排为while子句。Goto /exceptions/无论什么在这里肯定不合适。

def do_until_equal():
foreach a:
foreach b:
if a==b: return

您要求的是快速、漂亮、不使用布尔、不使用goto和c#的组合。你已经排除了所有可能的方法去做你想做的事。

最快捷和最不丑陋的方法是使用goto。

c#自适应的方法经常在C中使用——设置外循环的变量值的循环外条件(即对于循环使用int变量INT_MAX -1通常是很好的选择):

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不会神奇地跳转到外循环的下一个迭代-所以如果你的代码在内循环之外,这种方法需要更多的检查。在这种情况下考虑其他解决方案。

这种方法适用于forwhile循环,但不适用于foreach。在foreach的情况下,你不会有代码访问隐藏的枚举器,所以你不能改变它(即使你可以IEnumerator没有一些“MoveToEnd”方法)。

对内联评论的作者的致谢:
i = INT_MAX - 1的建议由
for/foreach注释由ygoe
正确的IntMaxjmbpiano
内循环后的代码注释blizpasta < / sub > < / p >

自从几十年前我第一次在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.

我见过很多用break的例子,但没有一个用continue的。

它仍然需要在内部循环中使用某种标志:

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#

对于通过其他语言找到这个问题的人,Javascript、Java和D语言允许标记中断和继续:

outer: while(fn1())
{
while(fn2())
{
if(fn3()) continue outer;
if(fn4()) break outer;
}
}
         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是唯一答案的情况)。所以,我从不使用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;
}

... 我希望这对那些像我一样反对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;
}
}
}
}

你有没有看break关键字?有限责任

这只是伪代码,但你应该能明白我的意思:

<?php
for(...) {
while(...) {
foreach(...) {
break 3;
}
}
}

如果你认为break是一个类似break()的函数,那么它的参数将是要跳出的循环数。由于我们在代码的第三个循环中,我们可以跳出这三个循环。

手动:http://php.net/break

抛出一个自定义异常,该异常进入外循环。

它适用于forforeachwhile或任何类型的循环和任何使用try catch exception块的语言

try
{
foreach (object o in list)
{
foreach (object another in otherList)
{
// ... some stuff here
if (condition)
{
throw new CustomExcpetion();
}
}
}
}
catch (CustomException)
{
// log
}

正如我看到你接受了这个人提到你的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;
}

简单明了。:)

在我看来,人们很不喜欢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;
// ...
}

第二种翻译:forbreak被翻译成一个更简单的等价:

int i=0;
while (i < array.Length)
{
var item = array[i];
// ...
break;
// ...
++i;
}

第三个转换(这相当于IL代码):我们将breakwhile更改为一个分支:

    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;

虽然编译器在一个步骤中完成这些事情,但它让您深入了解这个过程。从c#程序发展而来的IL代码是最后一个c#代码的直译。你可以自己在这里看到:https://dotnetfiddle.net/QaiLRz(点击'view IL')

现在,您在这里观察到的一件事是,在这个过程中,代码变得更加复杂。观察这一点最简单的方法是,我们需要越来越多的代码来完成同样的事情。你也可能会说foreachforwhilebreak实际上是goto的简称,这在一定程度上是正确的。

从IL到汇编程序

. net JIT编译器是一个SSA编译器。我不会在这里详细介绍SSA表单以及如何创建一个优化编译器,这太多了,但可以对将要发生的事情有一个基本的了解。为了更深入地了解,最好开始阅读优化编译器(我非常喜欢这本书的简要介绍:http://ssabook.gforge.inria.fr/latest/book.pdf)和LLVM (llvm.org)。

每个优化编译器都依赖于代码为容易并遵循可预测的模式这一事实。在FOR循环的情况下,我们使用图论来分析分支,然后优化分支中的cycli(例如反向分支)。

但是,我们现在有了前向分支来实现我们的循环。正如你可能已经猜到的,这实际上是JIT将要修复的第一步,就像这样:

    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优化期间发挥神奇的作用。

然而,分支往往会破坏这些良好的可预测模式,这是优化器不喜欢的。Break, continue, goto——他们都想打破这些可预测的模式,因此并不是真的“好”。

在这一点上,你也应该意识到一个简单的foreach比一堆goto语句更可预测。从(1)可读性和(2)优化器的角度来看,这都是更好的解决方案。

另一件值得一提的事情是,它与优化编译器将寄存器分配给变量(一个称为寄存器分配的过程)非常相关。正如你可能知道的,在你的CPU中只有有限数量的寄存器,它们是目前硬件中最快的内存。在最内部循环的代码中使用的变量更有可能得到分配的寄存器,而循环之外的变量则不那么重要(因为这段代码可能命中较少)。

帮助,太复杂了……我该怎么办?

底线是您应该始终使用您所拥有的语言结构,这通常会(隐式地)为您的编译器构建可预测的模式。如果可能的话,尽量避免奇怪的分支(特别是:breakcontinuegotoreturn在无内容的中间)。

这里的好消息是,这些可预测的模式既易于阅读(对于人类),也易于发现(对于编译器)。

其中一种模式被称为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将从程序中删除,只保留分支。这些分支将被优化,因此您只能从内部循环中获得一个分支。
  • 如果不成功,变量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;博士:

底线:

  • 如果可能的话,在for循环中使用一个简单的条件。尽可能地使用您所掌握的高级语言结构。
  • 如果一切都失败了,只剩下gotobool more,选择前者。

我就是这么做的。还是个变通办法。

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;
}
}

结束双循环最简单的方法是直接结束第一个循环

string TestStr = "The frog jumped over the hill";
char[] KillChar = {'w', 'l'};


for(int i = 0; i < TestStr.Length; i++)
{
for(int E = 0; E < KillChar.Length; E++)
{
if(KillChar[E] == TestStr[i])
{
i = TestStr.Length; //Ends First Loop
break; //Ends Second Loop
}
}
}

可以使用循环中的自定义条件来打破循环,从而允许有干净的代码。

    static void Main(string[] args)
{
bool isBreak = false;
for (int i = 0; ConditionLoop(isBreak, i, 500); i++)
{
Console.WriteLine($"External loop iteration {i}");
for (int j = 0; ConditionLoop(isBreak, j, 500); j++)
{
Console.WriteLine($"Inner loop iteration {j}");


// This code is only to produce the break.
if (j > 3)
{
isBreak = true;
}
}


Console.WriteLine("The code after the inner loop will be executed when breaks");
}


Console.ReadKey();
}


private static bool ConditionLoop(bool isBreak, int i, int maxIterations) => i < maxIterations && !isBreak;

在这段代码中,我们包含以下输出:

  • 外部循环迭代0
  • 内循环迭代0
  • 内循环迭代1
  • 内循环迭代2
  • 内循环迭代3
  • 内循环迭代4
  • 内部循环之后的代码将在中断时执行

最干净的最短的,和大多数可重用方法是自调用匿名函数:

  • 没有转到
  • 没有标签
  • 没有临时变量
  • 没有命名函数

一行比上面的答案短匿名方法。

new Action(() =>
{
for (int x = 0; x < 100; x++)
{
for (int y = 0; y < 100; y++)
{
return; // exits self invoked lambda expression
}
}
})();
Console.WriteLine("Hi");

这里没有提到的另一种方法是将双循环合并为产品上的单个循环,它既干净又不依赖于更新的。net特性。然后在循环内部,计数器的值可以使用简单的数学计算:

int n; //set to max of first loop
int m; //set to max of second loop


for (int k = 0; k < n * m; k++)
{
//calculate the values of i and j as if there was a double loop
int i = k / m;
int j = k % m;
    

if(exitCondition)
{
break;
}
}

人们经常忘记for循环的第二个语句本身就是中断条件,所以在代码中没有必要有额外的if。

这样做是可行的:

bool run = true;
int finalx = 0;
int finaly = 0;
for (int x = 0; x < 100 && run; x++)
{
finalx = x;
for (int y = 0; y < 100 && run; y++)
{
finaly = y;
if (x == 10 && y == 50) { run = false; }
}
}
Console.WriteLine("x: " + finalx + " y: " + finaly);  // outputs 'x: 10 y: 50'

只要在内层循环中使用return,两个循环就会被退出…

我就设个旗子。

var breakOuterLoop = false;
for (int i = 0; i < 30; i++)
{
for (int j = 0; j < 30; j++)
{
if (condition)
{
breakOuterLoop = true;
break;
}
}
if (breakOuterLoop){
break;
}
}