如果我有一个嵌套在另一个循环中的for循环,我如何以最快的方式有效地走出两个循环(内部和外部)?
我不想使用布尔值,而不必说转到另一种方法,而只是在外循环之后执行第一行代码。
有什么快速而又好的方法?
我以为异常的代价不菲,应该只在真正异常的情况下抛出异常,所以从性能的角度来看,这种解决方案不是很好。
我认为利用.NET的新功能(匿名方法)来做一些非常基础的事情是不对的。
如果我有一个嵌套在另一个循环中的for循环,我如何以最快的方式有效地走出两个循环(内部和外部)?
我不想使用布尔值,而不必说转到另一种方法,而只是在外循环之后执行第一行代码。
有什么快速而又好的方法?
我以为异常的代价不菲,应该只在真正异常的情况下抛出异常,所以从性能的角度来看,这种解决方案不是很好。
我认为利用.NET的新功能(匿名方法)来做一些非常基础的事情是不对的。
Answers:
好吧,,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");
goto
其本质是有害的,而它只是做某些事情的正确工具,就像许多工具可能被滥用。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
不会神奇地跳到外循环的下一个迭代-因此,如果您的代码不在内循环中,则此方法需要进行更多检查。在这种情况下,请考虑其他解决方案。
此方法适用于for
和while
循环,但不适用于foreach
。如果foreach
您没有对隐藏的枚举器的代码访问权限,则无法更改它(即使您IEnumerator
没有某些“ MoveToEnd”方法也是如此)。
对内联评论作者的致谢:
Meta的i = INT_MAX - 1
建议/ ygoe的评论。
正确的jmbpiano通过内部循环后约代码备注blizpasta
for
foreach
IntMax
此解决方案不适用于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
}
不要在此引用我,但是您可以按照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循环重构为私有方法?这样,您可以简单地“返回”该方法以退出循环。
[&] { ... return; ... }();
在我看来,人们似乎非常不喜欢这种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;
// ...
}
第二种翻译:for
和break
被翻译成更容易的等价形式:
int i=0;
while (i < array.Length)
{
var item = array[i];
// ...
break;
// ...
++i;
}
和第三翻译(这是等效的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;
尽管编译器只需一步即可完成这些操作,但它可以让您深入了解该过程。从C#程序演变而来的IL代码是最后C#代码的字面翻译。您可以在这里自己看到:https : //dotnetfiddle.net/QaiLRz(单击“查看IL”)
现在,您在这里观察到的一件事是,在此过程中,代码变得更加复杂。观察这一点的最简单方法是,我们需要越来越多的代码来说明同一件事。您可能还认为foreach
,for
,while
和break
实际上短期手中goto
,这部分是真实的。
从IL到汇编器
.NET JIT编译器是SSA编译器。我不会在这里讨论SSA表单的所有详细信息以及如何创建优化的编译器,这虽然太多了,但可以对将会发生的事情提供基本的了解。为了获得更深入的了解,最好开始阅读优化编译器(我确实喜欢这本书作为简短介绍:http : //ssabook.gforge.inria.fr/latest/book.pdf //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优化过程中做魔术。
但是,分支趋向于破坏那些很好的可预测模式,这是优化程序所不喜欢的。打破,继续,前进-他们都打算打破这些可预测的模式-因此并不是真正的“好”。
在这一点上,您还应该认识到,比起遍布各处foreach
的一堆goto
语句,更容易预测。从(1)可读性和(2)从优化器的角度来看,这都是更好的解决方案。
值得一提的另一件事是,优化编译器将寄存器分配给变量(称为寄存器分配的过程)非常重要。您可能知道,CPU中只有有限数量的寄存器,它们是迄今为止硬件中最快的内存。最内层循环中的代码中使用的变量更有可能分配一个寄存器,而循环外的变量则不太重要(因为此代码的命中率可能会降低)。
帮助,太复杂了...我该怎么办?
最重要的是,您应该始终使用您可以使用的语言构造,这通常(隐式)将为编译器构建可预测的模式。尽量避免陌生的分支(如果可能具体是: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)
{
// ...
}
}
在其他情况下,您也可以将方法分为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 (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:
底线:
goto
或bool more
,则最好选择前者。yield return
。也就是说,虽然很容易显示出一些有用的情况,但是对于大多数“应用程序级”代码,C#挫败感发生的可能性很小,除了那些“仅来自” C / C ++之外;-)我偶尔也会错过Ruby的“抛出”(非异常展开)有时也适用于此领域。
goto
,不会使您的代码更具可读性-我认为发生的事情是完全相反的:实际上,您的控制流将包含更多节点-这几乎是复杂性的定义。只是避免使用它来帮助自律听起来在可读性方面适得其反。
goto
就是没有纪律不滥用它。
将其分解为函数/方法并使用及早返回,或将循环重新安排为有条件的子句。goto / exceptions /无论什么都不适合这里。
def do_until_equal():
foreach a:
foreach b:
if a==b: return
您要求快速,不错,不使用布尔值,不使用goto和C#的组合。您已经排除了所有可能的方式来做自己想要的事情。
最快捷最丑陋的方法是使用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
几十年前第一次在C语言中看到以来,这个问题困扰着我。我希望某些语言增强功能可以打破常规,从而可以解决问题:
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;
}
}
结束双循环的最简单方法是直接结束第一个循环
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;
使用此代码,我们获得以下输出:
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
这只是伪代码,但是您应该能够理解我的意思:
<?php
for(...) {
while(...) {
foreach(...) {
break 3;
}
}
}
如果你想break
成为一个像break()
,那么它的参数就是要中断的循环数。由于我们处于此处代码的第三个循环中,因此我们可以突破所有这三个条件。
手册:http : //php.net/break