Y a-t-il des avantages à utiliser cette variable supplémentaire dans l'annotation de boucle for?

11

J'ai trouvé l'annotation de boucle suivante dans un grand projet sur lequel je travaille (pseudocode):

var someOtherArray = [];
for (var i = 0, n = array.length; i < n; i++) {
    someOtherArray[i] = modifyObjetFromArray(array[i]);
}

Ce qui a attiré mon attention, c'est cette variable "n" supplémentaire. Je n'ai jamais vu un for lop écrit de cette façon auparavant.

Évidemment, dans ce scénario, il n'y a aucune raison pour que ce code ne puisse pas être écrit de la manière suivante (ce à quoi je suis très habitué):

var someOtherArray = [];
for (var i = 0; i < array.length; i++) {
    someOtherArray[i] = modifyObjetFromArray(array[i]);
}

Mais il m'a fait réfléchir.

Existe-t-il un scénario où l'écriture d'une telle boucle serait logique? L'idée vient à l'esprit que la longueur du "tableau" peut changer pendant l'exécution de la boucle for, mais nous ne voulons pas boucler plus loin que la taille d'origine, mais je ne peux pas imaginer un tel scénario.

Réduire le tableau à l'intérieur de la boucle n'a pas beaucoup de sens non plus, car nous sommes susceptibles d'obtenir OutOfBoundsException.

Existe-t-il un modèle de conception connu où cette annotation est utile?

Modifier Comme indiqué par @ Jerry101, la raison en est la performance. Voici un lien vers le test de performance que j'ai créé: http://jsperf.com/ninforloop . À mon avis, la différence n'est pas assez grande à moins que vous ne répétiez à travers un très large éventail. Le code à partir duquel je l'ai copié ne comportait que jusqu'à 20 éléments, donc je pense que la lisibilité dans ce cas l'emporte sur la considération des performances.

Vlad Spreys
la source
En réaction à votre modification: il s'agit d'une programmation orientée objet. La longueur de réseau standard est rapide et bon marché. Mais pour une raison quelconque, la mise en œuvre de .length pourrait changer en quelque chose de cher. Si une valeur reste la même, ne l'obtenez pas plusieurs fois.
Pieter B
Je suis d'accord. Il est bon de connaître cette option, mais je ne l'utiliserais probablement pas très souvent, sauf si j'obtiens un exemple similaire à votre requête SQL. Merci
Vlad Spreys
La chose est, si votre tableau est un million d'éléments, vous allez appeler array.length un million de fois au lieu d'une fois tandis que la valeur reste la même. Demander quelque chose un million de fois et savoir que chaque réponse subséquente sera la même que la première réponse ... me semble un peu absurde.
Pieter B
1
Je ne trouve pas que cela rend le code beaucoup plus difficile à lire. (Sérieusement, si quelqu'un a des problèmes avec une boucle aussi simple, ils devraient être inquiets.) Mais: 1) Je serais terriblement surpris si après 4 décennies de langages descendants en C et C, chaque compilateur sérieux ne l'avait pas déjà fait pour vous; et 2) ce n'est peut-être pas un point chaud. Cela ne semble pas valoir la peine de passer 5 secondes supplémentaires à l'analyser à moins qu'il ne soit à l'intérieur d'une bibliothèque (par exemple, la mise en œuvre d'une structure de données.)
Doval
1
En C # /. NET, la variante using npeut être plus lente que la variante using array.Lengthcar le JITter peut ne pas remarquer qu'il pourrait éliminer les vérifications des limites du tableau.
CodesInChaos

Réponses:

27

La variable ngarantit que le code généré ne récupère pas la longueur du tableau pour chaque itération.

Il s'agit d'une optimisation qui peut faire une différence dans l'exécution en fonction de la langue utilisée, que le tableau soit en fait un objet de collection ou un «tableau» JavaScript, et d'autres détails d'optimisation.

Jerry101
la source
3
Non, non. Que la longueur du tableau soit récupérée à chaque itération dépend non seulement du langage, mais aussi du compilateur et des options du compilateur et de ce qui est fait dans la boucle (je parle de C et C ++)
B --овић
.. Et même ainsi, je placerais personnellement l'affectation n juste au-dessus de la boucle si je le voulais vraiment, car - comme OP l'a noté - c'est une construction rare (YMMV) à utiliser et elle est facilement manquée / non comprise par les autres développeurs lisant le code.
cwap
20
Tel qu'il est écrit, il s'étend nà la boucle, ce qui est généralement une bonne chose. Je le définirais avant la boucle uniquement si je voulais le réutiliser plus tard.
Jerry101
4
@ Jerry101: Apparemment, l'extrait d'exemple est JavaScript, où il n'y a pas de portée de boucle…
Bergi
1
@ BЈовић: dans la pratique, le compilateur parvient rarement à prouver que la taille du tableau ne varie pas (en général, cela n'est prouvable que si le vecteur sur lequel vous bouclez est local à la fonction et que vous n'appelez que des fonctions intégrées dans la boucle).
Matteo Italia
2

La récupération de la longueur du tableau peut facilement être «plus coûteuse» que l'action réelle sur laquelle vous effectuez une itération.

Donc, si l'ensemble ne change pas, interrogez la longueur une seule fois.

Pas avec des tableaux, mais avec des jeux d'enregistrements provenant d'un serveur SQL, j'ai vu des améliorations spectaculaires en ne demandant pas le nombre d'enregistrements à chaque itération. (bien sûr, ne le faites que si vous pouvez garantir que votre tableau ou jeu d'enregistrements ne sera pas modifié pendant ce processus).

Pieter B
la source
Si le nombre d'enregistrements change, alors quoi? Supposons qu'il y ait 100 éléments, et pendant que vous traitez l'élément 50, un élément après le numéro 25 est inséré. Ainsi, lorsque vous traitez l'élément # 51, c'est la même chose que # 50 que vous avez déjà traitée, et les choses tournent mal. Le problème n'est donc pas que la longueur change, c'est beaucoup plus omniprésent.
gnasher729
@ gnasher729 une fois que vous adoptez un comportement asymétrique, beaucoup de choses peuvent mal tourner. Mais il y a des choses que vous pouvez faire contre cela comme le démarrage et la transaction sql.
Pieter B
2

L'idée vient à l'esprit que la longueur du "tableau" peut changer pendant l'exécution de la boucle for, mais nous ne voulons pas boucler plus loin que la taille d'origine, mais je ne peux pas imaginer un tel scénario.

Les personnes qui parlent de performances ont probablement raison, c'est pourquoi cela a été écrit de cette façon (la personne qui l'a écrit de cette façon peut ne pas être correcte car cela affecte de manière significative les performances, mais c'est une autre question).

Cependant, pour répondre à cette partie de votre question, je pense que cela se produira probablement lorsque le but principal de la boucle est de modifier le tableau sur lequel elle boucle. Considérez quelque chose comme ça, en pseudo-code:

guests = [];
for (i = 0; i < invitations.length; ++i) {
    if (! invitations[i].is_declined()) {
        guests.append(invitations[i].recipient())
    }
}
// maybe some other stuff to modify the guestlist
for (i = 0, n = guests.length; i < n; ++i) {
    if (guests[i].has_plus_one()) {
        guests.append(guests[i].get_plus_one());
    }
}

Bien sûr, il existe d'autres façons de l'écrire. Dans ce cas, j'aurais pu vérifier les plus en même temps que vérifier si les destinataires acceptaient leurs invitations et produire la liste complète des invités une invitation à la fois. Je ne veux pas nécessairement combiner ces deux opérations, mais je ne peux pas immédiatement penser à une raison pour laquelle c'est définitivement faux et donc cette conception est requise . C'est une option à considérer, à l'occasion.

En fait, je pense que ce qui ne va pas avec ce code, c'est que l'appartenance au gueststableau signifie différentes choses à différents points du code. Par conséquent, je ne l'appellerais certainement pas un "modèle", mais je ne sais pas que c'est assez d'un défaut pour exclure de faire quoi que ce soit de ce genre :-)

Steve Jessop
la source
Le tableau peut également être modifié par un autre thread. Cela empêche le compilateur d'optimiser la récupération de longueur. Donc, en question, le programmeur dit explicitement que le tableau ne change pas même dans d'autres threads.
Basilevs
0

Si possible, vous devez utiliser un itérateur qui traverse tous les éléments du tableau. Vous utilisez le tableau uniquement comme une séquence, pas comme un stockage à accès aléatoire, il serait donc évident qu'un itérateur qui effectue simplement un accès séquentiel serait plus rapide. Imaginez ce que vous pensez qu'un tableau est en fait un arbre binaire, mais quelqu'un a écrit du code qui détermine la "longueur" en comptant les éléments, et du code qui accède au i-ème élément, également en parcourant l'élément, chacun en O (n ). Un itérateur peut être très rapide, votre boucle sera ralentie.

gnasher729
la source