web-dev-qa-db-fra.com

Comment écrire des boucles correctes?

La plupart du temps, lors de l'écriture de boucles, j'écris généralement des conditions aux limites erronées (par exemple: mauvais résultat) ou mes hypothèses sur les terminaisons de boucle sont erronées (par exemple: boucle infinie). Bien que mes hypothèses soient correctes après quelques essais et erreurs, je suis devenu trop frustré à cause du manque de modèle informatique correct dans ma tête.

/**
 * Inserts the given value in proper position in the sorted subarray i.e. 
 * array[0...rightIndex] is the sorted subarray, on inserting a new value 
 * our new sorted subarray becomes array[0...rightIndex+1].
 * @param array The whole array whose initial elements [0...rightIndex] are 
 * sorted.
 * @param rightIndex The index till which sub array is sorted.
 * @param value The value to be inserted into sorted sub array.
 */
function insert(array, rightIndex, value) {
    for(var j = rightIndex; j >= 0 && array[j] > value; j--) {
        array[j + 1] = array[j];
    }   
    array[j + 1] = value; 
};

Les erreurs que j'ai commises au départ étaient:

  1. Au lieu de j> = 0, je l'ai gardé j> 0.
  2. Vous ne savez pas si tableau [j + 1] = valeur ou tableau [j] = valeur.

Quels sont les outils/modèles mentaux pour éviter de telles erreurs?

64
CodeYogi

Tester

Non, sérieusement, teste.

Je code depuis plus de 20 ans et je n'ai toujours pas confiance en moi pour écrire une boucle correctement la première fois. J'écris et exécute des tests qui prouvent que cela fonctionne avant de soupçonner que cela fonctionne. Testez chaque côté de chaque condition aux limites. Par exemple, un rightIndex de 0 devrait faire quoi? Que diriez-vous -1?

Rester simple

Si les autres ne peuvent pas voir ce qu'il fait en un coup d'œil, vous le rendez trop difficile. N'hésitez pas à ignorer les performances si cela signifie que vous pouvez écrire quelque chose de facile à comprendre. Rendez-le plus rapide seulement dans le cas improbable où vous en avez vraiment besoin. Et même alors seulement une fois que vous êtes absolument sûr de savoir exactement ce qui vous ralentit. Si vous pouvez réellement améliorer Big O , cette activité peut ne pas être inutile, mais même alors, rendez votre code aussi lisible que possible.

Off par un

Connaissez la différence entre compter vos doigts et compter les espaces entre vos doigts. Parfois, les espaces sont ce qui est réellement important. Ne laissez pas vos doigts vous distraire. Sachez si votre pouce est un doigt. Sachez si l'écart entre votre petit doigt et votre pouce compte comme un espace.

Commentaires

Avant de vous perdre dans le code, essayez de dire ce que vous voulez dire en anglais. Énoncez clairement vos attentes. N'expliquez pas comment fonctionne le code. Expliquez pourquoi vous le faites faire ce qu'il fait. Gardez-en les détails d'implémentation. Il devrait être possible de refactoriser le code sans avoir à modifier le commentaire.

Le meilleur commentaire est un bon nom.

Si vous pouvez dire tout ce que vous avez à dire avec un bon nom, ne le dites pas encore avec un commentaire.

Abstractions

Les objets, fonctions, tableaux et variables sont tous des abstractions qui ne valent que le nom qui leur est donné. Donnez-leur des noms qui garantissent que lorsque les gens regardent en eux, ils ne seront pas surpris par ce qu'ils trouveront.

Noms courts

Utilisez des noms courts pour des choses de courte durée. i est un nom fin pour un index dans une belle boucle serrée dans une petite portée qui rend sa signification évidente. Si i vit assez longtemps pour s'étaler ligne après ligne avec d'autres idées et noms pouvant être confondus avec i alors il est temps de donner à i un joli long nom explicatif .

Noms longs

Ne raccourcissez jamais un nom simplement pour des raisons de longueur de ligne. Trouvez une autre façon de présenter votre code.

Espace blanc

Les défauts aiment se cacher dans un code illisible. Si votre langue vous permet de choisir votre style de retrait au moins, soyez cohérent. Ne faites pas ressembler votre code à un flux de bruit. Le code devrait ressembler à une marche en formation.

Constructions de boucle

Apprenez et passez en revue les structures de boucles dans votre langue. Regarder un débogueur mettre en évidence une boucle for(;;) peut être très instructif. Apprenez tous les formulaires. while, do while, while(true), for each. Utilisez le plus simple avec lequel vous pouvez vous en sortir. Cherchez amorçant la pompe . Découvrez ce que break et continue font si vous en avez. Connaissez la différence entre c++ Et ++c. N'ayez pas peur de revenir tôt tant que vous fermez toujours tout ce qui doit être fermé. Enfin bloque ou de préférence quelque chose qui le marque pour la fermeture automatique lorsque vous l'ouvrez: tilisation de l'instruction / Essayez avec les ressources .

Alternatives de boucle

Laissez quelque chose d'autre faire la boucle si vous le pouvez. C'est plus agréable pour les yeux et déjà débogué. Celles-ci se présentent sous plusieurs formes: collections ou flux qui autorisent map(), reduce(), foreach(), et d'autres méthodes de ce type qui appliquent un lambda. Recherchez des fonctions spéciales comme Arrays.fill(). Il existe également une récursivité, mais attendez-vous à ce que cela soit facile dans des cas particuliers. N'utilisez généralement pas la récursivité jusqu'à ce que vous voyiez à quoi ressemblerait l'alternative.

Oh, et teste.

Test, test, test.

Ai-je mentionné des tests?

Il y avait encore une chose. Je ne m'en souviens pas. Commencé avec un T ...

206
candied_orange

Lors de la programmation, il est utile de penser à:

et lorsque vous explorez un territoire inexploré (comme jongler avec des indices), cela peut être très, très, utile pour non seulement y penser, mais les rendre explicites dans le code avec assertions .

Prenons votre code d'origine:

/**
 * Inserts the given value in proper position in the sorted subarray i.e. 
 * array[0...rightIndex] is the sorted subarray, on inserting a new value 
 * our new sorted subarray becomes array[0...rightIndex+1].
 * @param array The whole array whose initial elements [0...rightIndex] are 
 * sorted.
 * @param rightIndex The index till which sub array is sorted.
 * @param value The value to be inserted into sorted sub array.
 */
function insert(array, rightIndex, value) {
    for(var j = rightIndex; j >= 0 && array[j] > value; j--) {
        array[j + 1] = array[j];
    }   
    array[j + 1] = value; 
};

Et vérifiez ce que nous avons:

  • pré-condition: array[0..rightIndex] est trié
  • post-condition: array[0..rightIndex+1] est trié
  • invariant: 0 <= j <= rightIndex mais cela semble un peu redondant; ou comme @Jules l'a noté dans les commentaires, à la fin d'un "round", for n in [j, rightIndex+1] => array[j] > value.
  • invariant: à la fin d'un "round", array[0..rightIndex+1] est trié

Vous pouvez donc d'abord écrire un is_sorted ainsi qu'une fonction min travaillant sur une tranche de tableau, puis affirmer:

function insert(array, rightIndex, value) {
    assert(is_sorted(array[0..rightIndex]));

    for(var j = rightIndex; j >= 0 && array[j] > value; j--) {
        array[j + 1] = array[j];

        assert(min(array[j..rightIndex+1]) > value);
        assert(is_sorted(array[0..rightIndex+1]));
    }   
    array[j + 1] = value; 

    assert(is_sorted(array[0..rightIndex+1]));
};

Il y a aussi le fait que votre condition de boucle est un peu compliquée; vous voudrez peut-être vous faciliter la tâche en divisant les choses:

function insert(array, rightIndex, value) {
    assert(is_sorted(array[0..rightIndex]));

    for (var j = rightIndex; j >= 0; j--) {
        if (array[j] <= value) { break; }

        array[j + 1] = array[j];

        assert(min(array[j..rightIndex+1]) > value);
        assert(is_sorted(array[0..rightIndex+1]));
    }   
    array[j + 1] = value; 

    assert(is_sorted(array[0..rightIndex+1]));
};

Maintenant, la boucle est simple (j va de rightIndex à 0).

Enfin, cela doit maintenant être testé:

  • pensez aux conditions aux limites (rightIndex == 0, rightIndex == array.size - 2)
  • pensez à value étant plus petit que array[0] ou plus grand que array[rightIndex]
  • pensez à value étant égal à array[0], array[rightIndex] ou un indice intermédiaire

De plus, ne sous-estimez pas fuzzing. Vous avez des assertions en place pour détecter les erreurs, alors générez un tableau aléatoire et triez-le en utilisant votre méthode. Si une assertion se déclenche, vous avez trouvé un bogue et pouvez étendre votre suite de tests.

73
Matthieu M.

Utiliser les tests unitaires/TDD

Si vous avez vraiment besoin d'accéder à des séquences via une boucle for, vous pouvez éviter les erreurs via tests unitaires, et surtout développement piloté par les tests.

Imaginez que vous ayez besoin d'implémenter une méthode qui prend les valeurs supérieures à zéro, dans l'ordre inverse. À quels cas de test pourriez-vous penser?

  1. Une séquence contient une valeur supérieure à zéro.

    Réel: [5]. Attendu: [5].

    L'implémentation la plus simple qui satisfait aux exigences consiste à renvoyer simplement la séquence source à l'appelant.

  2. Une séquence contient deux valeurs, toutes deux supérieures à zéro.

    Réel: [5, 7]. Attendu: [7, 5].

    Maintenant, vous ne pouvez pas simplement renvoyer la séquence, mais vous devez l'inverser. Souhaitez-vous utiliser une boucle for (;;), une autre construction de langage ou une méthode de bibliothèque n'a pas d'importance.

  3. Une séquence contient trois valeurs, une étant zéro.

    Réel: [5, 0, 7]. Attendu: [7, 5].

    Vous devez maintenant modifier le code pour filtrer les valeurs. Encore une fois, cela peut être exprimé par une instruction if ou un appel à votre méthode de framework préférée.

  4. Selon votre algorithme (comme il s'agit d'un test en boîte blanche, l'implémentation est importante), vous devrez peut-être gérer spécifiquement le cas de la séquence vide [] → [], Ou peut-être pas. Ou vous pouvez vous assurer que le cas Edge où toutes les valeurs sont négatives [-4, 0, -5, 0] → [] Est géré correctement, ou même que les valeurs négatives des limites sont: [6, 4, -1] → [4, 6]; [-1, 6, 4] → [4, 6]. Dans de nombreux cas, cependant, vous n'aurez que les trois tests décrits ci-dessus: tout test supplémentaire ne vous obligera pas à modifier votre code, et ne serait donc pas pertinent.

Travailler à un niveau d'abstraction plus élevé

Cependant, dans de nombreux cas, vous pouvez éviter la plupart de ces erreurs en travaillant à un niveau d'abstraction plus élevé, en utilisant les bibliothèques/frameworks existants. Ces bibliothèques/frameworks permettent de rétablir, trier, diviser et joindre les séquences, d'insérer ou de supprimer des valeurs dans des tableaux ou des listes à double liaison, etc.

Habituellement, foreach peut être utilisé à la place de for, ce qui rend la vérification des conditions aux limites non pertinente: la langue le fait pour vous. Certains langages, comme Python, n'ont même pas la construction for (;;), mais seulement for ... in ....

En C #, LINQ est particulièrement pratique lorsque vous travaillez avec des séquences.

var result = source.Skip(5).TakeWhile(c => c > 0);

est beaucoup plus lisible et moins sujet aux erreurs que sa variante for:

for (int i = 5; i < source.Length; i++)
{
    var value = source[i];
    if (value <= 0)
    {
        break;
    }

    yield return value;
}
28
Arseni Mourzenko

Je suis d'accord avec d'autres personnes qui disent tester votre code. Cependant, il est également agréable de bien faire les choses en premier lieu. J'ai souvent tendance à avoir des conditions aux limites erronées, j'ai donc développé des astuces mentales pour éviter de tels problèmes.

Avec un tableau indexé 0, vos conditions normales vont être:

for (int i = 0; i < length; i++)

ou

for (int i = length - 1; i >= 0; i--)

Ces modèles devraient devenir une seconde nature, vous ne devriez pas avoir à y penser du tout.

Mais tout ne suit pas exactement ce schéma. Donc, si vous n'êtes pas sûr d'avoir bien écrit, voici votre prochaine étape:

Branchez des valeurs et évaluez le code dans votre propre cerveau. Faites en sorte qu'il soit aussi simple que possible de réfléchir. Que se passe-t-il si les valeurs pertinentes sont 0? Que se passe-t-il s'ils sont 1s?

for(var j = rightIndex; j >= 0 && array[j] > value; j--) {
    array[j + 1] = array[j];
}   
array[j + 1] = value;

Dans votre exemple, vous n'êtes pas sûr que ce soit [j] = valeur ou [j + 1] = valeur. Il est temps de commencer à l'évaluer manuellement:

Que se passe-t-il si vous avez une longueur de tableau 0? La réponse devient évidente: rightIndex doit être (longueur - 1) == -1, donc j commence à -1, donc pour insérer à l'index 0, vous devez ajouter 1.

Nous avons donc prouvé que la condition finale était correcte, mais pas l'intérieur de la boucle.

Que se passe-t-il si vous avez un tableau avec 1 élément, 10, et que nous essayons d'insérer un 5? Avec un seul élément, rightIndex devrait commencer à 0. Donc la première fois dans la boucle, j = 0, donc "0> = 0 && 10> 5". Puisque nous voulons insérer le 5 à l'index 0, le 10 doit être déplacé vers l'index 1, donc tableau [1] = tableau [0]. Puisque cela se produit lorsque j vaut 0, tableau [j + 1] = tableau [j + 0].

Si vous essayez d'imaginer un grand tableau et ce qui se passe en s'insérant dans un endroit arbitraire, votre cerveau sera probablement submergé. Mais si vous vous en tenez à des exemples simples de taille 0/1/2, il devrait être facile de faire un rapide examen mental et de voir où vos conditions aux limites se dégradent.

Imaginez que vous n'ayez jamais entendu parler du problème des poteaux de clôture auparavant et je vous dis que j'ai 100 poteaux de clôture en ligne droite, combien de segments entre eux. Si vous essayez d'imaginer 100 poteaux de clôture dans votre tête, vous allez juste être submergé. Alors, quel est le moins de poteaux de clôture pour faire une clôture valide? Vous avez besoin de 2 pour faire une clôture, alors imaginez 2 messages, et l'image mentale d'un seul segment entre les messages le rend très clair. Vous n'avez pas à vous asseoir là à compter les messages et les segments, car vous avez fait du problème quelque chose d'intuitif pour votre cerveau.

Une fois que vous pensez que c'est correct, il est alors bon de le faire passer un test et de vous assurer que l'ordinateur fait ce que vous pensez qu'il devrait, mais à ce stade, cela ne devrait être qu'une formalité.

15
Bryce Wagner

Je suis devenu trop frustré à cause du manque de modèle informatique correct dans ma tête.

Est un point très intéressant à cette question et il a généré ce commentaire: -

Il n'y a qu'une seule façon: mieux comprendre votre problème. Mais c'est aussi général que votre question. - Thomas Junk

... et Thomas a raison. Ne pas avoir l'intention claire d'une fonction devrait être un drapeau rouge - une indication claire que vous devez immédiatement ARRÊTER, saisir un crayon et du papier, vous éloigner de l'IDE et décomposer correctement le problème; ou tout au moins, vérifiez ce que vous avez fait.

J'ai vu tellement de fonctions et de classes qui sont devenues un gâchis complet parce que les auteurs ont tenté de définir l'implémentation avant d'avoir complètement défini le problème. Et c'est si facile à gérer.

Si vous ne comprenez pas complètement le problème, il est également peu probable que vous codiez la solution optimale (soit en termes d'efficacité ou de clarté), ni ne pourrez-vous créer des tests unitaires véritablement utiles dans une méthodologie TDD.

Prenez votre code ici à titre d'exemple, il contient un certain nombre de défauts potentiels que vous n'avez pas encore pris en compte par exemple: -

  • que faire si rightIndex est trop bas? (indice: il va entraîner une perte de données)
  • que faire si rightIndex est en dehors des limites du tableau? (obtiendrez-vous une exception, ou venez-vous de créer vous-même un débordement de tampon?)

Il y a quelques autres problèmes liés aux performances et à la conception du code ...

  • ce code devra-t-il évoluer? Garder le tableau trié est-il la meilleure option, ou devriez-vous regarder d'autres options (comme une liste chaînée?)
  • pouvez-vous être sûr de vos hypothèses? (pouvez-vous garantir que le tableau soit trié, et si ce n'est pas le cas?)
  • réinventez-vous la roue? Les tableaux triés sont un problème bien connu, avez-vous étudié les solutions existantes? Existe-t-il une solution déjà disponible dans votre langue (telle que SortedList<t> En C #)?
  • devez-vous copier manuellement une entrée de tableau à la fois? ou votre langage propose-t-il des fonctions communes comme la Array.Insert(...) de JScript? ce code serait-il plus clair?

Il existe de nombreuses façons d'améliorer ce code, mais jusqu'à ce que vous ayez correctement défini ce que vous devez faire, vous ne développez pas de code, vous le piratez simplement dans l'espoir qu'il fonctionnera. Investissez-y du temps et votre vie deviendra plus facile.

11
James Snell

Erreurs au coup par coup sont l'une des erreurs de programmation les plus courantes. Même les développeurs expérimentés se trompent parfois. Les langages de niveau supérieur ont généralement des constructions d'itération comme foreach ou map, ce qui évite complètement l'indexation explicite. Mais parfois, vous avez besoin d'une indexation explicite, comme dans votre exemple.

Le défi est de savoir comment penser aux gammes des cellules du tableau. Sans modèle mental clair, il devient difficile de savoir quand inclure ou exclure les points finaux.

Lors de la description des plages de tableaux, la convention consiste à inclure la borne inférieure, exclure la borne supérieure . Par exemple, la plage 0..3 correspond aux cellules 0,1,2. Cette convention est utilisée dans toutes les langues indexées sur 0, par exemple la méthode slice(start, end) en JavaScript renvoie le sous-tableau commençant par index start jusqu'à mais sans inclure index end.

C'est plus clair quand vous pensez aux index de plage comme décrivant les bords entre les cellules du tableau. L'illustration ci-dessous est un tableau de longueur 9, et les nombres sous les cellules sont alignés sur les bords, et c'est ce qui est utilisé pour décrire les segments du tableau. Par exemple. il est clair d'après l'illustration que la plage 2..5 correspond aux cellules 2,3,4.

┌───┬───┬───┬───┬───┬───┬───┬───┬───┐
│ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │   -- cell indexes, e.g array[3]
└───┴───┴───┴───┴───┴───┴───┴───┴───┘
0   1   2   3   4   5   6   7   8   9   -- segment bounds, e.g. slice(2,5) 
        └───────────┘ 
          range 2..5

Ce modèle est cohérent avec la longueur du tableau comme limite supérieure d'un tableau. Un tableau de longueur 5 a des cellules 0..5, ce qui signifie qu'il y a cinq cellules 0,1,2,3,4. Cela signifie également que la longueur d'un segment est la borne supérieure moins la borne inférieure, c'est-à-dire que le segment 2..5 a 5-2 = 3 cellules.

Garder ce modèle à l'esprit lors de l'itération vers le haut ou vers le bas rend beaucoup plus clair le moment d'inclure ou d'exclure les points finaux. Lors de l'itération vers le haut, vous devez inclure le point de départ mais exclure le point de fin. Lors de l'itération vers le bas, vous devez exclure le point de départ (la borne supérieure) mais inclure le point de fin (la borne inférieure).

Étant donné que vous itérez vers le bas dans votre code, vous devez inclure la borne inférieure, 0, afin de répéter tout en j >= 0.

Compte tenu de cela, votre choix d'avoir l'argument rightIndex représente le dernier index dans le sous-tableau rompt la convention. Cela signifie que vous devez inclure les deux points de terminaison (0 et rightIndex) dans l'itération. Cela rend également difficile la représentation du segment vide (dont vous avez besoin lorsque vous commencez le tri). Vous devez en fait utiliser -1 comme rightIndex lors de l'insertion de la première valeur. Cela semble assez contre nature. Il semble plus naturel que rightIndex indique l'index après le segment, donc 0 représente le segment vide.

Bien sûr, votre code est extrêmement déroutant car il développe le sous-tableau trié avec un, écrasant l'élément immédiatement après le sous-tableau trié initialement. Vous lisez donc à partir de l'index j mais écrivez la valeur dans j + 1. Ici, vous devez juste être clair que j est la position dans le sous-tableau initial, avant l'insertion. Lorsque les opérations d'indexation deviennent trop délicates, cela m'aide à les représenter sur un morceau de papier quadrillé.

10
JacquesB

L'introduction de votre question me fait penser que vous n'avez pas appris à coder correctement. Quiconque programme dans un langage impératif depuis plus de quelques semaines devrait vraiment avoir ses limites de boucle correctement dans plus de 90% des cas. Peut-être vous précipitez-vous pour commencer à coder avant d'avoir suffisamment réfléchi au problème.

Je vous suggère de corriger cette lacune en (ré) apprenant à écrire des boucles - et je recommanderais quelques heures de travail à travers une gamme de boucles avec du papier et un crayon. Prenez un après-midi pour le faire. Ensuite, passez environ 45 minutes par jour à travailler sur le sujet jusqu'à ce que vous compreniez vraiment.

Tout cela est très bien testé, mais vous devriez tester dans l'espoir que vous obtenez généralement vos limites de boucle (et le reste de votre code).

5

Je devrais peut-être mettre un peu de chair dans mon commentaire:

Il n'y a qu'une seule façon: mieux comprendre votre problème. Mais c'est aussi général que votre question est

Votre point est

Bien que mes hypothèses soient correctes après quelques essais et erreurs, je suis devenu trop frustré à cause du manque de modèle informatique correct dans ma tête.

Quand je lis trial and error, mes alarmes se mettent à sonner. Bien sûr, beaucoup d’entre nous connaissent l’état d’esprit, quand on veut résoudre un petit problème et s’enrouler autour d’autres choses et commencer à deviner d’une manière ou d’une autre, pour faire le code seem à faire, ce qui est censé faire. Certaines solutions piratées en découlent - et certaines sont pures genius; mais pour être honnête: la plupart ne le sont pas. Moi inclus, connaissant cet état.

Indépendamment de votre problème concret, vous avez posé des questions sur la façon d'améliorer:

1) Test

Cela a été dit par d'autres et je n'aurais rien de valable à ajouter

2) Analyse des problèmes

Il est difficile de donner des conseils à ce sujet. Je ne pourrais vous donner que deux conseils, qui vous aideront probablement à améliorer vos compétences sur ce sujet:

  • la plus évidente et la plus banale est à long terme la plus efficace: résoudre de nombreux problèmes. En pratiquant et en répétant, vous développez l'état d'esprit qui vous aide pour les tâches futures. La programmation est comme toute autre activité à améliorer par un travail acharné

Le code Katas est un moyen qui pourrait aider un peu.

Comment devient-on un grand musicien? Cela aide à connaître la théorie et à comprendre la mécanique de votre instrument. Ça aide d'avoir du talent. Mais finalement, la grandeur vient de la pratique; appliquer la théorie encore et encore, en utilisant la rétroaction pour s'améliorer à chaque fois.

Code Kata

Un site que j'aime beaucoup: Code Wars

Atteignez la maîtrise grâce au défi Améliorez vos compétences en vous entraînant avec d'autres sur les défis du code réel

Ce sont des problèmes relativement petits, qui vous aident à affiner vos compétences en programmation. Et ce que j'aime le plus sur Code Wars, c'est que vous pouvez comparer la solution votre à l'une des solutions autres.

Ou peut-être, vous devriez jeter un œil à Exercism.io où vous obtenez des commentaires de la communauté.

  • Les autres conseils sont presque aussi triviaux: Apprenez à décomposer les problèmes Vous devez vous entraîner, décomposer les problèmes en très petits problèmes. Si vous dites, vous avez des problèmes dans l'écriture de boucles, vous faites l'erreur, que vous voyez la boucle dans son ensemble et ne la déconstruisez pas en morceaux. Si vous apprenez à démonter les choses étape par étape, vous apprenez à éviter de telles erreurs.

Je sais - comme je l'ai dit plus haut que vous êtes parfois dans un tel état - qu'il est difficile de diviser des choses "simples" en tâches plus "mortes simples"; mais ça aide beaucoup.

Je me souviens, quand j'ai appris la programmation - professionnellement, j'ai eu énorme des problèmes avec le débogage de mon code. Quel était le problème? Hybris - L'erreur ne peut pas être dans telle ou telle région du code, car je sais qu'elle ne peut pas l'être. Et en conséquence? J'ai survolé le code au lieu de l'analyser J'ai dû apprendre - même si c'était fastidieux de décomposer mon code instruction pour instruction.

3) Développer une ceinture à outils

En plus de connaître votre langue et vos outils - je sais que ce sont les choses brillantes auxquelles les développeurs pensent en premier - apprennent les algorithmes (aka lecture).

Voici deux livres pour commencer:

C'est comme apprendre des recettes pour commencer à cuisiner. Au début, vous ne savez pas quoi faire, vous devez donc chercher ce que les - chefs ont préparé pour vous. Il en va de même pour les algortihms. Les algorithmes sont comme cuisiner des recettes pour des repas courants (structures de données, tri, hachage, etc.) Si vous les connaissez (essayez au moins) par cœur, vous avez un bon point de départ.

3a) Connaître les constructions de programmation

Ce point est un dérivé - pour ainsi dire. Connaissez votre langue - et mieux: sachez, quelles constructions sont possibles dans votre langue.

Un point commun pour un code incorrect ou inefficace est parfois que le programmeur ne connaît pas la différence entre différents types de boucles (for-, while- et do- boucles). Ils sont en quelque sorte tous interchangeables utilisables; mais dans certaines circonstances, le choix d'une autre construction en boucle conduit à un code plus élégant.

Et il y a l'appareil de Duff ...

P.S .:

sinon votre commentaire n'est pas bon que Donal Trump.

Oui, nous devrions refaire le codage!

Une nouvelle devise pour Stackoverflow.

3
Thomas Junk

Je vais donner un exemple plus détaillé de la façon d'utiliser les conditions pré/post et les invariants pour développer une boucle correcte. Ensemble, ces affirmations sont appelées spécification ou contrat.

Je ne suggère pas que vous essayez de le faire pour chaque boucle. Mais j'espère que vous trouverez utile de voir le processus de réflexion impliqué.

Pour ce faire, je traduirai votre méthode en outil appelé Microsoft Dafny , qui est conçu pour prouver l'exactitude de ces spécifications. Il vérifie également la terminaison de chaque boucle. Veuillez noter que Dafny n'a pas de boucle for donc j'ai dû utiliser une boucle while à la place.

Enfin, je montrerai comment vous pouvez utiliser ces spécifications pour concevoir une version, sans doute, légèrement plus simple de votre boucle. Cette version de boucle plus simple a en fait la condition de boucle j > 0 et la mission array[j] = value - tout comme votre intuition initiale.

Dafny va nous prouver que ces deux boucles sont correctes et faire la même chose.

Je ferai ensuite une affirmation générale, basée sur mon expérience, sur la façon d'écrire la bonne boucle arrière, qui vous aidera peut-être si vous êtes confronté à cette situation à l'avenir.

Première partie - Rédaction d'une spécification pour la méthode

Le premier défi auquel nous sommes confrontés est de déterminer ce que la méthode est réellement censée faire. À cette fin, j'ai conçu des conditions pré et post qui spécifient le comportement de la méthode. Pour rendre la spécification plus exacte, j'ai amélioré la méthode pour qu'elle retourne l'index où value a été inséré.

method insert(arr:array<int>, rightIndex:int, value:int) returns (index:int)
  // the method will modify the array
  modifies arr
  // the array will not be null
  requires arr != null
  // the right index is within the bounds of the array
  // but not the last item
  requires 0 <= rightIndex < arr.Length - 1
  // value will be inserted into the array at index
  ensures arr[index] == value 
  // index is within the bounds of the array
  ensures 0 <= index <= rightIndex + 1
  // the array to the left of index is not modified
  ensures arr[..index] == old(arr[..index])
  // the array to the right of index, up to right index is
  // shifted to the right by one place
  ensures arr[index+1..rightIndex+2] == old(arr[index..rightIndex+1])
  // the array to the right of rightIndex+1 is not modified
  ensures arr[rightIndex+2..] == old(arr[rightIndex+2..])

Cette spécification capture pleinement le comportement de la méthode. Ma principale observation à propos de cette spécification est qu'elle serait simplifiée si la procédure passait la valeur rightIndex+1 plutôt que rightIndex. Mais comme je ne vois pas d'où vient cette méthode, je ne sais pas quel effet ce changement aurait sur le reste du programme.

Deuxième partie - déterminer un invariant de boucle

Nous avons maintenant une spécification pour le comportement de la méthode, nous devons ajouter une spécification du comportement de la boucle qui convaincra Dafny que l'exécution de la boucle se terminera et se traduira par l'état final souhaité de array.

Voici votre boucle d'origine, traduite en syntaxe Dafny avec des invariants de boucle ajoutés. Je l'ai également changé pour retourner l'index où la valeur a été insérée.

{
    // take a copy of the initial array, so we can refer to it later
    // ghost variables do not affect program execution, they are just
    // for specification
    ghost var initialArr := arr[..];


    var j := rightIndex;
    while(j >= 0 && arr[j] > value)
       // the loop always decreases j, so it will terminate
       decreases j
       // j remains within the loop index off-by-one
       invariant -1 <= j < arr.Length
       // the right side of the array is not modified
       invariant arr[rightIndex+2..] == initialArr[rightIndex+2..]
       // the part of the array looked at by the loop so far is
       // shifted by one place to the right
       invariant arr[j+2..rightIndex+2] == initialArr[j+1..rightIndex+1]
       // the part of the array not looked at yet is not modified
       invariant arr[..j+1] == initialArr[..j+1] 
    {
        arr[j + 1] := arr[j];
        j := j-1;
    }   
    arr[j + 1] := value;
    return j+1; // return the position of the insert
}

Cela vérifie dans Dafny. Vous pouvez le voir vous-même en en suivant ce lien . Votre boucle implémente donc correctement la spécification de méthode que j'ai écrite dans la première partie. Vous devrez décider si cette spécification de méthode correspond vraiment au comportement souhaité.

Notez que Dafny produit ici une preuve d'exactitude. Il s'agit d'une garantie de justesse beaucoup plus forte que ce qui peut être obtenu par des tests.

Troisième partie - une boucle plus simple

Maintenant que nous avons une spécification de méthode qui capture le comportement de la boucle. Nous pouvons modifier en toute sécurité l'implémentation de la boucle tout en conservant la confiance que nous n'avons pas changé le comportement de la boucle.

J'ai modifié la boucle pour qu'elle corresponde à vos intuitions d'origine sur la condition de la boucle et la valeur finale de j. Je dirais que cette boucle est plus simple que la boucle que vous avez décrite dans votre question. Il est plus souvent capable d'utiliser j plutôt que j+1.

  1. Commencez j à rightIndex+1

  2. Remplacez la condition de boucle par j > 0 && arr[j-1] > value

  3. Remplacez l'affectation par arr[j] := value

  4. Décrémenter le compteur de boucle à la fin de la boucle plutôt qu'au début

Voici le code. Notez que les invariants de boucle sont également un peu plus faciles à écrire maintenant:

method insert2(arr:array<int>, rightIndex:int, value:int) returns (index:int)
  modifies arr
  requires arr != null
  requires 0 <= rightIndex < arr.Length - 1
  ensures 0 <= index <= rightIndex + 1
  ensures arr[..index] == old(arr[..index])
  ensures arr[index] == value 
  ensures arr[index+1..rightIndex+2] == old(arr[index..rightIndex+1])
  ensures arr[rightIndex+2..] == old(arr[rightIndex+2..])
{
    ghost var initialArr := arr[..];
    var j := rightIndex+1;
    while(j > 0 && arr[j-1] > value)
       decreases j
       invariant 0 <= j <= arr.Length
       invariant arr[rightIndex+2..] == initialArr[rightIndex+2..]
       invariant arr[j+1..rightIndex+2] == initialArr[j..rightIndex+1]
       invariant arr[..j] == initialArr[..j] 
    {
        j := j-1;
        arr[j + 1] := arr[j];
    }   
    arr[j] := value;
    return j;
}

Quatrième partie - conseils sur le bouclage vers l'arrière

Après avoir écrit et vérifié plusieurs boucles en quelques années, j'ai les conseils généraux suivants sur la boucle en arrière.

Il est presque toujours plus facile de penser et d'écrire une boucle en arrière (décrémentation) si la décrémentation est effectuée au début de la boucle plutôt qu'à la fin.

Malheureusement, la construction de boucle for dans de nombreuses langues rend cela difficile.

Je soupçonne (mais je ne peux pas prouver) que cette complexité est à l'origine de la différence dans votre intuition sur ce que devrait être la boucle et ce qu'elle devait réellement être. Vous avez l'habitude de penser aux boucles avancées (incrémentées). Lorsque vous souhaitez écrire une boucle arrière (décrémentation), vous essayez de créer la boucle en essayant d'inverser l'ordre dans lequel les choses se produisent dans une boucle avant (incrémentation). Mais en raison du fonctionnement de la construction for, vous avez négligé d'inverser l'ordre de la mise à jour des variables d'assignation et de boucle - ce qui est nécessaire pour une véritable inversion de l'ordre des opérations entre une boucle en amont et en aval.

Cinquième partie - bonus

Juste pour être complet, voici le code que vous obtenez si vous réussissez rightIndex+1 à la méthode plutôt que rightIndex. Cette modification élimine tous les +2 décalages qui sont autrement nécessaires pour réfléchir à l'exactitude de la boucle.

method insert3(arr:array<int>, rightIndex:int, value:int) returns (index:int)
  modifies arr
  requires arr != null
  requires 1 <= rightIndex < arr.Length 
  ensures 0 <= index <= rightIndex
  ensures arr[..index] == old(arr[..index])
  ensures arr[index] == value 
  ensures arr[index+1..rightIndex+1] == old(arr[index..rightIndex])
  ensures arr[rightIndex+1..] == old(arr[rightIndex+1..])
{
    ghost var initialArr := arr[..];
    var j := rightIndex;
    while(j > 0 && arr[j-1] > value)
       decreases j
       invariant 0 <= j <= arr.Length
       invariant arr[rightIndex+1..] == initialArr[rightIndex+1..]
       invariant arr[j+1..rightIndex+1] == initialArr[j..rightIndex]
       invariant arr[..j] == initialArr[..j] 
    {
        j := j-1;
        arr[j + 1] := arr[j];
    }   
    arr[j] := value;
    return j;
}
2
flamingpenguin

Bien faire les choses facilement et couramment est une question d'expérience. Même si le langage ne vous permet pas de l'exprimer directement, ou si vous utilisez un cas plus complexe que la simple chose intégrée peut gérer, ce que vous pensez est un niveau supérieur comme " visitez chaque élément une fois dans l'ordre respectueux "et le codeur le plus expérimenté traduit cela en les bons détails instantanément parce qu'il l'a fait tant de fois.

Même alors, dans des cas plus complexes, il est facile de se tromper, car la chose que vous écrivez est généralement pas la chose courante en conserve. Avec des langages et des bibliothèques plus modernes, vous ne faites pas écrivez la chose facile parce qu'il existe une construction en conserve ou appelez cela. En C++, le mantra moderne est "utiliser des algorithmes plutôt que d'écrire du code".

Donc, la façon de vous assurer que c'est bien, pour ce genre de chose en particulier, est de regarder les conditions aux limites. Tracez le code dans votre tête pour les quelques cas où les choses changent. Si la index == array-max, ce qui se produit? Qu'en est-il de max-1? Si le code fait un mauvais tour, ce sera à l'une de ces limites. Certaines boucles doivent se préoccuper du premier ou du dernier élément ainsi que de la construction en boucle pour obtenir les bonnes limites; par exemple. si vous faites référence à a[I] et a[I-1] que se passe-t-il lorsque I est la valeur minimale?

Regardez également les cas où le nombre d'itérations (correctes) est extrême: si les limites se rencontrent et que vous aurez 0 itérations, cela fonctionnera-t-il sans cas particulier? Et qu'en est-il d'une seule itération, où la borne la plus basse est également la borne la plus haute en même temps?

L'examen des cas Edge (des deux côtés de chaque Edge) est ce que vous devez faire lors de l'écriture de la boucle et ce que vous devez faire dans les revues de code.

2
JDługosz

Si j'ai bien compris le problème, votre question est de savoir comment penser à obtenir des boucles dès le premier essai, pas comment vous assurer que votre boucle est correcte (pour laquelle la réponse serait testée comme expliqué dans d'autres réponses).

Ce que je considère comme une bonne approche est d'écrire la première itération sans boucle. Après avoir fait cela, vous devriez remarquer ce qu'il faut changer entre les itérations.

Est-ce un nombre, comme un 0 ou un 1? Ensuite, vous avez probablement besoin d'un for, et d'un bingo, vous avez également votre départ i. Réfléchissez ensuite au nombre de fois où vous voulez exécuter la même chose, et vous aurez également votre condition finale.

Si vous ne savez pas exactement combien de fois il s'exécutera, vous n'avez pas besoin d'un pour, mais d'un certain temps ou d'un certain temps.

Techniquement, n'importe quelle boucle peut être traduite en n'importe quelle autre boucle, mais le code est plus facile à lire si vous utilisez la bonne boucle, voici donc quelques conseils:

  1. Si vous vous retrouvez en train d'écrire un if () {...; break;} dans un for, vous avez besoin d'un moment et vous avez déjà la condition

  2. "While" est peut-être la boucle la plus utilisée dans n'importe quelle langue, mais elle ne devrait pas être imo. Si vous vous retrouvez en train d'écrire bool ok = True; while (check) {faire quelque chose et, espérons-le, changer ok à un moment donné}; alors vous n'avez pas besoin d'un certain temps, mais d'un certain temps, car cela signifie que vous avez tout ce dont vous avez besoin pour exécuter la première itération.

Maintenant un peu de contexte ... Quand j'ai appris à programmer (Pascal), je ne parlais pas anglais. Pour moi, "pour" et "pendant", n'avait pas beaucoup de sens, mais le mot-clé "répéter" (faire tout en C) est presque le même dans ma langue maternelle, donc je l'utiliserais pour tout. À mon avis, répéter (faire pendant) est la boucle la plus naturelle, car presque toujours vous voulez que quelque chose soit fait et ensuite vous voulez que cela se fasse encore et encore, jusqu'à ce qu'un objectif soit atteint. "Pour" est juste un raccourci qui vous donne un itérateur et place étrangement la condition au début du code, même si, presque toujours, vous voulez que quelque chose soit fait jusqu'à ce qu'il se passe quelque chose. De plus, while n'est qu'un raccourci pour if () {do while ()}. Les raccourcis sont agréables pour plus tard, mais le moyen le plus simple d'obtenir les boucles dès le premier essai est de les écrire de la même manière que votre cerveau le pense, qui est "répéter (faire) {quelque chose} jusqu'à (pendant) (vous avez pour arrêter) ", puis utilisez le raccourci approprié si nécessaire.

2
Andrei

Les boucles inverses, en particulier, peuvent être difficiles à raisonner parce que beaucoup de nos langages de programmation sont biaisés vers l'itération directe, à la fois dans la syntaxe commune pour la boucle for et par l'utilisation d'intervalles semi-ouverts de base zéro. Je ne dis pas qu'il est faux que les langues aient fait ces choix; Je dis simplement que ces choix compliquent la réflexion sur les boucles inverses.

En général, rappelez-vous qu'une boucle for n'est qu'un sucre syntaxique construit autour d'une boucle while:

// pseudo-code!
for (init; cond; step) { body; }

est équivalent à:

// pseudo-code!
init;
while (cond) {
  body;
  step;
}

(éventuellement avec une couche supplémentaire de portée pour garder les variables déclarées dans l'étape init locale à la boucle).

C'est bien pour de nombreux types de boucles, mais faire passer l'étape en dernier est gênant lorsque vous marchez en arrière. Lorsque je travaille en arrière, je trouve qu'il est plus facile de démarrer l'index de boucle avec la valeur après celle que je veux et de déplacer la partie step en haut de la boucle, comme ceci:

auto i = v.size();  // init
while (i > 0) {  // simpler condition because i is one after
    --i;  // step before the body
    body;  // in body, i means what you'd expect
}

ou, comme une boucle for:

for (i = v.size(); i > 0; ) {
    --i;  // step
    body;
}

Cela peut sembler troublant, car l'expression de l'étape se trouve dans le corps plutôt que dans l'en-tête. C'est un effet secondaire malheureux du biais direct inhérent à la syntaxe de boucle. Pour cette raison, certains diront que vous faites plutôt ceci:

for (i = v.size() - 1; i >= 0; --i) {
    body;
}

Mais c'est un désastre si votre variable d'index est un type non signé (comme cela peut être en C ou C++).

Dans cet esprit, écrivons votre fonction d'insertion.

  1. Puisque nous travaillerons en arrière, nous laisserons l'index de boucle être l'entrée après l'emplacement de tableau "actuel". Je concevoir la fonction pour prendre la taille de l'entier plutôt qu'un index du dernier élément car les plages semi-ouvertes sont le moyen naturel de représenter les plages dans la plupart des langages de programmation et parce qu'elles nous donnent un moyen pour représenter un tableau vide sans recourir à une valeur magique comme -1.

    function insert(array, size, value) {
      var j = size;
    
  2. Alors que la nouvelle valeur est plus petite que l'élément précédent, continuez à changer. Bien sûr, l'élément précédent ne peut être vérifié que s'il y a is un élément précédent, nous devons donc d'abord vérifier que nous ne sommes pas au tout début:

      while (j != 0 && value < array[j - 1]) {
        --j;  // now j become current
        array[j + 1] = array[j];
      }
    
  3. Cela laisse j là où nous voulons la nouvelle valeur.

      array[j] = value; 
    };
    

Programming Pearls de Jon Bentley donne une explication très claire du type d'insertion (et d'autres algorithmes), ce qui peut aider à construire vos modèles mentaux pour ces types de problèmes.

1
Adrian McCarthy

J'essaierai de rester à l'écart des sujets mentionnés à gogo déjà.

Quels sont les outils/modèles mentaux pour éviter de telles erreurs?

Outils

Pour moi, le plus gros outil pour écrire de meilleures boucles for et while n'est pas du tout d'écrire des boucles for ou while.

La plupart des langues modernes essaient de cibler ce problème d'une manière ou d'une autre. Par exemple, Java, tout en ayant Iterator dès le début, qui était un peu maladroit à utiliser, a introduit une syntaxe de raccourci pour les utiliser plus facilement dans une version latler. C # les a aussi, etc.

Ma langue actuellement préférée, Ruby, a adopté l'approche fonctionnelle (.each, .map Etc.) sur le devant. C'est très puissant. Je viens de faire un comptage rapide dans une base de code Ruby sur laquelle je travaille: dans environ 10 000 lignes de code, il y a zéro for et environ 5 while.

Si j'étais obligé de choisir une nouvelle langue, la recherche de boucles fonctionnelles/basées sur des données comme celle-ci serait très élevée dans la liste des priorités.

Modèles mentaux

Gardez à l'esprit que while est le minimum d'abstraction le plus simple que vous puissiez obtenir, juste une étape au-dessus de goto. À mon avis, for aggrave encore la situation au lieu de l'améliorer, car il regroupe les trois parties de la boucle de manière serrée.

Donc, si je suis dans un environnement où for est utilisé, alors je fais en sorte que les 3 parties soient simples et toujours les mêmes. Cela signifie que j'écrirai

limit = ...;
for (idx = 0; idx < limit; idx++) { 

Mais rien de plus complexe. Peut-être, peut-être que j'aurai parfois un compte à rebours, mais je ferai de mon mieux pour l'éviter.

Si vous utilisez while, je reste à l'écart des manigances internes alambiquées impliquant la condition de boucle. Le test à l'intérieur de la while(...) sera aussi simple que possible, et j'éviterai break du mieux que je pourrai. De plus, la boucle sera courte (comptage des lignes de code) et toute plus grande quantité de code sera éliminée.

Surtout si la condition while réelle est complexe, j'utiliserai une "variable de condition" qui est très facile à repérer, et je ne placerai pas la condition dans l'instruction while elle-même:

repeat = true;
while (repeat) {
   repeat = false; 
   ...
   if (complex stuff...) {
      repeat = true;
      ... other complex stuff ...
   }
}

(Ou quelque chose comme ça, dans la bonne mesure, bien sûr.)

Cela vous donne un modèle mental très simple qui est "cette variable s'exécute de 0 à 10 de façon monotone" ou "cette boucle s'exécute jusqu'à ce que cette variable soit fausse/vraie". La plupart des cerveaux semblent être capables de gérer très bien ce niveau d'abstraction.

J'espère que cela pourra aider.

1
AnoE

Êtes-vous simplement confus quant à ce que fait réellement une boucle for et à la mécanique de son fonctionnement?

for(initialization; condition; increment*)
{
    body
}
  1. Tout d'abord, l'initialisation est exécutée
  2. Ensuite, la condition est vérifiée
  3. Si la condition est vraie, le corps est exécuté une fois. Si ce n'est pas le cas # 6
  4. Le code d'incrémentation est exécuté
  5. Goto # 2
  6. Fin de boucle

Il peut être utile de réécrire ce code à l'aide d'une construction différente. Voici la même chose en utilisant une boucle while:

initialization
while(condition)
{
    body
    increment
}

Voici quelques autres suggestions:

  • Pouvez-vous utiliser une autre construction de langage comme une boucle foreach? Cela prend en charge la condition et l'incrémentation pour vous.
  • Pouvez-vous utiliser une fonction Carte ou Filtre? Certaines langues ont des fonctions avec ces noms qui parcourent en interne une collection pour vous. Vous venez de fournir la collection et le corps.
  • Vous devriez vraiment passer plus de temps à vous familiariser avec les boucles for. Vous les utiliserez tout le temps. Je vous suggère de parcourir une boucle for dans un débogueur pour voir comment il s'exécute.

* Notez que même si j'utilise le terme "incrément", ce n'est qu'un code qui se trouve après le corps et avant la vérification de l'état. Cela peut être un décrément ou rien du tout.

0
user2023861

Tentative de perspicacité supplémentaire

Pour algorithmes non triviaux avec des boucles, vous pouvez essayer la méthode suivante:

  1. Créer un tableau fixe avec 4 positions, et mettre quelques valeurs dans, pour simuler le problème;
  2. Écrivez votre algorithme pour résoudre le problème donné, sans aucune boucle et avec des indexations codées en dur;
  3. Après cela, substituez les indexations codées en dur dans votre code avec une variable i ou j, et incrémentez/décrémentez ces variables si nécessaire (mais toujours sans boucle) ;
  4. Réécrivez votre code, et mettez les blocs répétitifs dans une boucle, remplissant les conditions pré et post;
  5. [ facultatif ] réécrivez votre boucle pour qu'elle soit sous la forme que vous souhaitez (pour/pendant/faire pendant);
  6. Le plus important est d'écrire votre algorithme correctement; après cela, vous refactorisez et optimisez votre code/boucles si nécessaire (mais cela pourrait rendre le code non trivial pour un lecteur)

Votre problème

//TODO: Insert the given value in proper position in the sorted subarray
function insert(array, rightIndex, value) { ... };

Écrivez le corps de la boucle plusieurs fois manuellement

Utilisons un tableau fixe à 4 positions et essayons d'écrire l'algorithme manuellement, sans boucles:

           //0 1 2 3
var array = [2,5,9,1]; //array sorted from index 0 to 2
var leftIndex = 0;
var rightIndex = 2;
var value = array[3]; //placing the last value within the array in the proper position

//starting here as 2 == rightIndex

if (array[2] > value) {
    array[3] = array[2];
} else {
    array[3] = value;
    return; //found proper position, no need to proceed;
}

if (array[1] > value) {
    array[2] = array[1];
} else {
    array[2] = value;
    return; //found proper position, no need to proceed;
}

if (array[0] > value) {
    array[1] = array[0];
} else {
    array[1] = value;
    return; //found proper position, no need to proceed;
}

array[0] = value; //achieved beginning of the array

//stopping here because there 0 == leftIndex

Réécriture, suppression des valeurs codées en dur

//consider going from 2 to 0, going from "rightIndex" to "leftIndex"

var i = rightIndex //starting here as 2 == rightIndex

if (array[i] > value) {
    array[i+1] = array[i];
} else {
    array[i+1] = value;
    return; //found proper position, no need to proceed;
}

i--;
if (i < leftIndex) {
    array[i+1] = value; //achieved beginning of the array
    return;
}

if (array[i] > value) {
    array[i+1] = array[i];
} else {
    array[i+1] = value;
    return; //found proper position, no need to proceed;
}

i--;
if (i < leftIndex) {
    array[i+1] = value; //achieved beginning of the array
    return;
}

if (array[i] > value) {
    array[i+1] = array[i];
} else {
    array[i+1] = value;
    return; //found proper position, no need to proceed;
}

i--;
if (i < leftIndex) {
    array[i+1] = value; //achieved beginning of the array
    return;
}

//stopping here because there 0 == leftIndex

Traduire en boucle

Avec while:

var i = rightIndex; //starting in rightIndex

while (true) {
    if (array[i] > value) { //refactor: this can go in the while clause
        array[i+1] = array[i];
    } else {
        array[i+1] = value;
        break; //found proper position, no need to proceed;
    }

    i--;
    if (i < leftIndex) { //refactor: this can go (inverted) in the while clause
        array[i+1] = value; //achieved beginning of the array
        break;
    }
}

Refactoriser/réécrire/optimiser la boucle comme vous le souhaitez:

Avec while:

var i = rightIndex; //starting in rightIndex

while ((array[i] > value) && (i >= leftIndex)) {
    array[i+1] = array[i];
    i--;
}

array[i+1] = value; //found proper position, or achieved beginning of the array

avec for:

for (var i = rightIndex; (array[i] > value) && (i >= leftIndex); i--) {
    array[i+1] = array[i];
}

array[i+1] = value; //found proper position, or achieved beginning of the array

PS: le code suppose que l'entrée est valide et que le tableau ne contient pas de répétitions;

0
Emerson Cardoso