Romper com um loop aninhado
-
11-07-2019 - |
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 .
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
oubool 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 ..!