Если у меня есть цикл for, который вложен в другой, как я могу эффективно выйти из обоих циклов (внутреннего и внешнего) как можно быстрее?
Я не хочу использовать логическое значение, а затем должен сказать перейти к другому методу, а просто выполнить первую строку кода после внешнего цикла.
Какой быстрый и приятный способ сделать это?
Я думал, что исключения не дешевы / должны создаваться только в действительно исключительном состоянии и т. Д. Поэтому я не думаю, что это решение было бы хорошим с точки зрения производительности.
Я не чувствую, что правильно использовать новые возможности .NET (анон методы), чтобы сделать что-то довольно фундаментальное.
c#
for-loop
nested-loops
GurdeepS
источник
источник
Ответы:
Ну,
goto
но это некрасиво и не всегда возможно. Вы также можете поместить циклы в метод (или anon-метод) и использоватьreturn
для возврата к основному коду.против:
Обратите внимание, что в C # 7 мы должны получить «локальные функции», что (синтаксис tbd и т. Д.) Означает, что оно должно работать примерно так:
источник
goto
вредны сами по себе , хотя это просто правильный инструмент для выполнения некоторых действий, и, как и многие другие инструменты, могут быть использованы неправильно. Эта религиозная неприязньgoto
откровенно довольно глупа и определенно ненаучна.C # адаптация подхода, часто используемого в C - установить значение переменной внешнего цикла вне условий цикла (т.е. для цикла, использующего переменную int,
INT_MAX -1
часто является хорошим выбором):Как говорится в примечании к коду
break
, магически не будет переходить к следующей итерации внешнего цикла - поэтому, если у вас есть код вне внутреннего цикла, этот подход требует больше проверок. Рассмотрим другие решения в этом случае.Этот подход работает с
for
иwhile
циклов, но не работает дляforeach
. В случае, если уforeach
вас не будет доступа к коду для скрытого перечислителя, вы не сможете его изменить (и даже если у васIEnumerator
не будет никакого метода MoveToEnd).Благодарность авторам комментариев:
i = INT_MAX - 1
предложение от Metafor
/foreach
комментарий ygoe .Правильно
IntMax
по jmbpianoзамечания о коде после внутреннего цикла по blizpasta
источник
Это решение не относится к C #
Для людей, которые нашли этот вопрос на других языках, Javascript, Java и D позволяют помечать разрывы и продолжать :
источник
Используйте подходящую защиту во внешней петле. Установите предохранитель во внутренней петле, прежде чем сломать.
Или еще лучше, абстрагируйте внутренний цикл в метод и выйдите из внешнего цикла, когда он вернет false.
источник
Не цитируйте меня по этому поводу , но вы можете использовать Goto поводу как предложено в MSDN. Существуют и другие решения, в том числе флаг, который проверяется на каждой итерации обоих циклов. Наконец, вы можете использовать исключение в качестве действительно тяжелого решения вашей проблемы.
ПЕРЕЙТИ К:
Состояние:
Исключение:
источник
Можно ли реорганизовать вложенный цикл for в закрытый метод? Таким образом, вы можете просто «вернуться» из метода, чтобы выйти из цикла.
источник
[&] { ... return; ... }();
Мне кажется, что людям очень не нравится
goto
высказывание, поэтому я почувствовал необходимость немного исправить это.Я считаю, что «эмоции», которые люди испытывают, в
goto
конечном итоге сводятся к пониманию кода и (заблуждениям) о возможных последствиях для производительности. Прежде чем ответить на вопрос, я сначала расскажу о некоторых деталях его компиляции.Как мы все знаем, C # компилируется в IL, который затем компилируется в ассемблер с использованием компилятора SSA. Я немного расскажу о том, как все это работает, а затем попытаюсь ответить на сам вопрос.
От C # до IL
Сначала нам нужен кусок кода C #. Давайте начнем с простого:
Я сделаю это шаг за шагом, чтобы дать вам хорошее представление о том, что происходит под капотом.
Первый перевод: из
foreach
эквивалентногоfor
цикла (Примечание: здесь я использую массив, потому что я не хочу вдаваться в подробности IDisposable - в этом случае мне также придется использовать IEnumerable):Второй перевод:
for
иbreak
переводится в более простой эквивалент:И третий перевод (это эквивалент IL-кода): мы меняемся
break
иwhile
превращаемся в ветку:Хотя компилятор делает все это за один шаг, он дает вам представление о процессе. Код IL, который развивается из программы C #, является буквальным переводом последнего кода C #. Вы можете увидеть здесь: https://dotnetfiddle.net/QaiLRz (нажмите «посмотреть IL»)
Одна вещь, которую вы здесь заметили, заключается в том, что во время процесса код становится более сложным. Самый простой способ убедиться в этом - это то, что нам требовалось все больше и больше кода для подтверждения того же самого. Можно также утверждать , что
foreach
,for
,while
иbreak
на самом деле являются короткими руками дляgoto
, что отчасти верно.От IL до Ассемблера
JIT-компилятор .NET является SSA-компилятором. Я не буду вдаваться во все детали формы SSA и о том, как создать оптимизирующий компилятор, это слишком много, но может дать общее представление о том, что произойдет. Для более глубокого понимания лучше начать с чтения по оптимизации компиляторов (мне нравится эта книга для краткого введения: http://ssabook.gforge.inria.fr/latest/book.pdf ) и LLVM (llvm.org) ,
Каждый оптимизирующий компилятор опирается на тот факт, что код прост и следует предсказуемым шаблонам . В случае циклов FOR мы используем теорию графов для анализа ветвей, а затем оптимизируем такие вещи, как cycli в наших ветвях (например, ветвления в обратном направлении).
Однако теперь у нас есть прямые ветви для реализации наших циклов. Как вы могли догадаться, это на самом деле один из первых шагов, которые JIT собирается исправить, например:
Как видите, у нас теперь есть обратная ветвь, которая является нашей маленькой петлей. Единственная вещь, которая все еще противна здесь, - это ветвь, в которой мы оказались из-за нашего
break
заявления. В некоторых случаях мы можем перемещать это таким же образом, но в других это остается.Так почему же это делает компилятор? Что ж, если мы сможем развернуть цикл, мы сможем его векторизовать. Мы могли бы даже доказать, что добавляются только константы, что означает, что весь наш цикл может исчезнуть в воздухе. Подводя итог: сделав шаблоны предсказуемыми (делая предсказуемые ветви), мы можем доказать, что в нашем цикле выполняются определенные условия, что означает, что мы можем творить чудеса во время оптимизации JIT.
Тем не менее, ветки имеют тенденцию нарушать эти хорошие предсказуемые паттерны, что является чем-то, что оптимизаторы, а значит, любить. Разбить, продолжить, перейти - все они намереваются нарушить эти предсказуемые закономерности - и поэтому не очень «хороши».
В этот момент вы также должны понимать, что простое
foreach
предсказуемее, чем наборgoto
утверждений, которые встречаются повсюду. С точки зрения (1) читабельности и (2) с точки зрения оптимизатора, это и лучшее решение.Еще одна вещь, о которой стоит упомянуть, это то, что для оптимизации компиляторов очень важно назначать регистры переменным (процесс, называемый распределением регистров ). Как вы, возможно, знаете, в вашем ЦП имеется только конечное число регистров, и они являются самыми быстрыми частями памяти в вашем оборудовании. Переменные, используемые в коде, который находится в самом внутреннем цикле, с большей вероятностью получат назначенный регистр, в то время как переменные вне вашего цикла менее важны (потому что этот код, вероятно, ударил меньше).
Помогите, слишком много сложностей ... что мне делать?
Суть в том, что вы всегда должны использовать языковые конструкции, которые есть в вашем распоряжении, что обычно (неявно) создает предсказуемые шаблоны для вашего компилятора. Старайтесь избегать странных ветвей , если это возможно ( в частности:
break
,continue
,goto
илиreturn
в середине ничего).Хорошая новость заключается в том, что эти предсказуемые шаблоны легко читаются (для людей) и легко обнаруживаются (для компиляторов).
Один из этих шаблонов называется SESE, что означает однократный выход.
И теперь мы подошли к реальному вопросу.
Представьте, что у вас есть что-то вроде этого:
Самый простой способ сделать это предсказуемой моделью - это просто
if
полностью исключить :В других случаях вы также можете разделить метод на 2 метода:
Временные переменные? Хорошо, плохо или безобразно?
Вы можете даже решить вернуть логическое значение из цикла (но я лично предпочитаю форму SESE, потому что именно так ее увидит компилятор, и я думаю, что ее чище читать).
Некоторые люди думают, что лучше использовать временную переменную, и предлагают решение, подобное этому:
Я лично против такого подхода. Посмотрите еще раз, как код компилируется. Теперь подумайте, что это будет делать с этими хорошими, предсказуемыми шаблонами. Получить картину?
Хорошо, позвольте мне изложить это. Что произойдет, так это:
more
переменную, которая используется только в потоке управления.more
будет удалена из программы, и останутся только ветви. Эти ветви будут оптимизированы, поэтому вы получите только одну ветку из внутреннего цикла.more
определенно используется в самом внутреннем цикле, поэтому, если компилятор не оптимизирует ее, у нее есть высокая вероятность быть назначенной регистру (который пожирает ценную память регистра).Итак, подведем итоги: оптимизатор в вашем компиляторе постигнет кучу хлопот, чтобы выяснить, что
more
используется только для потока управления, и в лучшем случае преобразует его в одну ветку за пределами внешнего для петля.Другими словами, в лучшем случае это будет эквивалентно следующему:
Мое личное мнение об этом довольно простое: если это то, что мы намеревались все время, давайте сделаем мир проще как для компилятора, так и для удобства чтения, и напишем это сразу.
ТЛ; др:
Нижняя граница:
goto
либоbool more
, предпочитайте первое.источник
yield return
. То есть, хотя легко показать, что есть некоторые полезные случаи, для большинства кода «уровня приложения» это, вероятно, очень низкий уровень разочарования в C #, исключая те, которые «только что пришли» из C / C ++ ;-) Я также иногда пропускаю Руби "throw" (не исключая раскручивание) иногда, поскольку он также вписывается в эту область.goto
, не сделает ваш код более читабельным - я бы сказал, что все происходит с точностью до наоборот: на самом деле ваш поток управления будет содержать больше узлов - что в значительной степени определение сложности. Просто избегать этого, чтобы помочь самодисциплине, звучит контрпродуктивно с точки зрения читабельности.goto
просто не имеют дисциплины, чтобы не злоупотреблять ею.включите функцию / метод и используйте ранний возврат или переставьте ваши циклы в предложение while. Перейти / исключения / все, что, конечно, здесь не подходит.
источник
Вы просили сочетание быстрого, красивого, без использования логического выражения, без использования goto и C #. Вы исключили все возможные способы делать то, что вы хотите.
Самый быстрый и наименее уродливый способ - использовать goto.
источник
Иногда приятно абстрагировать код в его собственную функцию, а затем использовать ранний возврат - хотя ранние возвраты являются злом:)
источник
Я видел много примеров, которые используют «break», но ни один из которых не использует «continue».
Это все еще потребовало бы флага некоторого вида во внутреннем цикле:
источник
С тех пор как я впервые увидел
break
C на пару десятилетий назад, эта проблема меня раздражала. Я надеялся, что некоторое улучшение языка будет иметь расширение, которое будет работать таким образом:источник
Из моих студенческих дней я помню, что было сказано, что математически доказуемо, что вы можете делать что-либо в коде без goto (то есть нет ситуации, когда goto является единственным ответом). Таким образом, я никогда не использую goto's (просто мое личное предпочтение, не предполагая, что я прав или нет)
В любом случае, чтобы вырваться из вложенных циклов, я делаю что-то вроде этого:
... я надеюсь, что те, кто любит меня, помогают анти-гото "фанатам" :)
источник
Вот как я это сделал. Все еще обходной путь.
источник
Самый простой способ завершить двойной цикл - это напрямую завершить первый цикл.
источник
Циклы могут быть нарушены с помощью пользовательских условий в цикле, что позволяет иметь чистый код.
С помощью этого кода мы получаем следующий вывод:
источник
Другой вариант - анонимная функция, вызываемая самостоятельно . Без перехода, без метки, без новой переменной, без использования нового имени функции и на одну строку короче, чем в примере с анонимным методом в верхней части.
источник
Создайте пользовательское исключение, которое выходит из внешнего цикла.
Он работает
for
,foreach
илиwhile
или любой вид цикла и любой язык , который используетtry catch exception
блокисточник
источник
Как я вижу, вы приняли ответ, в котором человек ссылается на ваше заявление goto, где в современном программировании и, по мнению экспертов, goto является убийцей, мы назвали его убийцей в программировании, у которого есть некоторые определенные причины, которые я не буду обсуждать здесь на данный момент, но решение вашего вопроса очень простое, вы можете использовать логический флаг в сценарии такого рода, как я продемонстрирую в своем примере:
просто и понятно. :)
источник
Вы даже смотрели на
break
ключевое слово? ОоЭто просто псевдокод, но вы должны понимать, что я имею в виду:
Если вы думаете о
break
том, чтобы быть функцией, какbreak()
, то ее параметром будет число циклов, из которых нужно выйти. Поскольку мы находимся в третьем цикле кода, мы можем выйти из всех трех.Руководство: http://php.net/break
источник
Я думаю, если вы не хотите делать «булеву вещь», единственное решение - бросить. Что вы, очевидно, не должны делать ..!
источник