Урожай полезен, потому что он сохраняет Вас пространство. Большая часть оптимизации в программировании делает компромисс между пространством (диск, память, объединяясь в сеть) и обработкой. Урожай как конструкция программирования позволяет Вам много раз выполнять итерации по набору в последовательности, не будучи нужен в отдельной копии набора для каждого повторения.
рассматривают этот пример:
static IEnumerable<Person> GetAllPeople()
{
return new List<Person>()
{
new Person() { Name = "George", Surname = "Bush", City = "Washington" },
new Person() { Name = "Abraham", Surname = "Lincoln", City = "Washington" },
new Person() { Name = "Joe", Surname = "Average", City = "New York" }
};
}
static IEnumerable<Person> GetPeopleFrom(this IEnumerable<Person> people, string where)
{
foreach (var person in people)
{
if (person.City == where) yield return person;
}
yield break;
}
static IEnumerable<Person> GetPeopleWithInitial(this IEnumerable<Person> people, string initial)
{
foreach (var person in people)
{
if (person.Name.StartsWith(initial)) yield return person;
}
yield break;
}
static void Main(string[] args)
{
var people = GetAllPeople();
foreach (var p in people.GetPeopleFrom("Washington"))
{
// do something with washingtonites
}
foreach (var p in people.GetPeopleWithInitial("G"))
{
// do something with people with initial G
}
foreach (var p in people.GetPeopleWithInitial("P").GetPeopleFrom("New York"))
{
// etc
}
}
(Очевидно, Вы не обязаны использовать урожай с дополнительными методами, он просто создает мощную парадигму для размышления о данных.)
, Как Вы видите, если у Вас есть много этих методов "фильтра" (но это может быть любой вид метода, который делает некоторую работу над списком людей) можно объединить многих в цепочку из них вместе, не требуя дополнительного пространства памяти для каждого шага. Это - один способ повысить язык программирования (C#) для выражения решений лучше.
первый побочный эффект урожая состоит в том, что он задерживает выполнение логики фильтрации, пока Вы на самом деле не требуете его. Если Вы поэтому создаете переменную типа IEnumerable<> (с урожаями), но никогда не выполняют итерации через него, Вы никогда не выполняете логику или занимаете место, которое является мощной и бесплатной оптимизацией.
другой побочный эффект состоит в том, что урожай воздействует на самый низкий общий интерфейс набора (IEnumerable<>), который включает создание подобного библиотеке кода с широкой применимостью.
Один из способов сделать это - конечный автомат. Но я бы все равно использовал goto. Все намного проще. :)
state = 0;
while( state >= 0){
switch(state){
case 0: i = 0; state = 1; // for i = 0
case 1:
i++;
if (i < 100) // if for i < 100 not finished
state = 2; // do the inner j loop
else
state = -1; // finish loop
case 2: j = 0; state = 3; // for j = 0
case 3:
j++;
if (j < 100) // if j < 100 not finished
state = 4 // do the inner k loop
else
state = 1; // go backt to loop i
break;
case 4: k = 0; state = 5;
case 5:
k++;
if (k == 50){
state = -1;
break;
}
if (k < 100) // if k loop not finished
state = 5; // do this loop
else
state = 3; // go back to upper loop
break;
default : state = -1;
}
}
Я бы сделал что-то вроде:
int i, j, k;
for (i = 0; i < 100; i++) {
for (j = 0; j < 100; j++) {
for (k = 0; k < 100; k++) {
if (k == 50) {
return;
}
}
}
}
Деление на 0 - самый надежный из известных мне методов, который вырвет вас из любого количества циклов. Это работает, потому что инструкция по сборке DIV не любит такой глупости.
Итак, вы можете попробовать следующее:
int i, j, k;
int flag1 = 0;
int flag2 = 0;
for (i = 0; i < 100; i++) {
for (j = 0; j < 100; j++) {
for (k = 0; k < 100; k++) {
if (k == 50) {
flag1 = 1;
flag2 = 1;
int z = 1 / 0; // we're outta here!!!
}
}
if (flag1 == 1)break;
}
if (flag2 == 1)break;
}
Возвращение из ловушки
, которая возникает при таких событиях, оставленных в качестве упражнения для читателя (это банально).
немного глупого самодокументирования:
int i, j, k;
int done = 0;
for (i = 0; i < 100 && ! done; i++) {
for (j = 0; j < 100 && ! done; j++) {
for (k = 0; k < 100 && ! done; k++) {
if (k == 50) we_are(done);
}
}
}
//...
void we_are(int *done) {
*done = 1;
}
но на самом деле у вас не должно быть трех вложенных for- петли. Вместо этого вам следует подумать о рефакторинге различных функций и улучшении логики вашей программы.
Хотя я согласен с тем, что иногда goto
действительно является лучшим решением, я думаю, что любая проблема, для которой goto
решение является результатом плохого кода.
Если преждевременное завершение любого цикла всегда означает, что вы должны прервать включающий цикл, то лишние флаги не нужны. Все это может выглядеть следующим образом
int i, j, k;
for (i = 0; i < 100; i++) {
for (j = 0; j < 100; j++) {
for (k = 0; k < 100; k++) {
if (k == 50)
break;
}
if (k < 100) break;
}
if (j < 100) break;
}
По моему опыту, это то, что нужно в большинстве случаев.
goto
. Это одно из немногих мест, где goto
является подходящим инструментом, и обычно это аргумент, почему goto
не является полным злом.
Однако иногда я так и поступаю. это:
void foo() {
bar_t *b = make_bar();
foo_helper(bar);
free_bar(b);
}
void foo_helper(bar_t *b) {
int i,j;
for (i=0; i < imax; i++) {
for (j=0; j < jmax; j++) {
if (uhoh(i, j) {
return;
}
}
}
}
Идея состоит в том, что я гарантированно получаю бесплатный бар, плюс я получаю чистый двухуровневый выход из переключателя через возврат.
Чуть лучше.
int i, j, k;
int flag1 = 0;
int flag2 = 0;
for (i = 0; i < 100 && !flag2; i++) {
for (j = 0; j < 100 && !flag1; j++) {
for (k = 0; k < 100; k++) {
if (k == 50) {
flag1 = 1;
flag2 = 1;
break;
}
}
}
}
Но если вам действительно нужны эти циклы, то для удобства чтения имеет смысл явно указывать в каждом цикле, какие условия должны выполняться для его продолжения.
иногда вы можете использовать такой трюк:
for (i = 0; i < 100 && !flag2; i++) {
for (j = 0; j < 100 && !flag1; j++) {
for (k = 0; k < 100; k++) {
if (k == 50) {
k = 100;
i = 100;
j = 100;
}
}
}
}
или объявить флаг сложения в вашем цикле:
bool end = false;
for(int i =0; i < 1000 && !end; i++) {
//do thing
end = true;
}
он стоит всего одну строку, но, я думаю, чистый.
Джастин
How would you accomplish the same thing? (w/o using jumps)
Why? Nothing is universally evil, and every put-upon tool has its uses (except gets()
). Using goto
here makes your code look cleaner, and is one of the only choices we have (assuming C). Look:
int i, j, k;
for (i = 0; i < 100; i++) {
for (j = 0; j < 100; j++) {
for (k = 0; k < 100; k++) {
if (k == 50) {
goto END;
}
}
}
}
END:
Much cleaner than all those flag variables, and it even shows more clearly what your code is doing.
Поместите все циклы в функцию и просто верните вместо break.
If you're using Java, you can associate labels with each for block and then reference the label after a continue statement. For example:
outerfor:
for (int i=0; i<5; i++) {
innerfor:
for (int j=0; j<5; j++) {
if (i == 1 && j == 2) {
continue outerfor;
}
}
}