有没有" smart"打破嵌套循环的方法?

时间:2015-05-18 00:44:37

标签: c# loops

(现在我主要使用C#。欢迎使用其他语言的想法,但如果可以的话,请将它们翻译成C#,并且要明确。)

我一次又一次遇到的是嵌套循环,搜索一些2D数组以找到一个元素(通常是某个对象),然后必须对其进行操作。所以当然,一旦你找到那个对象,就应该打破这两个循环,这样你就不必不必要地继续搜索你已经找到的东西(特别是在可以遍历指数巨大数组的嵌套循环中)。

以下代码目前是我首选的方法:

Obj O = null;
bool KeepLooping = true;

for (int j = 0; j < height && KeepLooping; j++)
{
    for (int i = 0; i < width; i++)
    {
        if (ObjArray[i, j] != null && ObjArray[i, j].property == search_value)
        {
            O = ObjArray[i, j]; // you found it, now remember it
            KeepLooping = false; // clear the flag so the outer loop will break too
            break;
        }
    }
}

感谢Erik Funkenbusch,如果我们这样做会变得更加优雅:

Obj O = null;
for (int j = 0; j < height && O == null; j++) // much, much better idea to check O for null in the outer loop
{
    for (int i = 0; i < width; i++)
    {
        if (ObjArray[i, j] != null && ObjArray[i, j].property == search_value)
        {
            O = ObjArray[i, j]; // you found it, now remember it
            break;
        }
    }
}

不再需要那个讨厌的额外布尔值!

然而,寻求替代或更好的解决方案仍在继续。多年来,我尝试了许多其他方法,但发现它们并不是因为某种原因而出现的那样:

  1. j(外循环迭代器)设置为高于height的值,这将触发它自动中断。不理想,因为有时您想要记住您找到的ij值。
  2. 在2D阵列上使用foreach。不理想,因为foreach 不允许您操作集合(删除或添加它,这通常是我搜索对象的原因)。
  3. 只需将2个循环放在一个除了查找并返回O之外什么也不做的函数中。 return有效地打破了两个循环。很多时候这没关系,但并非总是如此。我这样做是为了进行非常通用的搜索,但是还有很多&#34;组搜索&#34; 我希望集中搜索遍历。在这些情况下,我会找到2个或更多个对象(有时在同一个2D数组中),记住它们,然后才会突破这两个循环。
  4. 使用goto? (哇,这可能是goto的唯一合法使用吗?它比KeepLooping标志更具可读性,特别是如果我们有3个或更多循环。)不理想,因为同事会尖叫血腥的谋杀。在C#中,goto
  5. 之后是否会进行正确的垃圾清理
  6. 抛出自定义异常? idk,我从未尝试过,但它看起来不像我目前喜欢的方式。
  7. 找到正确的对象后,在内循环内完成所有对象操作,然后return; 这可能会变得混乱。有时,对象操作涉及自己的循环。
  8. 还有一个非常聪明的第7种方式,感谢User_PWY:

    int size = width*height; // save this so you dont have to keep remultiplying it every iteration
    for (int i = 0; i < size; i++)
    {
       int x = i % width; // ingenious method here
       int y = i / width; // ingenious method here
    
       O = ObjArray[x, y];
    
       if (O != null)
           break; // woohoo!
    }
    

    这有效地将2D数组压缩为一个for循环以进行迭代。然而,一些批评指出,与仅使用i ++或j ++相比,mod和除法运算符相当慢,因此它可能会更慢(请记住我们正在处理谁知道什么大小的2D数组)。就像我评论的那样,应该有一种方法可以在一次操作中得到除法和余数,因为我非常确定x86汇编代码具有DIV操作码,它将商和余数存储在单独的寄存器中,所有这些都在一个DIV指令中。但是如何在C#中使用它,idk。

    如果C#允许您命名循环(例如L1L2),然后执行L1.break()之类的操作,那将是很好的。无论你在哪个循环里面。唉......用这种语言做不到。 (使用宏可能会有一些秘密的方法吗?)是否会有一个C#6.0实现此功能?

    编辑:在我看来,我判断他们的优雅和速度的解决方案。请记住,我们正在处理嵌套循环,这可能会成倍增长。额外的操作或比较可能会产生影响。

    好吧,好吧,告诉我你喜欢的方式,特别是如果它没有列在这里。

7 个答案:

答案 0 :(得分:2)

for (int i = 0; i < width*height; i++)
{
   int x=i%width
      ,y=i/width;
   //dostuff
}

我喜欢这种访问二维数组的方式。

评论1)
有许多评论担心mod(%)运算符可能代价高昂,但这是我们正在讨论的整数运算,我认为它应该与其他解决方案没有区别。

评论2)
关于宏。我找不到代码但设法简单地生成了一个代码。

#define FOR2DARRAY(WIDTH,HEIGHT) 
    \for (int i = 0, x = 0,y = 0; i < (WIDTH)*(HEIGHT); i++, x=i%(WIDTH),y=i/(HEIGHT))

答案 1 :(得分:1)

重构以避免深度嵌套,可能使用LINQ的切割器可能是更好的解决方案。

对于这种情况,

Goto是可能的(如果你不能提出更好的方法,这基本上只能使用它)。设置带有可读名称的标志可能会减少问题/尖叫。

不要

  • 使用流量控制的例外
  • 更改循环变量。在这种情况下,弄清楚发生了什么是非常困难的。我敢打赌,大多数人会接受goto更好的方法。

答案 2 :(得分:1)

goto是一个非常好的解决方案,微软甚至recommends it

  

goto语句将程序控件直接传送到带标签的   言。

     

goto的一个常见用途是将控制转移到特定的开关盒   标签或switch语句中的默认标签。

     

goto语句对于摆脱深层嵌套循环也很有用。

至于你关于对象破坏的问题,一旦你超出范围对象的范围,垃圾收集器应该将其标记为销毁,但是如果你使用的话,我无法确定行为是否相同,例如,using循环for指令和goto,而不是正常退出范围。

答案 3 :(得分:1)

试试这个:

Obj O = ObjArray.Cast<Obj>().FirstOrDefault(obj => obj != null && obj.property == search_value);

这会将Obj的2D数组转换为IEnumerable<Obj>,并为您提供符合条件的第一个数组。如果它们都不匹配,则Obj O设置为null

请在此处查看:https://dotnetfiddle.net/dQTJbU

答案 4 :(得分:1)

也许我在你的选项列表中错过了它,但是为什么你不能将搜索重构为一个返回你的对象的方法呢?

private object FindObject(....)
{
    for (int j = 0; j < height && KeepLooping; j++)
    {
        for (int i = 0; i < width; i++)
        {
            if (ObjArray[i, j] != null && ObjArray[i, j].property = search_value)
            {
                return ObjArray[i, j]; // you found it, now remember it
            }
        }
    }
    return null;
} 

答案 5 :(得分:1)

有很多方法可以做到这一点。在你的例子中,我会这样做:

Obj O = null;

for (int j = 0; j < height && O == null; j++)
{
    for (int i = 0; i < width; i++)
    {
        if (ObjArray[i, j] != null && ObjArray[i, j].property == search_value)
        {
            O = ObjArray[i, j]; // you found it, now remember it
            break;
        }
    }
}

在这种情况下,KeepLo​​oping变量是不必要的,因此简单的break语句非常优雅。

你甚至可以更简化这一点:

Obj O = null;

for (int j = 0; j < height && O == null; j++)
{
    for (int i = 0; i < width && O == null; i++)
    {
        if (ObjArray[i, j] != null && ObjArray[i, j].property == search_value)
            O = ObjArray[i, j]; // you found it, now remember it
    }
}

现在,甚至不需要休息。

当然,这可能不适用于所有情况,但通常可以将结果类型用作状态值。

仅供参考,如果您正确地执行此操作,在foreach中修改集合的问题并不是真正的问题。例如:

// Assumes ObjArray is [,] and not [][]
foreach (int item in ObjArray.ToList()) // makes a copy to iterate over
{
    if (item != null && item.property == search_value) {
        ObjArray[0,0] = item; // I can now modify this for no reason
        break;
    }
}

答案 6 :(得分:0)

没有太大区别,但我更喜欢以这种方式使用布尔标志:

Obj O = null;
bool found = false;

for (int j = 0; j < height; j++)
{
    for (int i = 0; i < width; i++)
    {
        if (ObjArray[i, j] != null && ObjArray[i, j].property = search_value)
        {
            O = ObjArray[i, j]; // you found it, now remember it
            found = true; // clear the flag so the outer loop will break too
            break;
        }
    }
    if (found) break;
}

if (!found)
{
    // The loop finished with no values found
}
else
{
    // Do stuff here with the value found
}