Meu palestrante mencionou hoje que era possível "rotular" loops em Java para que você pudesse se referir a eles ao lidar com loops aninhados. Portanto, procurei o recurso porque não o conhecia e, em muitos lugares em que esse recurso foi explicado, foi seguido por um aviso, desencorajando loops aninhados.
Eu realmente não entendo o porquê? É porque afeta a legibilidade do código? Ou é algo mais "técnico"?
java
readability
loops
DSF
fonte
fonte
Respostas:
Loops aninhados são bons, desde que descrevam o algoritmo correto.
Os loops aninhados têm considerações de desempenho (consulte a resposta de @ Travis-Pesetto), mas às vezes é exatamente o algoritmo correto, por exemplo, quando você precisa acessar todos os valores em uma matriz.
Os loops de rotulagem em Java permitem interromper prematuramente vários loops aninhados quando outras maneiras de fazer isso seriam complicadas. Por exemplo, alguns jogos podem ter um código como este:
Embora código como o exemplo acima às vezes seja a maneira ideal de expressar um determinado algoritmo, geralmente é melhor dividir esse código em funções menores e provavelmente usar em
return
vez debreak
. Portanto, abreak
com um rótulo é um leve cheiro de código ; preste atenção extra ao vê-lo.fonte
Os loops aninhados são frequentemente (mas nem sempre) uma prática ruim, porque são frequentemente (mas nem sempre) um exagero no que você está tentando fazer. Em muitos casos, há uma maneira muito mais rápida e menos dispendiosa de atingir a meta que você está tentando alcançar.
Por exemplo, se você possui 100 itens na lista A e 100 itens na lista B e sabe que para cada item da lista A há um item na lista B que corresponde a ele (com a definição de "correspondência" deixada deliberadamente obscura aqui) e você deseja produzir uma lista de pares, a maneira mais simples de fazer isso é assim:
Com 100 itens em cada lista, isso levará em média 100 * 100/2 (5.000)
matches
operações. Com mais itens, ou se a correlação 1: 1 não for garantida, ela se tornará ainda mais cara.Por outro lado, há uma maneira muito mais rápida de executar uma operação como esta:
Se você fizer dessa maneira, em vez do número de
matches
operações baseadaslength(A) * length(B)
, agora será baseadolength(A) + length(B)
, o que significa que seu código será executado muito mais rápido.fonte
O(n log n)
duas vezes, se o Quicksort for usado.O(n^2)
para valores não-pequenas de N.<
operador, que geralmente não podem ser derivados domatches
operador. Em segundo lugar, mesmo que X e Y sejam numéricos, o segundo algoritmo ainda pode produzir resultados errados, por exemplo, quandoX matches Y
éX + Y == 100
.Uma razão para evitar aninhar loops é porque é uma má idéia aninhar estruturas de bloco muito profundamente, independentemente de serem ou não loops.
Cada função ou método deve ser fácil de entender, tanto seu objetivo (o nome deve expressar o que faz) quanto para os mantenedores (deve ser fácil entender os internos). Se uma função é muito complicada para entender com facilidade, isso geralmente significa que alguns dos internos devem ser fatorados em funções separadas para que possam ser referidos na função principal (agora menor) por nome.
Os loops aninhados podem ficar difíceis de entender de maneira relativamente rápida, embora alguns aninhamentos de loops sejam bons - desde que, como outros apontam, isso não signifique que você esteja criando um problema de desempenho usando um algoritmo lento extremamente (e desnecessariamente).
Na verdade, você não precisa de loops aninhados para obter limites de desempenho absurdamente lentos. Considere, por exemplo, um único loop que, em cada iteração, retira um item de uma fila e, em seguida, retira vários itens - por exemplo, a busca pela primeira vez em um labirinto. O desempenho não é decidido pela profundidade do aninhamento do loop (que é apenas 1), mas pelo número de itens que são colocados nessa fila antes de acabar eventualmente ( se algum dia esgotar) - quão grande é a parte alcançável do labirinto é.
fonte
Dado o caso de muitos loops aninhados, você acaba com o tempo polinomial. Por exemplo, dado este pseudo-código:
Isso seria considerado tempo O (n ^ 2), que seria um gráfico semelhante a:
Onde o eixo y é a quantidade de tempo que seu programa leva para terminar e o eixo x é a quantidade de dados.
Se você obtiver muitos dados, seu programa será tão lento que ninguém os esperará. e não são tantas as 1.000 entradas de dados que acredito que levem muito tempo.
fonte
Dirigir um caminhão de trinta toneladas em vez de um veículo pequeno de passageiros é uma má prática. Exceto quando você precisa transportar 20 ou 30 toneladas de material.
Quando você usa um loop aninhado, não é uma prática ruim. Ou é totalmente estúpido, ou é exatamente o que é necessário. Você decide.
No entanto, alguém se queixou de rotular loops. A resposta para isso: se você precisar fazer a pergunta, não use etiquetas. Se você souber o suficiente para se decidir, então se decide.
fonte
Não há nada inerentemente errado ou necessariamente ruim nos loops aninhados. No entanto, eles têm certas considerações e armadilhas.
Os artigos aos quais você foi direcionado, provavelmente em nome da brevidade ou devido a um processo psicológico conhecido como sendo queimado, pulando os detalhes.
Ser queimado é quando você tem uma experiência negativa de algo com o ser implicante e evita-o. Por exemplo, eu poderia cortar legumes com uma faca afiada e me cortar. Eu poderia então dizer que facas afiadas são ruins, não as use para cortar legumes para tentar tornar impossível que essa experiência ruim aconteça novamente. Isso é obviamente muito impraticável. Na realidade, você só precisa ter cuidado. Se você está dizendo para outra pessoa cortar legumes, você tem uma sensação ainda mais forte disso. Se eu estivesse instruindo as crianças a cortarem legumes, sentiria muito forte que lhes dissessem para não usarem uma faca afiada, especialmente se não puder supervisioná-las de perto.
O problema da programação é que você não alcançará o pico de eficiência se sempre preferir segurança primeiro. Nesse caso, as crianças só podem cortar legumes macios. Confrontado com qualquer outra coisa e eles só vão fazer uma bagunça usando uma faca sem corte. É importante aprender o uso adequado de loops, incluindo loops aninhados, e você não pode fazer isso se eles forem considerados ruins e você nunca tentar usá-los.
Como muitas respostas aqui apontam que um loop aninhado é uma indicação das características de desempenho do seu programa que podem ficar exponencialmente piores a cada aninhamento. Ou seja, O (n), O (n ^ 2), O (n ^ 3) e assim por diante, que compreende O (n ^ profundidade), em que profundidade representa quantos ciclos você aninhou. À medida que o seu aninhamento cresce, o tempo necessário aumenta exponencialmente. O problema é que não é uma certeza que sua complexidade de tempo ou espaço seja essa (muitas vezes a * b * c, mas nem todos os loops de ninho podem ser executados o tempo todo) nem é uma certeza que você tem um problema de desempenho mesmo que seja isso.
Para muitas pessoas, especialmente estudantes, escritores e professores que, para ser franco, raramente programa para viver ou diariamente para loops também pode ser algo com o qual não está acostumado e que induziu muita carga cognitiva nos primeiros encontros. Esse é um aspecto problemático, porque sempre há uma curva de aprendizado e evitá-la não será eficaz na conversão de alunos em programadores.
Loops aninhados podem enlouquecer, ou seja, podem acabar aninhados muito profundamente. Se eu percorrer cada continente, depois cada país, depois cada cidade, depois cada loja, depois cada prateleira e cada produto, se for uma lata de feijão através de cada feijão e medir seu tamanho para obter a média, então você pode ver que vai aninhar muito profundamente. Você terá uma pirâmide e muito espaço desperdiçado longe da margem esquerda. Você pode até acabar saindo da página.
Este é um problema que seria mais significativo historicamente onde as telas eram pequenas e de baixa resolução. Nesses casos, mesmo alguns níveis de aninhamento poderiam realmente ocupar muito espaço. Hoje, essa é uma preocupação menor, em que o limite é mais alto, embora ainda possa apresentar um problema se houver aninhamento suficiente.
Relacionado é o argumento da estética. Muitas pessoas não acham aninhadas para loops esteticamente agradáveis, em contraste com layouts com alinhamento mais consistente, isso pode ou não estar relacionado ao que as pessoas estão acostumadas, rastreamento ocular e outras preocupações. No entanto, é problemático, pois tende a se auto-reforçar e, finalmente, pode dificultar a leitura do código, pois quebra um bloco de código e encapsula loops atrás de abstrações, como funções, também corre o risco de quebrar o mapeamento do código para o fluxo de execução.
Há uma tendência natural para o que as pessoas estão acostumadas. Se você estiver programando algo da maneira mais simples, a probabilidade de não precisar de aninhamento é maior, a probabilidade de precisar de um nível diminui em uma ordem de magnitude, a probabilidade de outro nível diminui novamente. A frequência diminuindo e, essencialmente, significando que quanto mais profundo o ninho, menos treinados os sentidos humanos são para antecipá-lo.
Relacionado a isso é que, em qualquer construção complexa, na qual um loop aninhado pode ser considerado, você deve sempre perguntar: a solução mais simples possível, pois há potencial para uma solução perdida que precisa de menos loops. A ironia é que uma solução aninhada costuma ser a maneira mais simples de produzir algo que funcione com a quantidade mínima de esforço, complexidade e carga cognitiva. Geralmente, é natural aninhar-se em loops. Se você considerar, por exemplo, uma das respostas acima, em que a maneira mais rápida que um loop for aninhado também é muito mais complexa e consiste em significativamente mais código.
É necessário muito cuidado, pois muitas vezes é possível abstrair os loops ou achatá-los, mas o resultado final acaba sendo uma cura pior do que a doença, principalmente se você não estiver, por exemplo, recebendo um aprimoramento de desempenho mensurável e significativo do esforço.
É muito comum as pessoas experimentarem problemas de desempenho frequentemente associados a loops que estão dizendo ao computador para repetir uma ação muitas vezes e, inerentemente, frequentemente estão envolvidos em gargalos de desempenho. Infelizmente, as respostas a isso podem ser muito superficiais. Torna-se comum que as pessoas vejam um loop e vejam um problema de desempenho onde não há nenhum e depois ocultem o loop da vista, sem nenhum efeito real. O código "parece" rápido, mas o coloca na estrada, aciona a ignição, pisca no acelerador e dá uma olhada no velocímetro e você pode achar que ele ainda é tão rápido quanto uma velhinha andando em seu zimmer.
Esse tipo de ocultação é semelhante a se você tiver dez ladrões no seu caminho. Se, em vez de seguir um caminho direto para onde você deseja ir, você o organizar de forma que haja um assaltante atrás de cada esquina, isso dará a ilusão de que, ao iniciar sua jornada, não haverá assaltantes. Fora da vista, longe da mente. você ainda será assaltado dez vezes, mas agora não verá isso chegando.
A resposta para sua pergunta é que são as duas coisas, mas nenhuma das preocupações é absoluta. Eles são inteiramente subjetivos ou apenas contextualmente objetivos. Infelizmente, às vezes, a opinião inteiramente subjetiva ou melhor, tem precedentes e domina.
Como regra geral, se precisar de um loop aninhado ou parecer o próximo passo óbvio, é melhor não deliberar e simplesmente fazê-lo. No entanto, se houver alguma dúvida, deve ser revisado posteriormente.
Outra regra prática é que você deve sempre verificar a cardinalidade e se perguntar se esse loop será um problema. No meu exemplo anterior, passei pelas cidades. Para os testes, posso passar por apenas dez cidades, mas qual é o número máximo razoável de cidades que se pode esperar no uso no mundo real? Eu poderia então multiplicar o mesmo para os continentes. É uma regra prática sempre considerar com loops, especialmente que iteram uma quantidade dinâmica (variável) de vezes o que isso pode traduzir para baixo da linha.
Independentemente, sempre faça o que funciona primeiro. Da maneira que você vê uma oportunidade de otimização, você pode comparar sua solução otimizada com a mais fácil de começar a trabalhar e confirmar que ela produziu os benefícios esperados. Você também pode gastar muito tempo otimizando prematuramente antes que as medições sejam realizadas, o que leva ao YAGNI ou a muito desperdício de tempo e prazos perdidos.
fonte
n
, é geométrica ou polinomial .