Comment ce C for-loop imprime-t-il des pyramides text-art?

C’est la première fois que je poste ici, j’espère que je le fais bien.

En gros, j’ai besoin d’aide pour essayer de comprendre le code que j’ai écrit pour la classe en utilisant C. Le but du programme est de demander à l’utilisateur un nombre compris entre 0 et 23. Ensuite, en fonction du nombre entré par l’utilisateur, une demi-pyramide être imprimé (comme ceux des jeux Mario de la vieille école). Je suis un débutant en programmation et la réponse à mon code a été obtenue par simple chance, mais je ne peux pas vraiment dire comment mes boucles for-loop fournissent le chiffre pyramidal.

#include  int main ( void ) { int user_i; printf ( "Hello there and welcome to the pyramid creator program\n" ); printf ( "Please enter a non negative INTEGER from 0 to 23\n" ); scanf ( "%d", &user_i ); while ( user_i  23 ) { scanf ( "%d", &user_i ); } for ( int tall = 0; tall < user_i; tall++ ) { // this are the two for loops that happened by pure magic, I am still // trying to figure out why are they working they way they are for ( int space = 0; space <= user_i - tall; space++ ) { printf ( " " ); } for ( int hash = 0; hash <= tall; hash++ ) { printf ( "#" ); } // We need to specify the printf("\n"); statement here printf ( "\n" ); } return 0; } 

En tant que nouveau programmeur, j’ai suivi le peu que je sais sur le pseudocode. Je n’arrive pas à comprendre pourquoi la section for-loop fonctionne comme elle est. Je comprends parfaitement la boucle while (bien que les corrections et les meilleures pratiques soient les bienvenues), mais la logique de la boucle for-me continue de m’échapper et je voudrais bien comprendre cela avant de continuer. Toute aide est la bienvenue.

J’expliquerai le processus permettant de comprendre ce code au point où je serais à l’aise de l’utiliser moi-même. Je vais prétendre que je ne vous ai pas lu la description, alors je pars de zéro. Le processus est divisé en étapes que je numéroterai au fur et à mesure. Mon objective sera de vous donner quelques techniques générales qui faciliteront la lecture des programmes.

Étape 1: Comprendre la structure grossière

La première étape consistera à comprendre en termes généraux ce que le programme fait sans s’embourber dans les détails. Commençons par lire le corps de la fonction principale.

 int main(void) { int user_i; printf("Hello there and welcome to the pyramid creator program\n"); printf("Please enter a non negative INTEGER from 0 to 23\n"); scanf("%d", &user_i); 

Jusqu’ici, nous avons déclaré un nombre entier et avons demandé à l’utilisateur de saisir un nombre, puis nous avons utilisé la fonction scanf pour définir un nombre entier égal à celui entré par l’utilisateur. Malheureusement, l’invite ou le code n’indiquent pas clairement quelle est la fonction de l’entier. Continuons à lire.

  while (user_i < 0 || user_i > 23) { scanf("%d", &user_i); } 

Ici, nous demandons éventuellement à l’utilisateur de saisir des entiers supplémentaires. À en juger par l’invite, il semble raisonnable de supposer que le but de cette déclaration est de s’assurer que notre entier est dans la plage appropriée et qu’il est facile à vérifier en examinant le code. Regardons la ligne suivante

  for (int tall = 0; tall < user_i; tall++) { 

Ceci est la boucle externe. Notre entier énigmatique, user_i apparaît à nouveau et nous avons un autre entier qui va de 0 user_i . Regardons un peu plus de code.

  for (int space = 0; space <= user_i - tall; space++) { printf(" "); } 

Ceci est la première boucle interne. Ne nous attardons pas sur les détails de ce qui se passe avec ce nouvel space entier ou sur la raison pour laquelle user_i - tall apparaît, mais notons simplement que le corps de la boucle de sol affiche uniquement un espace. Donc, cette boucle for a simplement pour effet d'imprimer un tas d'espaces. Regardons la prochaine boucle interne.

  for (int hash = 0; hash <= tall; hash++) { printf("#"); } 

Celui-ci est similaire. Il imprime juste un tas de hachages. Ensuite nous avons

  printf("\n"); 

Cela imprime une nouvelle ligne. Suivant est

  } return 0; } 

Cela signifie que la boucle for externe se termine et, une fois la boucle for externe terminée, le programme se termine.

Notez que nous avons trouvé deux pièces majeures dans le code. Le premier élément correspond à l’ user_i une valeur pour user_i et le deuxième élément, la boucle externe for, doit correspondre à l’emplacement où cette valeur est utilisée pour dessiner la pyramide. Ensuite, essayons de comprendre ce que user_i signifie.

Étape 2: Découvrez le sens de user_i

Maintenant, puisqu'une nouvelle ligne est imprimée pour chaque itération de la boucle externe et que l'énigmatique user_i contrôle le nombre d'itérations de la boucle externe et, par conséquent, le nombre de nouvelles lignes imprimées, il semblerait que user_i contrôle la hauteur de la pyramide. qui est créé. Pour obtenir la relation exacte, supposons que user_i ait la valeur 3 , alors tall prendrait les valeurs 0,1, et 2, et ainsi la boucle s'exécuterait trois fois et la hauteur de la pyramide serait de trois. Notez également que si user_i augmente de un, la boucle s'exécutera une fois de plus et la pyramide sera supérieure d'un. Cela signifie que user_i doit être à la hauteur de la pyramide. pyramidHeight la variable en pyramidHeight avant d’oublier. Notre fonction principale ressemble maintenant à ceci:

 int main(void) { int pyramidHeight; printf("Hello there and welcome to the pyramid creator program\n"); printf("Please enter a non negative INTEGER from 0 to 23\n"); scanf("%d", &pyramidHeight); while (pyramidHeight < 0 || pyramidHeight > 23) { scanf("%d", &pyramidHeight); } for (int tall = 0; tall < pyramidHeight; tall++) { for (int space = 0; space <= pyramidHeight - tall; space++) { printf(" "); } for (int hash = 0; hash <= tall; hash++) { printf("#"); } printf("\n"); } return 0; } 

Étape 3: Créer une fonction qui prend la hauteur de la pyramide

Puisque nous comprenons la première partie du code, nous pouvons l’incorporer dans une fonction et ne plus y penser. Cela rendra le code plus facile à regarder. Comme cette partie du code est chargée d’obtenir une hauteur valide, appelons la fonction getValidHeight . Après cela, notez que la hauteur de la pyramide ne changera pas dans la méthode main . Nous pouvons donc la déclarer comme un const int . Notre code ressemble maintenant à ceci:

 #include  const int getValidHeight() { int pyramidHeight; printf("Hello there and welcome to the pyramid creator program\n"); printf("Please enter a non negative INTEGER from 0 to 23\n"); scanf("%d", &pyramidHeight); while (pyramidHeight < 0 || pyramidHeight > 23) { scanf("%d", &pyramidHeight); } return pyramidHeight; } int main(void) { const int pyramidHeight = getValidHeight(); for (int tall = 0; tall < pyramidHeight; tall++) { for (int space = 0; space <= pyramidHeight - tall; space++) { printf(" "); } for (int hash = 0; hash <= tall; hash++) { printf("#"); } printf("\n"); } return 0; } 

Étape 4: comprendre les boucles for internes.

Nous soaps que les boucles for internes impriment un caractère à plusieurs resockets, mais combien de fois? Considérons la première boucle interne. Combien d'espaces sont imprimés? Vous pourriez penser que, par analogie avec la boucle externe, il existe pyramidHeight - tall , mais ici nous avons un space <= pyramidHeight - tall où la situation réellement analogue serait space < pyramidHeight - tall . Puisque nous avons <= au lieu de < , nous obtenons une itération supplémentaire où space est égal à pyramidHeight - tall . Nous voyons donc que pyramidHeight - tall + 1 espaces sont imprimés. De même, les hachages tall + 1 sont imprimés

Étape 5: Déplacez l’impression de plusieurs caractères dans leurs propres fonctions.

L'impression d'un caractère à plusieurs resockets étant facile à comprendre, nous pouvons déplacer ce code dans ses propres fonctions. Voyons à quoi ressemble notre code maintenant.

 #include  const int getValidHeight() { int pyramidHeight; printf("Hello there and welcome to the pyramid creator program\n"); printf("Please enter a non negative INTEGER from 0 to 23\n"); scanf("%d", &pyramidHeight); while (pyramidHeight < 0 || pyramidHeight > 23) { scanf("%d", &pyramidHeight); } return pyramidHeight; } void printSpaces(const int numSpaces) { for (int i = 0; i < numSpaces; i++) { printf(" "); } } void printHashes(const int numHashes) { for (int i = 0; i < numHashes; i++) { printf("#"); } } int main(void) { const int pyramidHeight = getValidHeight(); for (int tall = 0; tall < pyramidHeight; tall++) { printSpaces(pyramidHeight - tall + 1); printHashes(tall + 1); printf("\n"); } return 0; } 

Maintenant, lorsque je regarde la fonction main , je n'ai pas à m'inquiéter de la manière dont printSpaces imprime réellement les espaces. J'ai déjà oublié s'il utilise une boucle for ou une boucle while. Cela libère mon cerveau pour penser à autre chose.

Étape 6: Introduction aux variables et choix de bons noms pour les variables

Notre fonction main est maintenant facile à lire. Nous sums prêts à commencer à réfléchir à ce qu'il fait réellement. Chaque itération de la boucle for imprime un certain nombre d'espaces, suivis d'un certain nombre de hachages, suivis d'une nouvelle ligne. Étant donné que les espaces sont imprimés en premier, ils seront tous à gauche, ce que nous souhaitons obtenir comme résultat.

Etant donné que les nouvelles lignes sont imprimées sous les anciennes lignes du terminal, une valeur de zéro pour le tall correspond à la ligne du haut de la pyramide.

En gardant cela à l’esprit, introduisons deux nouvelles variables, numSpaces et numHashes pour le nombre d’espaces et de hachages à imprimer dans une itération. Comme la valeur de ces variables ne change pas en une seule itération, nous pouvons en faire des constantes. Aussi, changeons le nom de tall (qui est un adjectif et donc un mauvais nom pour un entier) en distanceFromTop . Notre nouvelle méthode principale ressemble à ceci

 int main(void) { const int pyramidHeight = getValidHeight(); for (int distanceFromTop = 0; distanceFromTop < pyramidHeight; distanceFromTop++) { const int numSpaces = pyramidHeight - distanceFromTop + 1; const int numHashes = distanceFromTop + 1; printSpaces(numSpaces); printHashes(numHashes); printf("\n"); } return 0; } 

Étape 7: Pourquoi numSpaces et numHashes sont-ils ce qu’ils sont?

Tout vient ensemble maintenant. La seule chose qui rest à comprendre, ce sont les formules qui donnent numSpaces et numHashes .

Commençons par numHashes car il est plus facile à comprendre. Nous voulons que numHashes soit numHashes à un lorsque la distance par rapport au sumt est égale à zéro et que numHashes augmenté de un chaque fois que la distance par rapport au sumt numHashes = distanceFromTop + 1 ; par conséquent, la formule correcte est numHashes = distanceFromTop + 1 .

Maintenant pour numSpaces . Nous soaps que chaque fois que la distance entre le sumt et le sumt augmente, un espace se transforme en hachage. Il en rest donc un de moins. Ainsi, l'expression de numSpaces devrait numSpaces -distanceFromTop . Mais combien d’espaces la rangée supérieure doit-elle avoir? Comme la rangée supérieure contient déjà un hachage, il y a des hachages pyramidHeight - 1 qui doivent être créés. Il doit donc y avoir au moins pyramidHeight - 1 pour pouvoir les transformer en hachages. Dans le code, nous avons choisi pyramidHeight + 1 espaces dans la rangée supérieure, soit deux de plus que pyramidHeight - 1 , ce qui a pour effet de déplacer l’image entière de deux espaces.

Conclusion

Vous demandiez seulement comment les deux boucles intérieures fonctionnaient, mais j'ai donné une très longue réponse. C’est parce que j’ai pensé que le vrai problème n’était pas que vous ne compreniez pas comment les boucles for fonctionnent correctement, mais plutôt que votre code était difficile à lire et qu’il était donc difficile de dire ce qui se passait. Je vous ai donc montré comment j'aurais écrit le programme, en espérant que vous penseriez qu'il est plus facile à lire, pour que vous puissiez voir ce qui se passe plus clairement et, espérons-le, pour apprendre à écrire vous-même un code plus clair.

Comment ai-je changé le code? J'ai changé les noms des variables afin de préciser le rôle de chaque variable. J'ai introduit de nouvelles variables et essayé de leur donner de bons noms également; et j'ai déplacé une partie du code de niveau inférieur impliquant entrée et sortie et la logique d'impression des caractères dans leurs propres méthodes. Ce dernier changement a considérablement réduit le nombre de lignes de la fonction main , éliminé l’imbrication des boucles for dans la fonction main et a rendu la logique clé du programme facile à voir.

Tout d’abord, retirons le corps des boucles. Le premier imprime simplement des espaces et le second imprime des marques de hachage.

Nous voulons imprimer une ligne comme celle-ci, où _ est un espace:

 ______###### 

La question magique est donc: combien d’espaces et de nombres devons-nous imprimer?

À chaque ligne, nous voulons imprimer 1 # de plus que la ligne précédente et 1 espace de moins que la ligne précédente. C’est le but “grand” sert dans la boucle externe. Vous pouvez considérer cela comme “le nombre de marques de hachage devant être imprimées sur cette ligne”.

Tous les caractères restants à imprimer sur la ligne doivent être des espaces. En conséquence, nous pouvons prendre la longueur totale de la ligne (qui correspond au nombre saisi par l’utilisateur) et soustraire le nombre de marques de hachage sur cette ligne, ce qui correspond au nombre d’espaces nécessaires. C’est la condition dans la première boucle for:

 for ( int space = 0; space <= user_i - tall; space++ ) // ~~~~~~~~~~~~~ // Number of spaces == number_user_entered - current_line 

Ensuite, nous devons imprimer le nombre de marques de hachage, qui est toujours égal à la ligne actuelle:

 for ( int hash = 0; hash <= tall; hash++ ) // ~~~~ // Remember, "tall" is the current line 

Tout cela est dans une boucle for qui ne se répète qu'une fois par ligne.

Renommer certaines des variables et introduire de nouveaux noms peut rendre tout cela beaucoup plus facile à comprendre:

 #include  int main ( void ) { int userProvidedNumber; printf ( "Hello there and welcome to the pyramid creator program\n" ); printf ( "Please enter a non negative INTEGER from 0 to 23\n" ); scanf ( "%d", &userProvidedNumber ); while ( userProvidedNumber < 0 || userProvidedNumber > 23 ) { scanf ( "%d", &userProvidedNumber ); } for ( int currentLine = 0; currentLine < userProvidedNumber; currentLine++ ) { int numberOfSpacesToPrint = userProvidedNumber - currentLine; int numberOfHashesToPrint = currentLine; for ( int space = 0; space <= numberOfSpacesToPrint; space++ ) { printf ( " " ); } for ( int hash = 0; hash <= numberOfHashesToPrint; hash++ ) { printf ( "#" ); } // We need to specify the printf("\n"); statement here printf ( "\n" ); } return 0; } 

Quelques choses:

  • Envisagez de “vérifier sur banc” ceci. C’est-à-dire, tracez vous-même dans les boucles et tracez les espaces et les marques de hachage. Pensez à utiliser du papier quadrillé pour cela. Je fais ça depuis 15 ans et je trace toujours des trucs de temps en temps sur du papier quand ils sont poilus.

  • La magie dans ceci est la valeur “user_i-tall” et “hash <= tall". Ce sont les conditions sur les deux boucles for internes comme les valeurs intermédiaires entre parenthèses. Notez ce qu'ils font:

  • Parce que tall “monte” à partir de la boucle la plus externe, en la soustrayant de user_i, la boucle qui affiche les espaces “descend”. C’est-à-dire, imprimer de moins en moins d’espaces au fur et à mesure.

  • Parce que tall est “en train de monter” et que, comme la boucle de hachage l’utilise en l’état, elle augmente également. C’est-à-dire imprimer plus de marques de hachage au fur et à mesure.

Donc, ignorez vraiment la majeure partie de ce code. C’est générique: la boucle externe compte simplement le haut, et la plupart des boucles internes ne font que l’initialisation de base (ie space = 0 et hash = 0), ou l’incrémentation de base (space ++ et hash ++).

Ce ne sont que les parties centrales des boucles internes qui importent, et ils utilisent le mouvement de tall depuis la boucle la plus externe pour s’incrémenter respectivement vers le haut et le bas, comme indiqué ci-dessus, créant ainsi la combinaison d’espaces et de marques de hachage nécessaires -pyramide. J’espère que cela t’aides!

Voici votre code, juste reformaté et dépouillé de commentaires:

 for(int tall = 0;tall 

Et le même code, avec une explication interjectée:

 // This is a simple loop, it will loop user_i times. // tall will be [0,1,...,(user_i - 1)] inclusive. // If we peek at the end of the loop, we see that we're printing a newline character. // In fact, each iteration of this loop will be a single line. for(int tall=0; tall 

Ce type de petits programmes est celui qui m’a toujours fasciné. Je pense qu’ils jouent un rôle essentiel dans la construction de la logique et comprennent comment, quand, où et dans quelle situation quelle boucle sera parfaite.
La meilleure façon de comprendre ce qui se passe est de déboguer manuellement chaque instruction.
Mais pour mieux comprendre permet de comprendre comment construire la logique pour cela, je fais quelques changements mineurs afin que nous puissions mieux le comprendre,

  • n est pas de lignes à imprimer dans la pyramide n =5
  • Remplacer les espaces vides ' ' par '-' (symbole tiret)

Maintenant, la pyramide va ressembler à quelque chose,

  ----# ---## --### -#### ##### 

Maintenant, les étapes pour concevoir la boucle,

  • Tout d’abord, nous devons imprimer n lignes, c’est-à-dire 5 lignes. La première boucle sera exécutée 5 fois.
    for (int rowNo = 0; rowNo < n; rowNo++ ) le numéro de ligne rowNo est similaire à tall dans votre boucle.
  • Dans chaque ligne, nous devons imprimer 5 caractères, mais de manière à obtenir le chiffre souhaité, si nous examinons de près la logique qui existe,
    rowNo=0 (qui est notre première ligne) nous avons 4 tirets et 1 hash
    rowNo=1 nous avons 3 tirets et 2 hash
    rowNo=2 nous avons 2 tirets et 3 hash
    rowNo=3 nous avons 1 tiret et 4 hash
    rowNo=4 nous avons 0 tiret et 5 hash
  • Une petite inspection peut révéler que pour chaque ligne désignée par rowNo nous devons imprimer n - rowNo - 1 tirets - et rowNo + 1 hashes # ,
    Par conséquent, dans notre première boucle for , nous devons avoir deux boucles, une pour imprimer des tirets et une pour imprimer des hachages.
    La boucle de for (int dashes= 0; dashes < n - rowNo - 1; dashes ++ ) sera for (int dashes= 0; dashes < n - rowNo - 1; dashes ++ ) , ici les dashes sont semblables à l’ space dans votre programme original
    La boucle de hachage sera for (int hash = 0; hash < rowNo + 1; dashes ++ ) ,
  • La dernière étape après chaque ligne consiste à imprimer un saut de ligne pour pouvoir passer à la ligne suivante.

Espérons que l’explication ci-dessus montre clairement comment les boucles for que vous avez écrites seront formulées.

Dans votre programme, il n’ya que des modifications mineures puis mon explication; dans mes boucles, j’ai utilisé moins d’opérateur < et vous avez utilisé <= opérateur, ce qui fait la différence d’une itération.

Vous devez utiliser l’indentation pour rendre votre code plus lisible et donc plus facile à comprendre.

Votre code user_i que des lignes user_i composées de user_i-tall+1 espaces, puis de tall+1 hachage. Parce que l’indice d’itération tall augmente à chaque passe, cela signifie qu’un autre hachage est imprimé. Ils sont alignés à droite car un espace est également laissé de côté. Par la suite, vous avez des rangées de hachage «en croissance» qui forment une pyramide.
Ce que vous faites est équivalent à ce pseudo-code:

 for every i between 0 and user_i do: print " " (user_i-i+1) times print "#" (i+1) times 

Analyser vos boucles:
La première boucle

 for ( int tall = 0; tall < user_i; tall++ ){...} 

contrôle le rang. La deuxième boucle

 for ( int space = 0; space <= user_i - tall; space++ ){...} 

pour que la colonne soit remplie par des espaces.
Pour chaque ligne, toutes les colonnes user_i - tall seront user_i - tall par des espaces.
Maintenant les colonnes restantes sont remplies par # par la boucle

 for ( int hash = 0; hash <= tall; hash++ ){...} 
 #include  // Please note spacing of // - functions braces // - for loops braces // - equations // - indentation int main(void) { // Char holds all the values we want // Also, declaire all your variables at the top unsigned char user_i; unsigned char tall, space, hash; // One call to printf is more efficient printf("Hello there and welcome to the pyramid creator program\n" "Please enter a non negative INTEGER from 0 to 23\n"); // This is suited for a do-while. Exercise to the reader for adding in a // print when user input is invalid. do scanf("%d", &user_i); while (user_i < 0 || user_i > 23); // For each level of the pyramid (starting from the top)... // Goes from 0 to user_i - 1 for (tall = 0; tall < user_i; tall++) { // We are going to make each line user_i + 2 characters wide // At tall = 0, this will be user_i + 1 characters worth of spaces // At tall = 1, this will be user_i + 1 - 1 characters worth of spaces // ... // At tall = user_i - 1, this will be user_i + 1 - (user_i - 1) characters worth of spaces for (space = 0; space <= user_i - tall; space++) printf(" "); // no '\n', so characters print right next to one another // because of using '<=' inequality // \_ // At tall = 0, this will be 0 + 1 characters worth of hashes // At tall = 1, this will be 1 + 1 characters worth of hashes // ... // At tall = user_i - 1, this will be user_i - 1 + 1 characters worth of spaces for (hash = 0; hash <= tall; hash++) printf("#"); // Level complete. Add a newline to start the next level printf("\n"); } return 0; } 

La réponse la plus simple à cette raison est qu’une boucle imprime des espaces comme celui-ci représenté par -.

 ---------- --------- -------- ------- ------ ----- 

etc. Les hachages sont imprimés comme ça

 ------# -----## ----### ---#### --##### -###### 

puisque la boucle d’impression de hachage à la deuxième étape doit imprimer autant de hachages que nécessaire pour compléter la pyramide, elle peut être résolue par deux méthodes, l’une en copiant / collant deux fois la boucle de hachage ou en faisant exécuter la boucle deux fois la prochaine fois

 for ( int hash = 0; hash <= tall*2; hash++ ) { printf ( "#" ); } 

La logique pour créer de telles boucles est simple: la boucle la plus externe imprime une ligne à la fois, les boucles internes étant responsables du contenu de chaque ligne. La boucle d'espace insère des espaces et la boucle de hachage ajoute un hachage à la fin des espaces. [Ma réponse peut être redondante car je n'ai pas lu les autres réponses avec autant de soin, elles étaient longues] Résultat:

  # ### ##### ####### ######### ########### 
 for ( int tall = 0; tall < user_i; tall++ ) { ... } 

Je pense à cela en langage naturel comme ceci:

  1. int tall = 0; // Commencez avec une valeur initiale de tall = 0
  2. tall
  3. tall ++; // À la fin de chaque boucle, incrémente grand puis revérifie la condition de l'étape 2 avant de faire une autre boucle.

Et avec une bonne indentation dans votre code, il est maintenant beaucoup plus facile de voir comment les boucles for sont nestedes. Les boucles internes vont être exécutées pour chaque itération de la boucle externe.