Pergunta

Se eu tiver um loop para os quais é aninhada dentro de outras, como posso eficientemente sair de ambos os loops (interna e externa) da maneira mais rápida possível?

Eu não quero ter que usar um boolean e depois ter de dizer ir para outro método, mas sim apenas para executar a primeira linha de código após o loop externo.

O que é uma maneira rápida e agradável de ir sobre isso?

Graças


Eu estava pensando que as exceções não são baratos / só deve ser jogado em uma condição verdadeiramente excepcional etc Por isso eu não acho que esta solução seria bom a partir de uma perspectiva de desempenho.

Eu não sinto que é certo para aproveitar os recursos mais recentes em .NET (métodos Anon) para fazer algo que é muito fundamental.

Por isso, tvon (desculpe não pode soletrar nome completo!) Tem uma solução agradável.

Marc: uso de Nice de métodos anon, e isso também é grande, mas porque eu poderia estar em um trabalho onde não usamos uma versão do .NET / C # que suporta anon métodos, eu preciso saber uma abordagem tradicional também .

Foi útil?

Solução

Bem, goto, mas isso é feio, e nem sempre é possível. Você também pode colocar os loops em um método (ou um método-Anon) e uso return para sair de volta para o código principal.

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

vs:

// 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");

Note que em C # 7 devemos obter "funções locais", que (sintaxe tbd etc) significa que ele deve trabalhar algo como:

// 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");

Outras dicas

C # adaptação de abordagem muitas vezes usado em C - valor conjunto de fora variável do circuito externo de condições de alça (isto é, para loop usando INT_MAX -1 variável int é muitas vezes uma boa escolha):

for (int i = 0; i < 100; i++)
{
    for (int j = 0; j < 100; j++)
    {
        if (exit_condition)
        {
            // cause the outer loop to break:
            // use i = INT_MAX - 1; otherwise i++ == INT_MIN < 100 and loop will continue 
            i = int.MaxValue - 1;
            Console.WriteLine("Hi");
            // break the inner loop
            break;
        }
    }
    // if you have code in outer loop it will execute after break from inner loop    
}

Como nota no código diz break não vai magicamente saltar para a próxima iteração do loop externo - por isso, se você tem fora do código de loop interno esta abordagem exige mais verificações. Considerar outras soluções em tal caso.

Esta abordagem funciona com loops for e while mas não funciona para foreach. Em caso de foreach você não terá acesso código para o enumerador escondida para que você não pode mudá-lo (e mesmo se você poderia IEnumerator não tem algum método "MoveToEnd").

Agradecimentos aos autores Comentários inlined:
i = INT_MAX - 1 sugestão por Meta
for / foreach comentário por ygoe .
IntMax adequada por jmbpiano
observação sobre o código depois de loop interno por blizpasta

Esta solução não se aplica ao C #

Para as pessoas que encontraram esta questão através de outros idiomas, Javascript, Java, e D permite pausas rotulados e continua :

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

Use um protetor adequado no circuito exterior. Defina o guarda no laço interno antes de quebrar.

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
    }
}

Ou ainda melhor, abstrato do circuito interno em um método e sair do loop externo quando ele retorna false.

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

    .... some outer loop stuff

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

    ... more outer loop stuff
}

Do not citar-me sobre isso, mas você poderia usar Ir à como sugerido na MSDN. Existem outras soluções, como a inclusão de um sinalizador que é verificado em cada iteração de ambas as espiras. Finalmente, você pode usar uma exceção como uma solução muito pesado para o seu problema.

GOTO:

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

Estado:

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
   }
}

Exceção:

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 ) {}

É possível refatorar o laço for aninhado em um método particular? Dessa forma, você poderia simplesmente 'return' fora do método para sair do loop.

Parece-me que as pessoas não gostam de uma declaração goto muito, então eu senti a necessidade de endireitar isto um pouco.

Acredito que as pessoas das emoções 'ter sobre goto, eventualmente, se resumem a compreensão do código e (equívocos) sobre possíveis implicações de desempenho. Antes de responder à pergunta, vou, portanto, em primeiro lugar entrar em alguns dos detalhes de como ele é compilado.

Como todos sabemos, C # é compilado para IL, que é então compilado para assembler usando um compilador SSA. Vou te dar um pouco de insights sobre como tudo isso funciona, e, em seguida, tentar responder a pergunta em si.

De C # para IL

Em primeiro lugar, precisamos de um pedaço de código C #. Vamos começar simples:

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

Eu vou fazer isso passo a passo para lhe dar uma boa idéia do que acontece nos bastidores.

Primeiro tradução: de foreach ao lacete for equivalente (Nota: eu estou usando uma matriz aqui, porque eu não quero entrar em detalhes de IDisposable - caso em que eu também teria que usar um IEnumerable ):

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

Segundo tradução: o for e break é traduzido em um mais fácil equivalentes:

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

E terceiro tradução (este é o equivalente do código IL): mudamos break e while em um ramo:

    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; 

Enquanto o compilador faz essas coisas em uma única etapa, que lhe dá uma visão sobre o processo. O código IL que evolui a partir do programa C # é o tradução literal do último código de C #. Você pode ver por si mesmo aqui: https://dotnetfiddle.net/QaiLRz (clique 'visão IL')

Agora, uma coisa que você tem observado aqui é que durante o processo, o código torna-se mais complexa. A maneira mais fácil de observar isso é o fato de que precisávamos de mais e mais código para ackomplish a mesma coisa. Você também pode argumentar que foreach, for, while e break são realmente curtos mãos para goto, que é parcialmente verdadeiro.

De IL para Assembler

O compilador JIT .NET é um compilador de SSA. Não vou entrar em todos os detalhes da forma SSA aqui e como criar um compilador de otimização, é apenas muito, mas pode dar uma compreensão básica sobre o que vai acontecer. Para uma compreensão mais profunda, é melhor começar a ler-se na otimização de compiladores (eu gosto deste livro para uma introdução breve: http://ssabook.gforge.inria.fr/latest/book.pdf ) e LLVM (llvm.org).

Cada compilador otimização se baseia no fato de que o código é fácil e segue padrões previsíveis . No caso de laços FOR, usamos a teoria dos grafos para analisar ramos, e as coisas em seguida, otimizar como cycli em nossas filiais (por exemplo ramos para trás).

No entanto, agora temos filiais para a frente para implementar nossos loops. Como você deve ter adivinhado, este é realmente um dos primeiros passos que o JIT vai resolver, como este:

    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:
    // ...

Como você pode ver, temos agora uma filial para trás, que é o nosso pequeno loop. A única coisa que ainda é desagradável aqui é o ramo que acabamos com o devido à nossa declaração de break. Em alguns casos, podemos mover este da mesma forma, mas em outros ele está lá para ficar.

Então, por que o compilador fazer isso? Bem, se é que podemos desenrolar o loop, que pode ser capaz de vetorizar-lo. Podemos até ser capaz de prova de que não é apenas constantes que está sendo adicionado, o que significa que todo o nosso ciclo poderia desaparecer no ar. Para resumir:., Tornando os padrões previsíveis (fazendo os ramos previsíveis), podemos prova de que certas condições têm em nosso loop, o que significa que pode fazer mágica durante a otimização JIT

No entanto, os ramos tendem a quebrar aqueles agradávelpadrões previsíveis, que é algo otimizadores, portanto, tipo-a antipatia. Break, continuar, Goto -. Todos eles pretendem quebrar estes dos testes padrões previsíveis e, portanto, não realmente 'agradável'

Você também deve perceber neste momento que um foreach simples é mais previsível, em seguida, um monte de declarações goto que vão em todo o lugar. Em termos de (1) a legibilidade e (2) a partir de uma perspectiva optimizador, é tanto a melhor solução.

Outra coisa vale a pena mencionar é que é muito relevante para a otimização compiladores para registros atribuir a variáveis ??(um processo chamado registar alocação ). Como você deve saber, há apenas um número finito de registros em sua CPU e eles são de longe os pedaços mais rápidos de memória em seu hardware. Variáveis ??utilizadas no código que está no centro da maior loop, são mais propensos a ter um registo atribuído, enquanto variáveis ??fora de seu loop são menos importantes (porque este código é provavelmente atingiu menos).

Ajuda, muita complexidade ... o que devo fazer?

A linha inferior é que você deve sempre usar as construções de linguagem que você tem à sua disposição, o que normalmente (implicitamente) construir padrões previsíveis para o seu compilador. Tente evitar ramos estranhos se possível (especificamente: break, continue, goto ou um return no meio do nada).

A boa notícia é que esses padrões previsíveis são fáceis de ler (para humanos) e fáceis de detectar (para compiladores).

Um desses padrões é chamado SESE, o que significa Single Entrada Individual Sair.

E agora chegamos à questão real.

Imagine que você tem algo como isto:

// a is a variable.

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

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

A maneira mais fácil de fazer isso um padrão previsível é simplesmente eliminar a if completamente:

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

Em outros casos você pode também dividir o método em 2 métodos:

// 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;
}

variáveis ??temporários? Bom, mau ou feio?

Você pode até decidir retornar um booleano de dentro do loop (mas eu pessoalmente prefiro a forma SESE porque é assim que o compilador vai vê-lo e eu acho que é mais limpo para ler).

Algumas pessoas pensam que é mais limpo para usar uma variável temporária, e propor uma solução como esta:

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.
  // ...
}
// ...

Eu pessoalmente me oponho a essa abordagem. Olhe novamente sobre a forma como o código é compilado. Agora pense sobre o que isso vai fazer com estes agradáveis, padrões previsíveis. Obter a imagem?

Certo, deixe-me explicar isso. O que vai acontecer é que:

  • O compilador irá escrever tudo como ramos.
  • Como uma etapa de otimização, o compilador vai fazer fluxo de dados de análise em uma tentativa de remover a variável more estranho que só acontece a ser usado em fluxo de controle.
  • Se bem sucedida, a variável more será eliminado do programa, e apenas ramos permanecem. Estes ramos será otimizado, então você vai ter apenas um único ramo fora do circuito interno.
  • Se unsuccesful, o more variável é definitivamente usado no circuito mais interna, por isso, se o compilador não irá otimizar-lo afastado, ele tem uma grande chance de ser alocado para um registo (que consome memória registo valioso).

Assim, para resumir: o otimizador em seu compilador vai entrar em um inferno de um monte de problemas para descobrir que more é utilizado apenas para o fluxo de controle, e no melhor cenário traduzi-lo para um único lado de fora ramo do exterior para o loop.

Em outras palavras, o melhor cenário é que ele vai acabar com o equivalente a isso:

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:

// ...

A minha opinião pessoal sobre isso é bastante simples: se é isso que nós pretendemos todo o tempo, vamos tornar o mundo mais fácil tanto para o compilador e legibilidade, e escrever que aw direitaay.

tl; dr:

A linha inferior:

  • Use uma condição simples em seu loop for, se possível. Stick para a linguagem de alto nível constrói você tem à sua disposição, tanto quanto possível.
  • Se tudo falhar e você é deixado com qualquer goto ou bool more, prefiro a primeira.

fator em uma função / método e usar retorno precoce, ou reorganizar seus loops em um tempo-cláusula. Goto / exceções / whatever são certamente não apropriada aqui.

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

Você pediu uma combinação de rápida, agradável, sem o uso de um booleano, sem uso de Goto, e C #. Você já descartou todas as formas possíveis de fazer o que quiser.

A maneira mais rápida e menos feio é usar um goto.

Às vezes agradável para abstrair o código em si própria função e que o uso de um retorno precoce - primeiros resultados são maus embora:)

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;
            }
        }
    }
}

Dependendo da sua situação, você pode ser capaz de fazer isso, mas apenas se a sua não execução de código após o loop interno.

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

Não é elegent, mas pode ser a solução mais fácil dependendo do seu problema.

Eu vi um monte de exemplos que o uso de "break" mas nenhum que o uso de "continuar".

Ainda exigiria uma bandeira de algum tipo no loop interno:

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
    ...

}

Desde que eu primeiro break serra em C um par de décadas atrás, este problema tem me afligia. Eu estava esperando que algum acessório linguagem teria uma extensão de quebrar que funcionaria assim:

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

Eu me lembro dos meus tempos de estudante que foi dito é matematicamente demonstrável que você pode fazer qualquer coisa em código sem um goto (ou seja, não há nenhuma situação em Goto é a única resposta). Então, eu nunca usar goto (apenas a minha preferência pessoal, não sugerindo que eu estou certo ou errado)

De qualquer forma, para sair de loops aninhados eu faço algo como isto:

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;
}

... espero que ajuda para aqueles que como eu são anti-Goto "fanboys":)

Isso é como eu fiz isso. Ainda uma solução alternativa.

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;
  }
}

Lance uma exceção personalizada que sai outter loop.

Ele funciona para for, foreach ou while ou qualquer tipo de loop e qualquer linguagem que bloco usos try catch exception

try 
{
   foreach (object o in list)
   {
      foreach (object another in otherList)
      {
         // ... some stuff here
         if (condition)
         {
            throw new CustomExcpetion();
         }
      }
   }
}
catch (CustomException)
{
   // log 
}
         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
            }
        }

Como eu vejo você aceitou a resposta em que a pessoa se refere você Ir à declaração, onde na programação moderna e em Goto opinião de especialistas é um assassino, que chamou de um assassino em programação que tem algumas determinadas razões, que eu não vou discutir -lo aqui neste momento, mas a solução da sua pergunta é muito simples, você pode usar um sinalizador booleano neste tipo de cenário como vou demonstrar isso no meu exemplo:

            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;
               }

simples e clara. :)

Você mesmo olhar para a palavra-chave break? O.o

Este é apenas pseudo-código, mas você deve ser capaz de ver o que quero dizer:

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

Se você pensar sobre break sendo uma função como break(), então o parâmetro que de seria o número de loops de romper. Como estamos no terceiro laço no código aqui, podemos sair de todos os três.

Manual: http://php.net/break

Eu acho que a menos que você quer fazer a "coisa boolean" a única solução é realmente para jogar. Que obviamente você não deve fazer ..!

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top