打破了嵌套的循环
如果我有一个嵌套在另一个for循环,我怎么能有效地从两个循环(内部和外部)以最快的方式出来?
我不想要使用布尔值,然后不得不说去另一种方法,而只是执行外循环后的第一行代码。
什么是快速和好的方式去做这个?
谢谢
我认为例外不是很便宜/应该只是在一个真正的例外条件等等。因此,我不认为这个解决scheme从性能angular度来看是好的。
我不觉得利用.NET(anon方法)中的新特性来做一些非常重要的事情是正确的。
因此,tvon(抱歉不能拼完整的用户名!)有一个很好的解决scheme。
马克:很好的使用匿名方法,这也很好,但是因为我可以在一个不使用支持匿名方法的.NET / C#版本的工作中工作,所以我需要知道一个传统的方法。
那么, goto
,但这是丑陋的,并不总是可能的。 您也可以将循环放入方法(或anon方法)中,并使用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#中工作,但在CI中经常这样做:
for (int i = 0; i < 100; i++) { for (int j = 0; j < 100; j++) { if (exit_condition) { // cause the outer loop to break: i = INT_MAX; Console.WriteLine("Hi"); // break the inner loop break; } } }
此解决scheme不适用于C#
对于通过其他语言发现此问题的用户, Javascript,Java和D允许标记中断并继续 :
outer: while(fn1()) { while(fn2()) { if(fn3()) continue outer; if(fn4()) break outer; } }
在外环中使用合适的防护装置。 在rest之前,把守卫放在内线。
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 }
不要在此引用我的意思,但可以按照MSDN中的build议使用goto 。 还有其他解决scheme,包括在两个循环的每次迭代中检查的标志。 最后,你可以使用一个exception作为一个真正的重量级解决你的问题。
去:
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循环到一个私人的方法? 这样,你可以简单地“退出”方法退出循环。
因素放入函数/方法中,并使用提前返回,或将您的循环重新排列为while子句。 goto / exceptions /这里肯定是不合适的。
def do_until_equal(): foreach a: foreach b: if a==b: return
你问了快速,很好,没有使用布尔,不使用goto和C#的组合。 你已经排除了所有可能的做法。
最快捷和最不丑陋的方法是使用转到。
在我看来,人们似乎不喜欢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; // ... }
第二个翻译: for
和break
被翻译成更容易的等价物:
int i=0; while (i < array.Length) { var item = array[i]; // ... break; // ... ++i; }
和第三种翻译(这是IL代码的等价物):我们改变一个分支,
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 (点击'查看IL')
现在,你在这里观察到的一件事是,在这个过程中,代码变得更加复杂。 观察这个最简单的方法是,我们需要越来越多的代码来完成同样的事情。 你也许会认为, foreach
, while
和break
是goto
短手,这是部分事实。
从IL到汇编
.NET JIT编译器是一个SSA编译器。 我不会在这里详细介绍SSA的forms,以及如何创build一个优化的编译器,这只是太多了,但是可以对将要发生的事情给出基本的了解。 为了更深入的理解,最好开始阅读优化编译器(我喜欢这本书的简要介绍: http ://ssabook.gforge.inria.fr/latest/book.pdf)和LLVM(llvm.org) 。
每个优化编译器都依赖于这样的事实,即代码很容易,并遵循可预测的模式 。 在FOR循环的情况下,我们使用图论来分析分支,然后优化我们分支中的循环(比如分支向后)。
但是,我们现在有前向分支来实现我们的循环。 正如你可能已经猜到的那样,这实际上是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
语句而结束的分支。 在某些情况下,我们可以用相同的方式来解决这个问题,但是在另一些情况下,这个问题就在这里。
那么编译器为什么这样做呢? 那么,如果我们可以展开循环,我们可以将其向量化。 我们甚至可以certificate,只是增加了常量,这意味着我们整个循环可能消失在空气中。 总结:通过使模式可预测(通过使分支可预测),我们可以certificate某些条件在我们的循环中存在,这意味着我们可以在JIT优化期间做魔术。
然而,分支机构倾向于打破那些可预测的模式,因此优化器就是这样 – 不喜欢。 打破,继续,转到 – 他们都打算打破这些可预测的模式,因此并不真正“好”。
在这一点上你也应该认识到,一个简单的foreach
是可以预测的,然后一堆goto
语句遍布整个地方。 从(1)可读性和(2)从优化器的angular度来看,这是更好的解决scheme。
另外值得一提的是,它对优化编译器将寄存器分配给variables(一个称为寄存器分配的过程)非常重要。 您可能知道,CPU中只有有限数量的寄存器,它们是您硬件中最快的内存块。 在最内层循环的代码中使用的variables更有可能得到一个寄存器赋值,而循环之外的variables则不那么重要(因为这个代码可能会less一些)。
帮助,太复杂…我该怎么办?
底线是您应该始终使用您掌握的语言结构,这通常会(为您的编译器构build可预测的模式)。 尽量避免奇怪的分支(特别是: break
, continue
, goto
或在没有任何return
)。
这里的好消息是,这些可预测的模式既容易阅读(对于人类),也容易察觉(编译器)。
其中一种模式称为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) { // ... } }
在其他情况下,您也可以将该方法分成两种方法:
// 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; }
临时variables? 好,坏或丑陋?
你甚至可以决定从循环中返回一个布尔值(但是我个人更喜欢SESE格式,因为编译器会看到它,我认为它更清晰)。
有人认为使用临时variables更清洁,并提出如下解决scheme:
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. // ... } // ...
我个人反对这种做法。 再看看代码是如何编译的。 现在考虑一下这些好的,可预测的模式将会做什么。 获取图片?
好吧,让我把它拼出来。 将会发生什么事情是:
- 编译器会将所有内容写出来作为分支。
- 作为优化步骤,编译器将执行数据stream分析,尝试去除仅在控制stream中使用的奇怪的
more
variables。 - 如果成功,variables将从程序中消除,只剩下分支。 这些分支将被优化,所以你将只从内部循环中得到一个分支。
- 如果没有成功,那么最内层循环肯定会使用variables,所以如果编译器不会优化它,那么分配给寄存器(这会浪费宝贵的寄存器内存)的机会很大。
因此,总结一下:编译器中的优化器会陷入一个非常麻烦的地步,要弄清楚more
只用于控制stream,而在最好的情况下会将它转换为外部的一个单独的分支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循环中使用一个简单的条件。 尽可能地坚持你所掌握的高级语言结构。
- 如果一切都失败了,而你离开了
goto
或者bool more
,那么更喜欢前者。
有时候把代码抽象成它自己的函数是很好的,而不是使用早期的返回 – 虽然早期的返回是邪恶的:)
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; } }
这不是elegent,但它可能是最简单的解决scheme取决于您的问题。
我见过很多使用“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的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.
我记得从我的学生时代开始,有人说在math上可以certificate你可以在代码中做任何事情,而不用转身(即没有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; }
…我希望对那些喜欢我的人来说,这是帮助“反风扇”:)
抛出exception循环的自定义exception。
它适用于, foreach
或while
或任何types的循环和任何使用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是一个杀手,我们称之为编程杀手有一定的原因,我不会在这里讨论在这一点上,但你的问题的解决scheme是非常简单的,你可以在这种情况下使用布尔标志,就像我将在我的示例中演示它:
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; }
简单明了。 🙂
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 } }
你甚至看看break
关键字吗? 吴
这只是伪代码,但你应该能够看到我的意思:
<?php for(...) { while(...) { foreach(...) { break 3; } } }
如果你认为break
是一个像break()
这样的函数,那么它的参数就是要打出的循环的数量。 正如我们在这里的代码中的第三个循环,我们可以打破这三个。
手册: http : //php.net/break
我认为,除非你想做“布尔事情”,否则唯一的解决办法就是抛出。 你显然不应该这样做!