Нужен ли последний элемент в цикле отдельного лечения?

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

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
}

Теперь я даже увидел question на SO о том, как написать if -clause в хорошем смысле... И мне стало грустно: что-то здесь не так.

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

Ответ 1

@xtofl,

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

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

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

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

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

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

Ответ 2

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

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

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

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

Ответ 3

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

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

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

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

string = elements.join(',')

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

Ответ 4

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

Ответ 5

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

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

Ответ 6

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

Например:

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

Ответ 7

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

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

Ответ 8

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

Например: Рассмотрим случай 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
}

Ответ 9

Конечно, специальные вещи в петле, которые можно вытащить, глупы. Я бы не дублировал do_stuff, хотя; Я бы поставил его в функцию или макрос, чтобы не копировать-вставлять код.

Ответ 10

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

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

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

Ответ 11

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

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

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

Ответ 12

Специальный случай должен выполняться вне цикла, если его нужно выполнить только один раз.

Однако может существовать индекс или какая-либо другая переменная (ы), которые проще всего удерживать внутри цикла из-за области видимости. Также может быть контекстная причина для того, чтобы все операции над структурой данных были объединены внутри структуры управления контуром, хотя я считаю, что это слабый аргумент сам по себе.

Ответ 13

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