【发布时间】:2017-04-08 15:33:40
【问题描述】:
(现在我主要使用 C#。欢迎其他语言的想法,但如果可以的话,请将它们翻译成 C#,并且要明确。)
我一次又一次遇到的是一个嵌套循环,它在一些二维数组中搜索以找到一个元素(通常是某个对象),然后必须对其进行操作。所以当然,一旦你找到了那个对象,你应该跳出两个循环,这样你就不会不必要地继续搜索你已经找到的东西(尤其是在可以遍历指数级巨大数组的嵌套循环中)。
以下代码是目前我的首选方式:
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;
}
}
}
不再需要那个讨厌的额外布尔值!
尽管如此,寻找替代或更好的解决方案的工作仍在继续。多年来,我尝试了许多其他方法,但由于某种原因发现它们并不是那么好:
- 将
j(外循环迭代器)设置为高于height的值,这将触发它自动中断。不理想,因为有时您想记住您在其中找到它的i和j值。 - 在二维数组上使用
foreach。不理想,因为foreach不会让您操作集合(删除或添加到它,这通常是我搜索对象的原因)。 - 只需将 2 个循环放在一个除了查找并返回
O之外什么都不做的函数中。return有效地打破了两个循环。很多时候这没关系,但并非总是如此。我这样做是为了非常通用的搜索,但是还有很多“组搜索”我想将遍历集中起来。在这些情况下,我会找到 2 个或更多对象(有时在同一个 2D 数组中),记住它们,然后才跳出两个循环。 - 使用
goto? (哇,这可能是 goto 的唯一合法用法吗?它比KeepLooping标志更易读,尤其是当我们有 3 个或更多循环时。)不理想,因为同事会尖叫血腥谋杀。而在 C# 中,goto之后会有正确的垃圾清理吗? - 抛出自定义异常? idk,我从未尝试过,但它的可读性不如我目前首选的方式。
- 找到正确的对象后,在内部循环中执行所有对象操作,然后
return;这很快就会变得一团糟。有时对象操作涉及到自己的循环。
还有一个非常聪明的第七条路,感谢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!
}
这有效地将二维数组压缩为一个 for 循环以进行迭代。然而,一些批评指出,与 i++ 或 j++ 相比,mod 和除法运算符相当慢,因此它可能会更慢(请记住,我们正在处理未知大小的 2D 数组)。就像我评论的那样,应该有一种方法可以在一个操作中获得除法和余数,因为我很确定 x86 汇编代码具有 DIV 操作码,它们将商和余数存储在单独的寄存器中,所有这些都在一个 DIV 指令中。但是如何在 C# 中执行/使用它,idk。
如果 C# 允许你命名循环就好了,比如L1 和L2,然后做类似L1.break() 的事情;无论您在哪个循环中。唉......它不能用这种语言完成。 (会不会有一些使用宏的秘密方法?)C# 6.0 会实现这个功能吗?
编辑:在我看来,我根据它们的优雅和速度来判断解决方案。请记住,我们正在处理嵌套循环,它可能会成倍增长。额外的操作或比较可能会有所作为。
好的,好吧,告诉我你喜欢的方式,特别是如果它没有在这里列出。
【问题讨论】:
-
goto使用 C# 是有原因的,所以如果您认为它是 goto 的合法用例并且有性能数据来证明这一点,您的同事不应该抱怨。 -
goto不会改变垃圾收集器的工作方式。 -
@zzzzBov 好的,但很难告诉同事他们应该抱怨什么,不应该抱怨什么,除非你是老板(我不是):/
-
当你有证据时,很容易告诉你的同事他们不能抱怨。那么你所需要的只是证明他们有更好的方法的证据。如果他们这样做,请改用它。