Se eu tenho um loop for aninhado em outro, como posso sair eficientemente dos dois loops (internos e externos) da maneira mais rápida possível?
Eu não quero ter que usar um booleano e depois dizer ir para outro método, mas apenas para executar a primeira linha de código após o loop externo.
Qual é uma maneira rápida e agradável de fazer isso?
Eu estava pensando que as exceções não são baratas / devem ser lançadas apenas em uma condição verdadeiramente excepcional etc. Portanto, não acho que essa solução seja boa do ponto de vista de desempenho.
Eu não acho certo tirar proveito dos recursos mais recentes do .NET (métodos anon) para fazer algo que é bastante fundamental.
c#
for-loop
nested-loops
GurdeepS
fonte
fonte
Respostas:
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 não) e usáreturn
-lo para voltar ao código principal.vs:
Observe que no C # 7 devemos obter "funções locais", o que (sintaxe tbd etc) significa que deve funcionar algo como:
fonte
goto
são prejudiciais em si , enquanto simplesmente é o instrumento correto para fazer algumas coisas, e como muitos instrumentos podem ser mal utilizados. Essa aversão religiosa contragoto
é francamente bastante estúpida e definitivamente não científica.A adaptação de C # da abordagem frequentemente usada em C - define o valor da variável do loop externo fora das condições do loop (ou seja, para loop usando a variável int
INT_MAX -1
geralmente é uma boa escolha):Como a nota no código diz
break
, não irá pular magicamente para a próxima iteração do loop externo - portanto, se você tiver código fora do loop interno, essa abordagem exige mais verificações. Considere outras soluções nesse caso.Essa abordagem trabalha com
for
ewhile
voltas, mas não funciona paraforeach
. No caso deforeach
você não ter acesso ao código ao enumerador oculto, não poderá alterá-lo (e mesmo queIEnumerator
não possa ter algum método "MoveToEnd").Agradecimentos aos autores dos comentários embutidos :
i = INT_MAX - 1
sugestão de Metafor
/foreach
comentário de ygoe .Adequado
IntMax
por jmbpianoobservação sobre código após loop interno por blizpasta
fonte
Esta solução não se aplica ao C #
Para as pessoas que encontraram essa pergunta por outros idiomas, Javascript, Java e D permitem quebras rotuladas e continuam :
fonte
Use uma proteção adequada no circuito externo. Coloque a proteção no circuito interno antes de quebrar.
Ou, melhor ainda, abstraia o loop interno em um método e saia do loop externo quando ele retornar false.
fonte
Não me cite, mas você pode usar o goto conforme sugerido no MSDN. Existem outras soluções, como a inclusão de um sinalizador verificado em cada iteração dos dois loops. Finalmente, você pode usar uma exceção como uma solução realmente pesada para o seu problema.
VAMOS PARA:
Doença:
Exceção:
fonte
É possível refatorar o loop for aninhado em um método privado? Dessa forma, você pode simplesmente 'retornar' do método para sair do loop.
fonte
[&] { ... return; ... }();
Parece-me que as pessoas não gostam muito de uma
goto
declaração, então senti a necessidade de esclarecer um pouco isso.Acredito que as 'emoções' que as pessoas
goto
acabam se resumindo à compreensão do código e (equívocos) sobre possíveis implicações no desempenho. Antes de responder à pergunta, portanto, primeiro vou entrar em alguns detalhes de como ela é compilada.Como todos sabemos, o C # é compilado para IL, que é compilado para o assembler usando um compilador SSA. Darei algumas idéias sobre como tudo isso funciona e, em seguida, tentarei responder à pergunta em si.
De C # para IL
Primeiro, precisamos de um pedaço de código C #. Vamos começar simples:
Vou fazer isso passo a passo para lhe dar uma boa idéia do que acontece sob o capô.
Primeira tradução: de
foreach
para ofor
loop equivalente (Nota: estou usando uma matriz aqui, porque não quero entrar em detalhes de IDisposable - nesse caso, eu também teria que usar um IEnumerable):Segunda tradução: o
for
ebreak
é traduzido em um equivalente mais fácil:E terceira tradução (isso é equivalente ao código IL): mudamos
break
ewhile
em uma agência:Embora o compilador faça essas coisas em uma única etapa, ele fornece informações sobre o processo. O código IL que evolui do programa C # é o tradução literal do último código C #. Você pode ver por si mesmo aqui: https://dotnetfiddle.net/QaiLRz (clique em 'ver IL')
Agora, uma coisa que você observou aqui é que, durante o processo, o código se torna mais complexo. A maneira mais fácil de observar isso é pelo fato de precisarmos de mais e mais códigos para realizar a mesma coisa. Você também pode argumentar que
foreach
,for
,while
ebreak
são realmente curtos mãos paragoto
, que é parcialmente verdadeiro.De IL para Assembler
O compilador .NET JIT é um compilador SSA. Não vou entrar em todos os detalhes do formulário SSA aqui e como criar um compilador otimizador, é demais, mas posso fornecer um entendimento básico sobre o que acontecerá. Para uma compreensão mais profunda, é melhor começar a ler sobre a otimização de compiladores (eu gosto deste livro para uma breve introdução: http://ssabook.gforge.inria.fr/latest/book.pdf ) e LLVM (llvm.org) .
Todo compilador de otimização depende do fato de que o código é fácil e segue padrões previsíveis . No caso dos loops FOR, usamos a teoria dos grafos para analisar ramificações e, em seguida, otimizamos coisas como cycli em nossas ramificações (por exemplo, ramificações para trás).
No entanto, agora temos ramificações avançadas para implementar nossos loops. Como você deve ter adivinhado, esta é realmente uma das primeiras etapas que o JIT corrigirá, assim:
Como você pode ver, agora temos um ramo inverso, que é nosso pequeno loop. A única coisa que ainda é desagradável aqui é o ramo com o qual acabamos devido à nossa
break
declaração. Em alguns casos, podemos mudar isso da mesma maneira, mas em outros, existe para ficar.Então, por que o compilador faz isso? Bem, se conseguirmos desenrolar o loop, poderemos vetorizá-lo. Podemos até provar que há apenas constantes sendo adicionadas, o que significa que todo o nosso loop pode desaparecer no ar. Para resumir: tornando os padrões previsíveis (tornando os ramos previsíveis), podemos provar que certas condições se mantêm em nosso loop, o que significa que podemos fazer mágica durante a otimização do JIT.
No entanto, os ramos tendem a quebrar esses bons padrões previsíveis, o que é algo que otimiza, portanto, um desagrado. Quebre, continue, vá - todos eles pretendem quebrar esses padrões previsíveis - e, portanto, não são realmente "agradáveis".
Você também deve perceber, neste ponto, que um simples
foreach
é mais previsível do que um monte degoto
declarações que se espalham por todo o lugar. Em termos de (1) legibilidade e (2) da perspectiva do otimizador, é a melhor solução.Outra coisa que vale a pena mencionar é que é muito relevante para a otimização de compiladores atribuir registros a variáveis (um processo chamado alocação de registros ). Como você deve saber, existe apenas um número finito de registros em sua CPU e eles são de longe os pedaços de memória mais rápidos em seu hardware. As variáveis usadas no código que está no loop mais interno têm mais probabilidade de obter um registro atribuído, enquanto as variáveis fora do loop são menos importantes (porque esse código provavelmente é atingido menos).
Ajuda, muita complexidade ... o que devo fazer?
A conclusão é que você deve sempre usar as construções de linguagem que tem à sua disposição, que geralmente (implicitamente) criam padrões previsíveis para o seu compilador. Tente evitar ramos estranhos se possível (especificamente:
break
,continue
,goto
oureturn
no meio do nada).A boa notícia aqui é 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, que significa Saída única de entrada única.
E agora chegamos à verdadeira questão.
Imagine que você tem algo parecido com isto:
A maneira mais fácil de fazer disso um padrão previsível é simplesmente eliminar
if
completamente:Em outros casos, você também pode dividir o método em 2 métodos:
Variáveis temporárias? Bom, ruim ou feio?
Você pode até decidir retornar um booleano de dentro do loop (mas eu pessoalmente prefiro o formulário SESE, porque é assim que o compilador o verá e acho que é mais fácil de ler).
Algumas pessoas pensam que é mais limpo usar uma variável temporária e propõem uma solução como esta:
Pessoalmente, sou contra essa abordagem. Veja novamente como o código é compilado. Agora pense no que isso fará com esses padrões agradáveis e previsíveis. Obter a foto?
Certo, deixe-me explicar. O que acontecerá é que:
more
variável estranha que só é usada no fluxo de controle.more
será eliminada do programa e apenas as ramificações permanecerão. Essas ramificações serão otimizadas, para que você obtenha apenas uma ramificação fora do loop interno.more
é definitivamente usada no loop mais interno; portanto, se o compilador não a otimizar, há uma grande chance de ser alocado para um registro (que consome uma memória valiosa do registro).Portanto, para resumir: o otimizador em seu compilador enfrentará muitos problemas para descobrir que
more
é usado apenas para o fluxo de controle e, na melhor das hipóteses , o converterá em um único ramo fora do externo para ciclo.Em outras palavras, o melhor cenário é que ele acabe com o equivalente a isso:
Minha opinião pessoal sobre isso é bastante simples: se é isso que pretendemos o tempo todo, vamos facilitar o mundo para o compilador e a legibilidade, e escreva isso imediatamente.
tl; dr:
Bottom line:
goto
ou outrobool more
, prefira o primeiro.fonte
yield return
. Ou seja, embora seja fácil mostrar que existem alguns casos úteis, para a maioria dos códigos de "nível de aplicativo" é provavelmente uma ocorrência muito baixa de frustração com C #, excluindo aqueles "apenas provenientes de" C / C ++ ;-) Também ocasionalmente sinto falta O "lançamento" de Ruby (sem exceção), ocasionalmente, também se encaixa nesse domínio.goto
, não torna seu código mais legível - eu diria o que acontece é exatamente o oposto: na verdade, seu fluxo de controle conterá mais nós - o que é praticamente a definição de complexidade. Apenas evitá-lo para ajudar a autodisciplina parece contraproducente em termos de legibilidade.goto
simplesmente não têm disciplina para não abusar dela.fator em uma função / método e use retorno antecipado ou reorganize seus loops em uma cláusula while. Ir para / exceções / o que quer que certamente não seja apropriado aqui.
fonte
Você pediu uma combinação de rápido, agradável, sem uso de um booleano, sem uso de goto e C #. Você descartou todas as formas possíveis de fazer o que deseja.
A maneira mais rápida e menos feia é usar um goto.
fonte
Às vezes, é bom abstrair o código em sua própria função e depois usar um retorno antecipado - os retornos antecipados são maus:)
fonte
Eu já vi muitos exemplos que usam "break", mas nenhum que usa "continue".
Ainda seria necessário algum tipo de sinalizador no loop interno:
fonte
Desde que vi
break
C pela primeira vez há algumas décadas, esse problema me incomodou. Eu esperava que algum aprimoramento de idioma tivesse uma extensão para quebrar, que funcionaria assim:fonte
Lembro-me dos meus dias de estudante que foi dito que é matematicamente comprovável que você pode fazer qualquer coisa no código sem um goto (ou seja, não há situação em que goto seja a única resposta). Então, eu nunca uso goto's (apenas minha preferência pessoal, não sugerindo que estou certo ou errado)
De qualquer forma, para sair dos loops aninhados, faço algo assim:
... espero que ajude quem gosta de mim é anti-goto "fanboys" :)
fonte
Foi assim que eu fiz. Ainda uma solução alternativa.
fonte
A maneira mais fácil de terminar um loop duplo seria terminar diretamente o primeiro loop
fonte
Os loops podem ser interrompidos usando condições personalizadas no loop, permitindo um código limpo.
Com esse código, obtemos a seguinte saída:
fonte
Uma outra opção é uma função anônima auto-chamada . Sem ir, sem rótulo, sem nova variável, sem novo nome de função usado e uma linha menor que o exemplo do método anônimo na parte superior.
fonte
Lance uma exceção personalizada que sai do loop externo.
Funciona para
for
,foreach
ouwhile
qualquer tipo de loop e qualquer linguagem que usetry catch exception
blockfonte
fonte
Como vejo que você aceitou a resposta na qual a pessoa o encaminha para a declaração goto, onde na programação moderna e na opinião de especialistas goto é um assassino, chamamos isso de assassino na programação, com algumas razões, as quais não discutirei aqui. Neste ponto, mas a solução da sua pergunta é muito simples, você pode usar um sinalizador booleano nesse tipo de cenário, como demonstrarei no meu exemplo:
simples e claro. :)
fonte
Você olhou para a
break
palavra - chave? OoEste é apenas pseudo-código, mas você deve entender o que quero dizer:
Se você pensa em
break
ser uma funçãobreak()
, então seu parâmetro seria o número de loops para interromper. Como estamos no terceiro loop do código aqui, podemos sair dos três.Manual: http://php.net/break
fonte
Eu acho que, a menos que você queira fazer a "coisa booleana", a única solução é realmente jogar. O que você obviamente não deveria fazer ..!
fonte