循环中的最后一个元素是否值得单独处理?
-
03-07-2019 - |
题
在审核时,我有时会遇到这种循环:
i = begin
while ( i != end ) {
// ... do stuff
if ( i == end-1 (the one-but-last element) ) {
... do other stuff
}
increment i
}
然后我问这个问题:你会写这个吗?
i = begin
mid = ( end - begin ) / 2 // (the middle element)
while ( i != end ) {
// ... do stuff
if ( i > mid ) {
... do other stuff
}
increment i
}
在我看来,这超过了编写循环的意图:你循环,因为每个元素都有共同点。使用此构造,对于某些元素,您可以执行不同的操作。因此,我总结说,您需要为这些元素单独循环:
i = begin
mid = ( end - begin ) / 2 //(the middle element)
while ( i != mid ) {
// ... do stuff
increment i
}
while ( i != end ) {
// ... do stuff
// ... do other stuff
increment i
}
现在我甚至在SO上看到了关于如何使用的问题以一种很好的方式写出if
- 子句......我很难过:有些东西不在这里。
我错了吗?如果是这样的话,那么在编码时你需要预先知道特殊情况的环状体是多么的好呢?
解决方案
@xtofl,
我同意你的担忧。
百万次我遇到类似的问题。
开发人员为第一个或最后一个元素添加特殊处理。
在大多数情况下,值得从 startIdx + 1 或 endIdx - 1 元素循环,或者甚至将一个长循环拆分为多个较短的循环。
在极少数情况下,不可能分割循环。
在我看来, uncommon 应尽可能在循环之外处理事情。
其他提示
我不认为这个问题应该由一个原则来回答(例如<!>“;在一个循环中,平等对待每个元素<!>”;)。相反,您可以查看两个因素来评估实施是好还是坏:
- 运行时有效性 - 编译后的代码运行速度快,还是以不同的方式更快地运行?
- 代码可维护性 - 对于其他开发人员来说,了解这里发生的事情是否容易? 醇>
如果它更快并且通过在一个循环中执行所有操作来使代码更具可读性,那就这样做吧。如果它更慢,更不易读,那就换一种方式吧。
如果它更快,更不易读,或更慢但更易读,请找出在特定情况下更重要的因素,然后决定如何循环(或不循环)。
我知道当人们试图将数组元素连接成逗号分隔的字符串时,我已经看到了这一点:
for(i=0;i<elements.size;i++) {
if (i>0) {
string += ','
}
string += elements[i]
}
你或者在那里有if子句,或者你必须在最后再次复制字符串+ = line。
在这种情况下,显而易见的解决方案是
string = elements.join(',')
但是join方法在内部执行相同的循环。并不总是有办法做你想做的事。
我意识到,当我将特殊情况放入for循环中时,我通常会因为自己的利益而过于聪明。
在您发布的最后一个片段中,您正在重复//代码。
当你在一组不同的索引上有完全不同的操作集时,保持2个循环是有意义的。
i = begin
mid = ( end - begin ) / 2 //(the middle element)
while ( i != mid ) {
// ... do stuff
increment i
}
while ( i != end ) {
// ... do other stuff
increment i
}
情况并非如此,您仍然希望保留一个循环。但事实仍然是你仍然保存(结束 - 开始)/ 2次比较。因此,它归结为您是希望代码看起来整洁还是想要节省一些CPU周期。打电话是你的。
我认为你完全钉了它。大多数人都陷入了在循环中包含条件分支的陷阱,当他们可以在外面执行它们时:这只是更快。
例如:
if(items == null)
return null;
StringBuilder result = new StringBuilder();
if(items.Length != 0)
{
result.Append(items[0]); // Special case outside loop.
for(int i = 1; i < items.Length; i++) // Note: we start at element one.
{
result.Append(";");
result.Append(items[i]);
}
}
return result.ToString();
你所描述的中间案例只是简单的讨厌。想象一下,如果代码增长并需要重构为不同的方法。
除非您正在解析XML <!> lt; grin <!> gt;循环应尽可能简洁明了。
我认为你是正确的循环是为了平等地处理所有元素。不幸的是,有时会出现特殊情况,这些应该通过if语句在循环结构中处理。
如果有很多特殊情况,你应该考虑采用某种方法来处理不同结构中的两组不同元素。
我更喜欢简单地从循环中排除元素 并在循环外单独处理
例如:让我们考虑EOF的情况
i = begin
while ( i != end -1 ) {
// ... do stuff for element from begn to second last element
increment i
}
if(given_array(end -1) != ''){
// do stuff for the EOF element in the array
}
当然,可以拉出的循环中的特殊外壳是愚蠢的。我不会复制do_stuff;我要么把它放在函数或宏中,所以我不复制粘贴代码。
我讨厌的另一件事是 for-case pattern :
for (i=0; i<5; i++)
{
switch(i)
{
case 0:
// something
break;
case 1:
// something else
break;
// etc...
}
}
我在实际代码中看到了这一点。
哪一个表现更好?
如果项目数量非常大,那么我总是循环一次,特别是如果你要对每个项目执行一些操作。评估条件的成本可能低于循环两次。
糟糕,当然你没有循环两次......在这种情况下,最好使用两个循环。但是,我认为首要考虑因素应该是绩效。如果你可以通过简单的循环边界操作(一次)来分割工作,就不需要在循环中产生条件(N次)。
特殊情况应该在循环外完成,如果只执行一次。
但是,由于作用域,可能会有一个索引或一些其他变量更容易保留在循环内部。在循环控制结构中可能还存在将数据结构上的所有操作保持在一起的上下文原因,尽管我认为这本身就是一个弱论据。
它只是根据需要和方便使用它。因此没有提及平等对待元素,并且肯定没有伤害俱乐部提供的功能。