(Сейчас я в основном использую C#. Идеи на других языках приветствуются, но, пожалуйста, переведите их на C#, если сможете, и явным.)Есть ли «умный» способ вырваться из вложенных циклов?
Что-то, с чем я сталкиваюсь снова и снова, является вложенным циклом , просматривая какой-либо 2D-массив, чтобы найти элемент (обычно некоторый объект), который затем нужно манипулировать. Поэтому, конечно, как только вы обнаружите этот объект, вы должны вырваться из обоих циклов, чтобы вы не продолжали искать что-то, что уже было найдено (особенно в вложенных циклах, которые могут пересекать экспоненциально огромные массивы).
Следующий код в настоящее время мой предпочтительный способ сделать это:
Obj O = null;
bool KeepLooping = true;
for (int j = 0; j < height && KeepLooping; j++)
{
for (int i = 0; i < width; i++)
{
if (ObjArray[i, j] != null && ObjArray[i, j].property == search_value)
{
O = ObjArray[i, j]; // you found it, now remember it
KeepLooping = false; // clear the flag so the outer loop will break too
break;
}
}
}
Благодаря Эрик Funkenbusch, он становится гораздо более элегантным, если мы делаем это:
Obj O = null;
for (int j = 0; j < height && O == null; j++) // much, much better idea to check O for null in the outer loop
{
for (int i = 0; i < width; i++)
{
if (ObjArray[i, j] != null && ObjArray[i, j].property == search_value)
{
O = ObjArray[i, j]; // you found it, now remember it
break;
}
}
}
Больше нет необходимости в том, что pesky extra boolean!
Тем не менее поиск альтернативных или лучших решений продолжается. На протяжении многих лет я пытался много других способов, но найти их не так уж велика по тем или иным причинам: один к значению выше height
- Set
j
(итератор внешнего контура), который вызовет его сломать автоматически. Не идеально, потому что иногда вы хотите запомнить значенияi
иj
, где вы его нашли. - Используйте
foreach
на 2D-массиве. Не идеально, так какforeach
не позволит вам манипулировать коллекцией (удалите или добавьте к ней, что очень часто является причиной того, что я ищу объект в любом случае). - Просто поместите 2 петли в функцию, которая ничего не делает, кроме как найти и вернуть
O
.return
эффективно разрывает обе петли. Много раз это нормально, но не всегда. Я делаю это для очень общих поисков, но есть также довольно много «групповых поисков», где я хочу коллективизировать прохождение. В этих случаях я нахожу 2 или более объектов (иногда в пределах одного и того же 2D-массива), помню их и только потом вырывался из обоих циклов. - Использовать
goto
? (Whoa, может ли это быть единственным законным использованием goto? Это удивительно читаемо, чем флагKeepLooping
, особенно если у нас есть три или более петли.) Не идеально, потому что коллеги будут кричать кровавое убийство. И в C#, будет ли правильная очистка мусора послеgoto
? - Выбросить собственное исключение? idk, я никогда не пробовал, но он выглядит менее читаемым, чем мой предпочтительный способ.
- Как только вы нашли нужный объект, сделайте все свои манипуляции с объектом внутри внутреннего цикла, а затем
return;
Это может стать беспорядочным быстро. Иногда манипуляция объектом включает в себя свои собственные петли.
Существует также очень умный седьмой путь, благодаря User_PWY:
int size = width*height; // save this so you dont have to keep remultiplying it every iteration
for (int i = 0; i < size; i++)
{
int x = i % width; // ingenious method here
int y = i/width; // ingenious method here
O = ObjArray[x, y];
if (O != null)
break; // woohoo!
}
Это эффективно уплотняет 2D массива в одну for
цикла для итерации. Однако некоторые критики отметили, что мода и операторы деления довольно медленны по сравнению с только i ++ или j ++, поэтому он может быть медленнее (помните, что мы имеем дело с 2D-массивами того, кто знает, какой размер). Как я прокомментировал, должен быть способ получить разделение и остаток в одной операции, потому что я уверен, что ассемблерный код x86 имеет коды операций DIV, которые хранят частное и остальное в отдельных регистрах, все в одной инструкции DIV. Но как это сделать/использовать в C#, idk.
Было бы неплохо, если бы C# разрешили вам называть циклы, такие как L1
и L2
, а затем сделать что-то вроде L1.break()
; независимо от того, какой цикл вы внутри. Увы ... это невозможно сделать на этом языке. (Может ли быть секретный способ сделать это с помощью макросов?) Есть ли когда-нибудь C# 6.0 с этой функцией?
Редактировать: на мой взгляд, я сужу решения по их изяществу и скорости. Помните, что мы имеем дело с вложенными циклами, которые могут быть экспоненциально огромными. Дополнительная операция или сравнение могут иметь значение.
Хорошо, расскажите, пожалуйста, ваш предпочтительный способ, особенно если это что-то не перечисленное здесь.
'goto' в C# по причине, так что если вы чувствуете, что это законное использование случае Гото и имеют показатели производительности, чтобы доказать это, ваши коллеги не должны жаловаться. – zzzzBov
'goto' не меняется, как работает сборщик мусора. – MarcinJuraszek
@zzzzBov все в порядке, но сложно сказать коллегам, что они должны и не должны жаловаться, если вы не босс (я не):/ – DrZ214