Вырыв из вложенной петли

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

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

Что такое быстрый и приятный способ обойти это?

Спасибо


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

Я не считаю правильным использовать новые возможности .NET(anon methods), чтобы сделать что-то довольно фундаментальное.

Из-за этого, tvon (извините, не могу указать полное имя пользователя!) имеет приятное решение.

Marc: Хорошее использование методов anon, и это тоже здорово, но потому, что я мог быть в задании, где мы не используем версию .NET/С#, которая поддерживает методы anon, мне также нужно знать традиционный подход.

Ответ 1

Ну, goto, но это уродливо и не всегда возможно. Вы также можете поместить петли в метод (или anon-method) и использовать return для возврата к основному коду.

    // goto
    for (int i = 0; i < 100; i++)
    {
        for (int j = 0; j < 100; j++)
        {
            goto Foo; // yeuck!
        }
    }
Foo:
    Console.WriteLine("Hi");

против

// anon-method
Action work = delegate
{
    for (int x = 0; x < 100; x++)
    {
        for (int y = 0; y < 100; y++)
        {
            return; // exits anon-method
        }
    }
};
work(); // execute anon-method
Console.WriteLine("Hi");

Обратите внимание, что в С# 7 мы должны получить "локальные функции", которые (синтаксис tbd и т.д.) означает, что он должен работать примерно так:

// local function (declared **inside** another method)
void Work()
{
    for (int x = 0; x < 100; x++)
    {
        for (int y = 0; y < 100; y++)
        {
            return; // exits local function
        }
    }
};
Work(); // execute local function
Console.WriteLine("Hi");

Ответ 2

Не знаю, работает ли он на С#, но на C я часто делаю это:

    for (int i = 0; i < 100; i++)
    {
        for (int j = 0; j < 100; j++)
        {
            if (exit_condition)
            {
                // cause the outer loop to break:
                i = INT_MAX;
                Console.WriteLine("Hi");
                // break the inner loop
                break;
            }
        }
    }

Ответ 3

Это решение не относится к С#

Для людей, которые нашли этот вопрос на других языках, Javascript, Java и D позволяют помечать разрывы и продолжать:

outer: while(fn1())
{
   while(fn2())
   {
     if(fn3()) continue outer;
     if(fn4()) break outer;
   }
}

Ответ 4

Используйте подходящую защиту во внешнем контуре. Установите предохранитель во внутреннем контуре перед тем, как вы сломаетесь.

bool exitedInner = false;

for (int i = 0; i < N && !exitedInner; ++i) {

    .... some outer loop stuff

    for (int j = 0; j < M; ++j) {

        if (sometest) {
            exitedInner = true;
            break;
        }
    }
    if (!exitedInner) {
       ... more outer loop stuff
    }
}

Или еще лучше, абстрагируйте внутренний цикл в метод и выйдите из внешнего цикла, когда он вернет false.

for (int i = 0; i < N; ++i) {

    .... some outer loop stuff

    if (!doInner(i, N, M)) {
       break;
    }

    ... more outer loop stuff
}

Ответ 5

Не цитируйте меня на этом, но вы можете использовать goto, как предложено в MSDN. Существуют и другие решения, в том числе флаг, который проверяется на каждой итерации обоих циклов. Наконец, вы можете использовать исключение в качестве действительно тяжелого решения вашей проблемы.

GOTO:

for ( int i = 0; i < 10; ++i ) {
   for ( int j = 0; j < 10; ++j ) {
      // code
      if ( break_condition ) goto End;
      // more code
   }
}
End: ;

Состояние:

bool exit = false;
for ( int i = 0; i < 10 && !exit; ++i ) {
   for ( int j = 0; j < 10 && !exit; ++j ) {
      // code
      if ( break_condition ) {
         exit = true;
         break; // or continue
      }
      // more code
   }
}

Исключение:

try {
    for ( int i = 0; i < 10 && !exit; ++i ) {
       for ( int j = 0; j < 10 && !exit; ++j ) {
          // code
          if ( break_condition ) {
             throw new Exception()
          }
          // more code
       }
    }
catch ( Exception e ) {}

Ответ 6

Можно ли реорганизовать вложенный цикл в частный метод? Таким образом, вы можете просто "вернуться" из метода, чтобы выйти из цикла.

Ответ 7

в функцию/метод и использовать раннее возвращение или переставить ваши циклы в позицию while. goto/exceptions/независимо от того, что здесь не подходит.

def do_until_equal():
  foreach a:
    foreach b:
      if a==b: return

Ответ 8

Вы попросили комбинацию быстрого, приятного, без использования логического, без использования goto и С#. Вы исключили все возможные способы делать то, что хотите.

Самый быстрый и наименее уродливый способ - использовать goto.

Ответ 9

Мне кажется, что люди не любят выражение goto, поэтому я почувствовал необходимость немного выпрямить это.

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

Как мы все знаем, С# скомпилирован в IL, который затем скомпилируется для ассемблера с использованием компилятора SSA. Я расскажу немного о том, как все это работает, а затем попытайтесь ответить на сам вопрос.

От С# до IL

Сначала нам нужен кусок кода С#. Пусть начало простого:

foreach (var item in array)
{
    // ... 
    break;
    // ...
}

Я сделаю это шаг за шагом, чтобы дать вам хорошее представление о том, что происходит под капотом.

Первый перевод: от foreach до эквивалентного цикла for (Примечание: я использую массив здесь, потому что я не хочу вдаваться в подробности IDisposable - в этом случае у меня также было бы для использования IEnumerable):

for (int i=0; i<array.Length; ++i)
{
    var item = array[i];
    // ...
    break;
    // ...
}

Второй перевод: for и break переведен в более простой эквивалент:

int i=0;
while (i < array.Length)
{
    var item = array[i];
    // ...
    break;
    // ...
    ++i;
}

И третий перевод (это эквивалент кода IL): мы меняем break и while на ветку:

    int i=0; // for initialization

startLoop:
    if (i >= array.Length) // for condition
    {
        goto exitLoop;
    }
    var item = array[i];
    // ...
    goto exitLoop; // break
    // ...
    ++i;           // for post-expression
    goto startLoop; 

Пока компилятор делает это за один шаг, он дает вам представление о процессе. Код IL, который развивается из программы С#, является буквальным переводом последнего кода С#. Вы можете увидеть это здесь: https://dotnetfiddle.net/QaiLRz (нажмите 'view IL')

Теперь вы заметили, что во время процесса код становится более сложным. Самый простой способ наблюдать это - тот факт, что нам нужен все больше и больше кода, чтобы сделать то же самое. Вы также можете утверждать, что foreach, for, while и break на самом деле короткие руки для goto, что отчасти верно.

От IL до Assembler

Компилятор .NET JIT является компилятором SSA. Я не буду разбираться во всех деталях формы SSA здесь и как создать оптимизирующий компилятор, это слишком много, но может дать общее представление о том, что произойдет. Для более глубокого понимания лучше всего начинать читать оптимизацию компиляторов (мне очень нравится эта книга: http://ssabook.gforge.inria.fr/latest/book.pdf) и LLVM (llvm.org).

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

Однако теперь у нас есть ветки вперед для реализации наших циклов. Как вы могли догадаться, это на самом деле один из первых шагов, которые JIT собирается исправить, например:

    int i=0; // for initialization

    if (i >= array.Length) // for condition
    {
        goto endOfLoop;
    }

startLoop:
    var item = array[i];
    // ...
    goto endOfLoop; // break
    // ...
    ++i;           // for post-expression

    if (i >= array.Length) // for condition
    {
        goto startLoop;
    }

endOfLoop:
    // ...

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

Итак, почему компилятор делает это? Ну, если мы сможем развернуть цикл, мы можем его векторизовать. Мы могли бы даже доказать, что добавляются константы, что означает, что вся наша петля может исчезнуть в воздухе. Подводя итог: сделав шаблоны предсказуемыми (делая предсказуемые ветки), мы можем доказать, что в нашем цикле выполняются определенные условия, а это значит, что мы можем делать магию во время оптимизации JIT.

Однако, ветки склонны нарушать эти хорошие предсказуемые шаблоны, что, следовательно, является чем-то оптимизатором, таким образом, добрым - неприязнь. Перерыв, продолжение, goto - все они намерены сломать эти предсказуемые шаблоны - и поэтому не очень "хороши".

Вы также должны понимать на этом этапе, что простой foreach более предсказуем, чем куча операторов goto, которые идут повсюду. Что касается (1) читаемости и (2) с точки зрения оптимизатора, это лучшее решение.

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

Помогите, слишком много сложностей... что мне делать?

Суть в том, что вы всегда должны использовать имеющиеся в вашем распоряжении конструкторы языка, которые обычно (подразумеваются) создают предсказуемые шаблоны для вашего компилятора. По возможности избегайте странных ветвей (в частности: break, continue, goto или return в середине ничего).

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

Один из этих шаблонов называется SESE, что означает Single Entry Single Exit.

И теперь мы перейдем к реальному вопросу.

Представьте, что у вас есть что-то вроде этого:

// a is a variable.

for (int i=0; i<100; ++i) 
{
  for (int j=0; j<100; ++j)
  {
     // ...

     if (i*j > a) 
     {
        // break everything
     }
  }
}

Самый простой способ сделать это предсказуемым шаблоном - просто полностью исключить if:

int i, j;
for (i=0; i<100 && i*j <= a; ++i) 
{
  for (j=0; j<100 && i*j <= a; ++j)
  {
     // ...
  }
}

В других случаях вы также можете разбить метод на 2 метода:

// Outer loop in method 1:

for (i=0; i<100 && processInner(i); ++i) 
{
}

private bool processInner(int i)
{
  int j;
  for (j=0; j<100 && i*j <= a; ++j)
  {
     // ...
  }
  return i*j<=a;
}

Временные переменные? Хороший, плохой или уродливый?

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

Некоторые люди считают, что чище использовать временную переменную, и предлагают такое решение:

bool more = true;
for (int i=0; i<100; ++i) 
{
  for (int j=0; j<100; ++j) 
  {
     // ...
     if (i*j > a) { more = false; break; } // yuck.
     // ...
  }
  if (!more) { break; } // yuck.
  // ...
}
// ...

Я лично против этого подхода. Посмотрите еще раз, как компилируется код. Теперь подумайте о том, что это будет делать с этими хорошими, предсказуемыми шаблонами. Получить картину?

Хорошо, позвольте мне изложить это. Что произойдет, так это:

  • Компилятор выписывает все как ветки.
  • В качестве этапа оптимизации компилятор выполнит анализ потока данных, пытаясь удалить странную переменную more, которая используется только в потоке управления.
  • Если это так, переменная more будет удалена из программы, и остаются только ветки. Эти ветки будут оптимизированы, поэтому вы получите только одну ветвь из внутреннего цикла.
  • Если неуспешно, переменная more определенно используется во внутреннем цикле, поэтому, если компилятор не будет ее оптимизировать, он имеет высокий шанс быть выделенным регистру (который ест ценную память регистров).

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

Другими словами, наилучшим сценарием является то, что он будет эквивалентен этому:

for (int i=0; i<100; ++i) 
{
  for (int j=0; j<100; ++j)
  {
     // ...
     if (i*j > a) { goto exitLoop; } // perhaps add a comment
     // ...
  }
  // ...
}
exitLoop:

// ...

Мое личное мнение об этом довольно просто: если это то, что мы надумали, давайте упростим мир как для компилятора, так и для читаемости, и сразу напишите.

TL;DR:

Нижняя строка:

  • Используйте простое условие в вашем цикле for, если это возможно. Придерживайтесь высокоуровневых языковых конструкций, которые у вас есть в вашем распоряжении как можно больше.
  • Если все терпит неудачу, и вы остаетесь либо с goto, либо bool more, предпочтите первое.

Ответ 10

Иногда приятно абстрагировать код в его собственную функцию и использовать раннее возвращение - ранние возвращения - это зло, хотя:)

public void GetIndexOf(Transform transform, out int outX, out int outY)
{
    outX = -1;
    outY = -1;

    for (int x = 0; x < Columns.Length; x++)
    {
        var column = Columns[x];

        for (int y = 0; y < column.Transforms.Length; y++)
        {
            if(column.Transforms[y] == transform)
            {
                outX = x;
                outY = y;

                return;
            }
        }
    }
}

Ответ 11

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

for (int i = 0; i < 100; i++)
{
    for (int j = 0; j < 100; j++)
    {
        i = 100;
        break;
    }
}

Это не элегантно, но это может быть самое простое решение в зависимости от вашей проблемы.

Ответ 12

Я видел много примеров, которые используют "break", но не используют "continue".

По-прежнему потребуется какой-то флаг во внутреннем цикле:

while( some_condition )
{
    // outer loop stuff
    ...

    bool get_out = false;
    for(...)
    {
        // inner loop stuff
        ...

        get_out = true;
        break;
    }

    if( get_out )
    {
        some_condition=false;
        continue;
    }

    // more out loop stuff
    ...

}

Ответ 13

Поскольку я впервые увидел break в C пару десятилетий назад, эта проблема досадила мне. Я надеялся, что некоторые улучшения языка будут иметь расширение, которое будет работать таким образом:

break; // our trusty friend, breaks out of current looping construct.
break 2; // breaks out of the current and it parent looping construct.
break 3; // breaks out of 3 looping constructs.
break all; // totally decimates any looping constructs in force.

Ответ 14

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

В любом случае, чтобы вырваться из вложенных циклов, я делаю что-то вроде этого:

var isDone = false;
for (var x in collectionX) {
    for (var y in collectionY) {
        for (var z in collectionZ) {
            if (conditionMet) {
                // some code
                isDone = true;
            }
            if (isDone)
                break;
        }
        if (isDone) 
            break;
    }
    if (isDone)
        break;
}

... я надеюсь, что это поможет тем, кто любит меня, - "фанатисты":)

Ответ 15

Вот как я это сделал. Все еще обходной путь.

foreach (var substring in substrings) {
  //To be used to break from 1st loop.
  int breaker=1;
  foreach (char c in substring) {
    if (char.IsLetter(c)) {
      Console.WriteLine(line.IndexOf(c));
      \\setting condition to break from 1st loop.
      breaker=9;
      break;
    }
  }
  if (breaker==9) {
    break;
  }
}

Ответ 16

Выбросить настраиваемое исключение, выходящее за пределы цикла.

Он работает для for, foreach или while или любого типа цикла и любого языка, который использует try catch exception block

try 
{
   foreach (object o in list)
   {
      foreach (object another in otherList)
      {
         // ... some stuff here
         if (condition)
         {
            throw new CustomExcpetion();
         }
      }
   }
}
catch (CustomException)
{
   // log 
}

Ответ 17

         bool breakInnerLoop=false
        for(int i=0;i<=10;i++)
        {
          for(int J=0;i<=10;i++)
          {
              if(i<=j)
                {
                    breakInnerLoop=true;
                    break;
                }
          }
            if(breakInnerLoop)
            {
            continue
            }
        }

Ответ 18

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

            for (; j < 10; j++)
            {
                //solution
                bool breakme = false;
                for (int k = 1; k < 10; k++)
                {
                   //place the condition where you want to stop it
                    if ()
                    {
                        breakme = true;
                        break;
                    }
                }

                if(breakme)
                    break;
               }

простой и простой.:)

Ответ 19

Вы даже посмотрели ключевое слово break? O.o

Это просто псевдокод, но вы должны понимать, что я имею в виду:

<?php
for(...) {
    while(...) {
        foreach(...) {
            break 3;
        }
    }
}

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

Вручную: http://php.net/break

Ответ 20

Я думаю, если вы не хотите делать "логическую вещь", единственное решение - это бросить. Которое вы, очевидно, не должны делать..!