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

StackOverflow https://stackoverflow.com/questions/156650

Вопрос

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

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,

Я согласен с вашим беспокойством.

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

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

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

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

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

Другие советы

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

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

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

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

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

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

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

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

string = elements.join(',')

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

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

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

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

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

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

Например:

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

Я думаю, вы правы в том, что цикл предназначен для одинаковой обработки всех элементов.К сожалению, иногда возникают особые случаи, и их следует решать внутри конструкции цикла с помощью операторов 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 (i=0; i<5; i++)
{
  switch(i)
  {
    case 0:
      // something
      break;
    case 1:
      // something else
      break;
    // etc...
  }
}

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

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

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

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

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

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

Речь идет только об использовании его в соответствии с потребностями и удобством.Как таковое не существует никаких упоминаний об одинаковом подходе к элементам, и, конечно, нет никакого вреда в объединении функций, предоставляемых языком.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top