Question

Lors de l'examen, je rencontre parfois ce type de boucle:

i = begin
while ( i != end ) {    
   // ... do stuff
   if ( i == end-1 (the one-but-last element) ) {
      ... do other stuff
   }
   increment i
}

Ensuite, je pose la question: voudriez-vous écrire ceci?

i = begin
mid = ( end - begin ) / 2 // (the middle element)
while ( i != end ) {    
   // ... do stuff
   if ( i > mid ) {
      ... do other stuff
   }
   increment i
}

À mon avis, cela va à l'encontre de l'intention d'écrire une boucle: vous bouclez, car il y a quelque chose de commun à faire pour chacun des éléments. En utilisant cette construction, vous faites quelque chose de différent pour certains éléments. Je conclus donc que vous avez besoin d’une boucle distincte pour ces éléments:

i = begin
mid = ( end - begin ) / 2 //(the middle element)
while ( i != mid ) {    
   // ... do stuff
   increment i
}

while ( i != end ) {
   // ... do stuff
   // ... do other stuff
   increment i
}

Maintenant, j’ai même vu une question sur l’OS sur la procédure à suivre écrivez la if - clause de manière agréable ... Et je suis triste: quelque chose ne va pas ici.

Est-ce que je me trompe? Dans l’affirmative, qu’y at-il de si bon à encombrer le corps de la boucle avec des cas particuliers, que vous connaissez déjà, au moment de la codification?

Était-ce utile?

La solution

@xtofl,

Je suis d'accord avec votre préoccupation.

Des millions de fois, j'ai rencontré un problème similaire.

Chaque développeur ajoute une gestion spéciale pour le premier ou le dernier élément.

Dans la plupart des cas, il suffit de faire une boucle avec l'élément startIdx + 1 ou endIdx - 1 ou même de scinder une longue boucle en plusieurs boucles plus courtes.

Dans de très rares cas, il n'est pas possible de scinder la boucle.

À mon avis, les éléments inhabituels doivent être traités en dehors de la boucle chaque fois que cela est possible.

Autres conseils

Je ne pense pas que cette question doive recevoir une réponse de principe (par exemple, & "; dans une boucle, traitez tous les éléments de la même manière &";). Au lieu de cela, vous pouvez examiner deux facteurs pour évaluer si une implémentation est bonne ou mauvaise:

  1. Efficacité d'exécution - le code compilé est-il rapide ou le serait-il plus rapidement si on le faisait différemment?
  2. Maintenabilité du code - Est-il facile (pour un autre développeur) de comprendre ce qui se passe ici?

Si c'est plus rapide et que le code est plus lisible en faisant tout en une boucle, faites-le ainsi. Si c'est plus lent et moins lisible, faites-le autrement.

S'il est plus rapide et moins lisible, ou plus lent mais plus lisible, déterminez lequel de ces facteurs est le plus important dans votre cas particulier, puis décidez comment vous souhaitez (ou non) exécuter une boucle.

Je sais que j'ai vu cela lorsque des personnes ont tenté de joindre des éléments d'un tableau en une chaîne séparée par des virgules:

for(i=0;i<elements.size;i++) {
   if (i>0) {
     string += ','
   }
   string += elements[i]
}

Vous avez soit la clause if dedans, soit vous devez dupliquer la chaîne + = à la fin.

La solution évidente dans ce cas est

string = elements.join(',')

Mais la méthode de jointure fait la même boucle en interne. Et il n’ya pas toujours de méthode pour faire ce que vous voulez.

Je me suis rendu compte que lorsque je mettais des cas spéciaux dans une boucle perdue, je suis généralement trop malin pour mon propre bien.

Dans le dernier extrait que vous avez posté, vous répétez du code pour // .... faire des choses.

Il est logique de conserver deux boucles lorsque vous avez un ensemble d'opérations complètement différent sur un ensemble d'indices différent.

i = begin
mid = ( end - begin ) / 2 //(the middle element)
while ( i != mid ) {    
   // ... do stuff
   increment i
}

while ( i != end ) {
   // ... do other stuff
   increment i
}

Ce n’est pas le cas, vous voudrez toujours garder une seule boucle. Cependant, il reste que vous enregistrez toujours (fin - début) / 2 le nombre de comparaisons. Il s’agit donc de savoir si votre code doit être soigné ou si vous souhaitez économiser certains cycles de traitement. L'appel est à vous.

Je pense que vous l'avez entièrement cloué. La plupart des gens tombent dans le piège d'inclure des branches conditionnelles dans les boucles, alors qu'ils pourraient les faire dehors: ce qui est tout simplement plus rapide .

Par exemple:

if(items == null)
    return null;

StringBuilder result = new StringBuilder();
if(items.Length != 0)
{
    result.Append(items[0]); // Special case outside loop.
    for(int i = 1; i < items.Length; i++) // Note: we start at element one.
    {
        result.Append(";");
        result.Append(items[i]);
    }
}
return result.ToString();

Et le cas intermédiaire que vous avez décrit est tout simplement vilain . Imaginez si ce code grandit et nécessite une refonte dans différentes méthodes.

Sauf si vous analysez XML < grin > les boucles doivent être aussi simples et concises que possible.

Je pense que vous avez raison de dire que la boucle est censée traiter tous les éléments de la même manière. Malheureusement, il arrive parfois que des cas particuliers se présentent et qu’ils doivent être traités à l’intérieur de la construction de boucle via les instructions if.

S'il y a beaucoup de cas particuliers, vous devriez probablement penser à trouver un moyen de traiter les deux ensembles d'éléments différents dans des constructions séparées.

Je préfère simplement exclure l'élément de la boucle et donner un traitement séparé en dehors de la boucle

Par exemple: considérons le cas d'EOF

i = begin
while ( i != end -1 ) {    
   // ... do stuff for element from begn to second last element
   increment i
}

if(given_array(end -1) != ''){
   // do stuff for the EOF element in the array
}

Bien sûr, il est ridicule de mettre en boucle des objets spéciaux dans une boucle. Je ne dupliquerais pas le do_stuff non plus; Je le mettrais soit dans une fonction ou une macro pour ne pas copier-coller le code.

Une autre chose que je n'aime pas voir est le modèle de cas :

for (i=0; i<5; i++)
{
  switch(i)
  {
    case 0:
      // something
      break;
    case 1:
      // something else
      break;
    // etc...
  }
}

J'ai vu cela dans un code réel.

Lequel fait le mieux?

Si le nombre d'éléments est très important, je boucle toujours une fois, surtout si vous allez effectuer une opération sur chaque élément. Le coût de l’évaluation du conditionnel sera probablement inférieur à deux fois la boucle.

Oups, bien sûr, vous ne faites pas une boucle deux fois ... Dans ce cas, deux boucles sont préférables. Cependant, j’affirme que la performance doit être la principale considération. Il n’est pas nécessaire d’engager le conditionnel dans la boucle (N fois) si vous pouvez partitionner le travail par une simple manipulation des limites de la boucle (une fois).

Ce cas spécial doit être effectué en dehors de la boucle s'il ne doit être exécuté qu'une seule fois.

Cependant, il est possible qu’un index ou une autre variable soit simplement plus facile à conserver dans la boucle en raison de la portée. Il peut également y avoir une raison contextuelle de conserver toutes les opérations de la structure de données ensemble dans la structure de contrôle de boucle, bien que je pense que cela constitue un argument faible en soi.

Il s’agit de l’utiliser selon les besoins et la commodité. En tant que tels, il n’est pas fait mention de traiter les éléments de la même manière et il n’ya certainement aucun mal à ce que les caractéristiques fournies par la langue soient traitées.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top