Последний элемент в цикле заслуживает отдельного обращения? - PullRequest
18 голосов
/ 01 октября 2008

При просмотре я иногда сталкиваюсь с таким циклом:

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 -классу ... И мне стало грустно: что-то здесь не так.

Я не прав? Если так, что хорошего в загромождении тела цикла особыми случаями, о которых вы знаете заранее, во время кодирования?

Ответы [ 13 ]

19 голосов
/ 01 октября 2008

Я не думаю, что на этот вопрос следует отвечать принципом (например, «в цикле, относись ко всем элементам одинаково»). Вместо этого вы можете посмотреть на два фактора, чтобы оценить, является ли реализация хорошей или плохой:

  1. Эффективность во время выполнения - работает ли скомпилированный код быстрее или будет быстрее, если делать это иначе?
  2. Сопровождаемость кода - Легко ли (другому разработчику) понять, что здесь происходит?

Если это быстрее и код более читабелен, выполняя все за один цикл, сделайте это так. Если он медленнее и менее читабелен, сделайте это по-другому.

Если это быстрее и менее читабельно, или медленнее, но более читабельно, выясните, какой из факторов имеет большее значение в вашем конкретном случае, а затем решите, как выполнить цикл (или не цикл).

10 голосов
/ 01 октября 2008

Я знаю, что видел это, когда люди пытались объединить элементы массива в строку, разделенную запятой

for(i=0;i<elements.size;i++) {
   if (i>0) {
     string += ','
   }
   string += elements[i]
}

У вас либо есть предложение if, либо вам нужно дублировать строку + = строка в конце.

Очевидное решение в этом случае -

string = elements.join(',')

Но метод join внутренне выполняет тот же цикл. И не всегда есть способ сделать то, что вы хотите.

5 голосов
/ 01 октября 2008

В последнем опубликованном вами фрагменте кода вы повторяете код для // .... делать вещи.

Имеет смысл сохранять 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 числа сравнений. Таким образом, все сводится к тому, хотите ли вы, чтобы ваш код выглядел аккуратно или вы хотите сохранить некоторые циклы ЦП. Звонок твой.

5 голосов
/ 01 октября 2008

@ xtofl

Я согласен с вашей заботой.

Миллион раз я сталкивался с подобной проблемой.

Любой разработчик добавляет специальную обработку для первого или последнего элемента.

В большинстве случаев стоит просто зациклить элемент startIdx + 1 или endIdx - 1 или даже разбить один длинный цикл на несколько более коротких циклов.

В очень редких случаях невозможно разделить цикл.

По моему мнению необычные вещи должны обрабатываться вне цикла, когда это возможно.

5 голосов
/ 01 октября 2008

Я пришел к выводу, что когда я помещаю особые случаи в цикл for, я обычно слишком умен для своего блага.

3 голосов
/ 01 октября 2008

Я думаю, что у вас это полностью прибито. Большинство людей попадают в ловушку включения условных ветвей в циклы, когда они могут сделать их снаружи: это просто быстрее .

Например:

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, циклы должны быть максимально простыми и краткими.

2 голосов
/ 06 апреля 2013

Я предпочитаю просто исключить элемент из цикла и дать отдельное лечение вне цикла

Например: давайте рассмотрим случай 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
}
2 голосов
/ 01 октября 2008

Я думаю, что вы правы в том, что цикл предназначен для одинакового взаимодействия со всеми элементами. К сожалению, иногда есть особые случаи, и они должны решаться внутри конструкции цикла с помощью операторов if.

Если существует множество особых случаев, хотя вам, вероятно, следует подумать о том, чтобы придумать какой-то способ работы с двумя различными наборами элементов в отдельных конструкциях.

1 голос
/ 01 октября 2008

Еще одну вещь, которую я не хочу видеть, это шаблон для случая :

for (i=0; i<5; i++)
{
  switch(i)
  {
    case 0:
      // something
      break;
    case 1:
      // something else
      break;
    // etc...
  }
}

Я видел это в реальном коде.

1 голос
/ 01 октября 2008

Какой из них работает лучше?

Если количество элементов очень велико, то я всегда выполняю цикл один раз, особенно если вы собираетесь выполнить некоторую операцию над каждым элементом Стоимость оценки условного выражения, вероятно, будет меньше, чем зацикливание в два раза.

Упс, конечно, вы не зацикливаетесь дважды ... В этом случае предпочтительнее два цикла. Тем не менее, я утверждаю, что основное внимание должно уделяться производительности. Нет необходимости вводить условие в цикл (N раз), если вы можете разделить работу простым манипулированием границами цикла (один раз).

...