Mon conférencier a mentionné aujourd'hui qu'il était possible de "étiqueter" les boucles dans Java afin que vous puissiez vous y référer lorsque vous traitez des boucles imbriquées. J'ai donc recherché la fonctionnalité car je ne connaissais pas et de nombreux endroits où cette fonctionnalité a été expliquée, elle a été suivie d'un avertissement, décourageant les boucles imbriquées.
Je ne comprends pas vraiment pourquoi? Est-ce parce que cela affecte la lisibilité du code? Ou est-ce quelque chose de plus "technique"?
Les boucles imbriquées sont correctes tant qu'elles décrivent l'algorithme correct.
Les boucles imbriquées ont des considérations de performances (voir la réponse de @ Travis-Pesetto), mais parfois c'est exactement l'algorithme correct, par ex. lorsque vous devez accéder à toutes les valeurs d'une matrice.
L'étiquetage des boucles dans Java permet de rompre prématurément plusieurs boucles imbriquées lorsque d'autres façons de le faire seraient lourdes. Par exemple, certains jeux peuvent avoir un morceau de code comme celui-ci:
Player chosen_one = null;
...
outer: // this is a label
for (Player player : party.getPlayers()) {
for (Cell cell : player.getVisibleMapCells()) {
for (Item artefact : cell.getItemsOnTheFloor())
if (artefact == HOLY_GRAIL) {
chosen_one = player;
break outer; // everyone stop looking, we found it
}
}
}
Alors que du code comme l'exemple ci-dessus peut parfois être le moyen optimal d'exprimer un certain algorithme, il est généralement préférable de diviser ce code en fonctions plus petites et d'utiliser probablement return
au lieu de break
. Donc un break
avec une étiquette est un faible odeur de code ; faites très attention lorsque vous le voyez.
Les boucles imbriquées sont souvent (mais pas toujours) de mauvaises pratiques, car elles sont souvent (mais pas toujours) excessives pour ce que vous essayez de faire. Dans de nombreux cas, il existe un moyen beaucoup plus rapide et moins inutile d'atteindre l'objectif que vous essayez d'atteindre.
Par exemple, si vous avez 100 éléments dans la liste A et 100 éléments dans la liste B, et que vous savez que pour chaque élément de la liste A, il y a un élément dans la liste B qui lui correspond (avec la définition de "correspondance" laissée délibérément obscure) ici), et vous voulez produire une liste de paires, la façon simple de le faire est la suivante:
for each item X in list A:
for each item Y in list B:
if X matches Y then
add (X, Y) to results
break
Avec 100 éléments dans chaque liste, cela prendra en moyenne 100 * 100/2 (5 000) matches
opérations. Avec plus d'articles, ou si la corrélation 1: 1 n'est pas assurée, elle devient encore plus chère.
D'un autre côté, il existe un moyen beaucoup plus rapide d'effectuer une opération comme celle-ci:
sort list A
sort list B (according to the same sort order)
I = 0
J = 0
repeat
X = A[I]
Y = B[J]
if X matches Y then
add (X, Y) to results
increment I
increment J
else if X < Y then
increment I
else increment J
until either index reaches the end of its list
Si vous le faites de cette façon, au lieu que le nombre d'opérations matches
soit basé sur length(A) * length(B)
, il est maintenant basé sur length(A) + length(B)
, ce qui signifie que votre code s'exécutera beaucoup plus rapidement .
Une raison pour éviter d'imbriquer des boucles est parce que c'est une mauvaise idée d'imbriquer les structures de bloc trop profondément, qu'elles soient ou non en boucle.
Chaque fonction ou méthode doit être facile à comprendre, à la fois son objectif (le nom doit exprimer ce qu'elle fait) et pour les responsables (il doit être facile de comprendre les éléments internes). Si une fonction est trop compliquée pour être facilement comprise, cela signifie généralement que certains des éléments internes doivent être pris en compte dans des fonctions distinctes afin de pouvoir être désignés par leur nom dans la fonction principale (désormais plus petite).
Les boucles imbriquées peuvent devenir difficiles à comprendre relativement rapidement, bien que certaines imbrications de boucles soient correctes - à condition, comme d'autres le soulignent, que cela ne signifie pas que vous créez un problème de performances en utilisant un algorithme extrêmement (et inutilement) lent.
En fait, vous n'avez pas besoin de boucles imbriquées pour obtenir des limites de performances absurdement lentes. Considérons, par exemple, une boucle unique qui, à chaque itération, prend un élément d'une file d'attente, puis en remet éventuellement plusieurs - par exemple recherche en profondeur d'un labyrinthe. Les performances ne sont pas déterminées par la profondeur d'imbrication de la boucle (qui n'est que de 1) mais par le nombre d'éléments placés dans cette file d'attente avant qu'elle ne soit finalement épuisée (si elle est jamais épuisée) - la taille de la partie accessible du labyrinthe.
Étant donné le cas de nombreuses boucles imbriquées, vous obtenez un temps polynomial. Par exemple, étant donné ce pseudo-code:
set i equal to 1
while i is not equal to 100
increment i
set j equal to 1
while j is not equal to i
increment j
end
end
Cela serait considéré comme un temps O (n ^ 2) qui serait un graphique similaire à:
L'axe des y correspond au temps nécessaire à votre programme pour se terminer et l'axe des x à la quantité de données.
Si vous obtenez trop de données, votre programme sera si lent que personne ne l'attendra. et ce n'est pas tant que 1000 entrées de données, je crois, prendront trop de temps.
Conduire un camion de trente tonnes au lieu d'un petit véhicule de tourisme est une mauvaise pratique. Sauf lorsque vous devez transporter 20 ou 30 tonnes de trucs.
Lorsque vous utilisez une boucle imbriquée, ce n'est pas une mauvaise pratique. C'est complètement stupide, ou c'est exactement ce dont on a besoin. Tu décides.
Cependant, quelqu'un s'est plaint de étiquetage boucles. La réponse à cela: si vous devez poser la question, n'utilisez pas d'étiquetage. Si vous en savez assez pour vous décider, alors vous décidez vous-même.