Choisissez votre style : colorisé, impression

Correction 9
Tableaux de taille fixe et chaînes de caractères


Les solutions suivantes ne sont évidemment pas uniques. Si vous en trouvez de meilleures (ou si vous trouvez des erreurs), faites-le savoir ! Merci.
Par ailleurs, les solutions proposées correspondent à l'état de vos connaissances au moment où la série est abordée. D'autres solutions pourront être envisagées une fois de nouveaux concepts acquis.
Exercice 1 Exercice 2 Exercice 3 Exercice 4

Exercice 1 : Générateur automatique de lettres (fonctions et chaînes de caractères, niveau 1)

Exercice n°16 (pages 54 et 217) de l'ouvrage C++ par la pratique.
(pages 55 et 217 dans la 2e édition).
Exercice n°17 du MOOC

Première version du code :

#include <iostream>
using namespace std;
 
void genereLettre()
{
  cout 
    << "Bonjour chère Mireille,"                                     << endl
    << "Je vous écris à propos de votre cours."                      << endl
    << "Il faudrait que nous nous voyons le 18/12 pour en discuter." << endl
    << "Donnez-moi vite de vos nouvelles !"                          << endl
    << "Amicalement, John."                                          << endl;
}
 
int main()
{
  genereLettre();
  return 0;
}

Seconde version du code :

#include <iostream>
#include <string>
using namespace std;

// Note : depuis C++17, il est préférable d'utiliser une string_view
// en guise et place d'une référence constante à une string
void genereLettre(bool masculin, const string& destinataire, string sujet,
                  unsigned int jour, unsigned int mois,
                  const string& politesse, const string& auteur)
{
  cout << "Bonjour ";
  if (masculin) cout << "cher";
  else cout << "chère";
  cout << " " << destinataire << "," << endl;
 
  cout 
    << "Je vous écris à propos de " << sujet << endl
    << "Il faudrait que nous nous voyons le " << jour << "/" << mois 
    << " pour en discuter." << endl
    << "Donnez-moi vite de vos nouvelles !" << endl
    << politesse << ", " << auteur << endl;
}
 
int main()
{
  genereLettre(false, "Mireille", "votre cours" , 18, 12,
               "Amicalement", "John");
  cout << endl;
  genereLettre(true, "John", "votre demande de rendez-vous", 16, 12,
               "Sincèrement", "Mireille");
  return 0;
}
 

Exercice 2 : Segmentation en mots (string, niveau 2)

Exercice n°19 (pages 56 et 219) de l'ouvrage C++ par la pratique.
(pages 56 et 220 dans la 2e édition).
Exercice supplémentaire n°16 du MOOC
#include <iostream>
#include <string>
using namespace std;
 
bool nextToken(const string& str, int& from, int& len);
bool issep (char c); // teste si le caractère est un separateur
 
int main()
{
  string phrase;
 
  cout << "Entrez une chaîne : ";
  getline(cin,  phrase);
 
  cout << "Les mots de \"" << phrase << "\" sont :" << endl;
  int debut(0);
  int longueur(0);
  while (nextToken(phrase, debut, longueur)) {
    cout << "'" << phrase.substr(debut, longueur) << "'" << endl;
    debut += longueur;
  }
  return 0;
}
 
/* La fonction suivante teste si le caractère est un séparateur.
 *
 * Ecrire une fonction présente l'avantage de pouvoir redéfinir facilement 
 * la notion de séparateur (et éventuellement d'en définir plusieurs).
 */
bool issep (char c)
{
  return (c == ' ');
}
 
/* Il y a de multiples façons d'écrire cette fonction.
 * Nous trouvons celle-ci est assez élégante.
 */
bool nextToken(const string& str, int& from, int& len)
{
  const int taille(str.size());
 
  // saute tous les separateurs à partir de from
  while ((from < taille) and issep(str[from])) ++from;
 
  // avance jusqu'au prochain séparateur ou la fin de str
  len = 0;
  for (int i(from); ((i < taille) and not issep(str[i])); ++len, ++i);
 
  return (len != 0);
}
 

Commentaires

Le corrigé proposé pour nextToken est assez compact et nécessite d'avoir bien compris les structures de contrôle (while, for). Il peut être, de ce fait, un peu plus difficile à comprendre que d'autres solutions.

Comme indiqué, vous pouvez le faire de plein de façons différentes ; mais examinons justement celle proposée :

// Note : depuis C++17, string const& peut avantageusement être remplacé
// par string_view
bool nextToken (string const&; str, int& from, int& len)
{
   int const taille(str.size());

Bon, jusque là probablement que "ça va"... ; juste peut être préciser les arguments :

Continuons avec :

while ((from < taille) && issep(str[from])) ++from;

from est ici incrémenté(/augmenté) du nombre de séparateurs rencontrés. En effet, on ne peut pas commencer le nouveau « token » par un séparateur.

Comment fait-on pour sauter tous ces séparateurs ?
-> tant que le caractère courant est un séparateur, on avance. Ça, c'est le « while (issep(str[from])) ».

Mais il faut penser aussi à ne pas déborder de la chaîne (imaginez le cas où la chaîne ne contient que des séparateurs). Ça, c'est le « (from < taille) » dans le test du while.

Passons au bloc suivant. Son but est de trouver la fin du « token » (puisqu'on vient de trouver le début avec la boucle while précédente).

Cette fin de « token » est en fait indiquée par la longueur, len, du « token ». Au départ le « token » est vide (on a très bien pu sortir du while précédent par la condition « from ≥ taille ». Repensez encore une fois au cas d'une chaîne ne contenant aucun « token », mais uniquement des séparateurs). On a donc :

len = 0;

Puis on cherche la fin du « token » ; c'est-à-dire le prochain séparateur. C'est-à-dire que tant que l'on n'a pas de séparateur (« !issep(str[i]) »), on avance.

Ca veut dire quoi « on avance » ?
-> on passe au caractère suivant, ça c'est le « ++i », et on met à jour la taille du « token » en la faisant augmenter de 1, ça c'est le « ++len ».

D'où part-on ?
-> du caractère « from » précédemment trouvé.

Il ne reste plus qu'à ne pas oublier de ne pas déborder de la chaîne (« i < taille »), et le tour est joué :

for (int i(from); ((i < taille) && !issep(str[i])); ++len, ++i);

Pour essayer d'être encore plus clair, ceci peut aussi s'écrire de la façon moins compacte suivante (rappel : « from » représente le début de « token ») :

 bool continuer(true);
 int position_courante(from);
 
 do {
	// si on déborde de la chaine il faut s'arreter
	if (position_courante >= taille) {
		continuer = false ;
	}
 
	// si on rencontre un séparateur, il faut s'arreter
        // (c'est la fin du token)
	else if (issep(str[position_courante])) {
		continuer = false ;
	}
 
	// sinon, tout va bien, ...
	else {
		// ...on augmente la longueur du token de 1...
		len = len + 1;
 
		// ...et on passe au caractère suivant.
		++position_courante;
	}
 
 } while (continuer);
 

Voilà pour cette partie.

Et pour finir, on renvoie « true » si on a trouvé un « token », c.-à-d. si len n'est pas nulle, et « false » sinon. Soit :

return (len != 0);
qui revient exactement à formulation explicite mais inélégante  :
//Tournure à éviter

if (len != 0) {
	return true;
} else {
	return false;
}

Exercice 3 : placements sans recouvrements

Exercice n°21 (pages 58 et 224) de l'ouvrage C++ par la pratique.
#include <iostream>
#include <array>
using namespace std;
 
constexpr size_t DIM(10);
 
// --------------------------------------------------------------------
bool remplitGrille(array<array<bool, DIM>, DIM>& grille,
                   unsigned int ligne, unsigned int colonne,
                   char direction, unsigned int longueur)
{
  int dx, dy;
 
  switch (direction) {
  case 'N': dx = -1; dy =  0; break;
  case 'S': dx =  1; dy =  0; break;
  case 'E': dx =  0; dy =  1; break;
  case 'O': dx =  0; dy = -1; break;
  }
 
  unsigned int i(ligne);     // les coordonnées de la case...
  unsigned int j(colonne);   // ...à modifier
  unsigned int l;            // la longueur modifiée
 
  bool possible(true); // est-ce possible de mettre tout l'élément ?
 
  // avant de modifier la grille, il faut vérifier si c'est possible de
  // mettre tout l'objet.
  for (// Initialisation 
       l = 0;
 
       /* Condition de continuation. Vu que i et j sont des "unsigned" *
        * pas besoin de tester ici les condition (i >= 0) et (j >= 0), *
        * lesquelles sont forcément vraies                             */
       (possible) and (i < grille.size()) and (j < grille[0].size()) 
         and (l < longueur);
 
       // Incréments
       ++l, i += dx, j += dy) {
    if (grille[i][j]) // c'est-à-dire la grille est déjà occupée
    {
      possible = false;  // on ne peut donc pas mettre tout l'objet voulu
    }
  }
 
  /* Si l == longueur c'est qu'on a pu tout placer.
   * Il se pourrait en effet qu'on soit sorti de la boucle ci-dessus
   * parce que i >= DIM ou j >= DIM... ...ce qui n'a pas modifié
   * "possible" jusqu'ici.
   */
  possible = possible and (l == longueur); 
 
  if (possible) {
    // on met effectivement l'objet, plus besoin de test ici
    for (l = 0, i = ligne, j = colonne; l < longueur; 
         ++l, i += dx, j += dy) {
      grille[i][j] = true;
    }
  }
 
  return possible;
}
 
// --------------------------------------------------------------------
void initGrille(array<array<bool, DIM>, DIM>& grille) 
{
  for (auto& ligne : grille) 
    for (auto& cell : ligne) 
      cell = false;
}
 
// --------------------------------------------------------------------
void ajouterElements(array<array<bool, DIM>, DIM>& grille)
{
  int x, y;
  do {
    cout << "Entrez coord. x : ";
    cin >> x;
 
    if (x >= 0) {
      cout << "Entrez coord. y : ";
      cin >> y;
 
      if (y >= 0) {
 
        char dir;
        do {
          cout << "Entrez direction (N,S,E,O) : ";
          cin >> dir;
        } while ((dir != 'N') and (dir != 'S') and
                 (dir != 'E') and (dir != 'O'));
 
        cout << "Entrez taille : ";
        unsigned int l;
        cin >> l;
 
        cout << "Placement en (" << x << "," << y << ") direction "
             << dir << " longueur " << l << " -> ";
 
        if (remplitGrille(grille, x, y, dir, l))
          cout << "succès";
        else
          cout << "ECHEC";
        cout << endl;
 
      }
    }
  } while ((x >= 0) and (y >= 0));
}
 
// --------------------------------------------------------------------
void afficheGrille(const array<array<bool, DIM>, DIM>& grille)
{
  for (auto ligne : grille) {
    for (auto cell : ligne) {
      if (cell) cout << '#';
      else      cout << '.';
    }
    cout << endl;
  }
}
 
// --------------------------------------------------------------------
int main()
{
  array<array<bool, DIM>, DIM> grille;
  initGrille(grille);
  ajouterElements(grille);
  afficheGrille(grille);
  return 0;
}

Exercice 4 : LIEN PARTIE THEORIQUE : Conversions binaire <--> décimal (chaînes de caractères, niveau 1)

#include <iostream>
#include <string>
using namespace std;
 
/* ======================================================================
 * 1er algorithme
 * conversion d'un décimal positif en binaire
 */
string binary_nonnegative(int n)
{
  if (n <= 0) {
    return "0";
  }
 
  // ici n >= 1
  string output;
  while (n > 0) {
       if (n%2 == 1) {
           output = '1' + output;
       } else {
           output = '0' + output;
       }
    n /= 2;
  }
 
  return output;
}
 
/* ======================================================================
 * 2ème algorithme
 * conversion d'un binaire positif (pas de bit de signe) en décimal
 */
unsigned int decimal_nonnegative(string const& binary)
// Note: depuis C++-17, utiliser string_view au lieu de "string const&"
{
  unsigned int output(0);
  unsigned int power(1);
 
  if (not binary.empty()) {
    for (size_t i(binary.size() - 1); i < binary.size(); --i) {
      if (binary[i] == '1') {
        output += power;
      }
      power *= 2;
    }
  }
 
  return output;
}
 
/* ======================================================================
 * 3ème algorithme
 * complément à deux d'une chaîne binaire
 */
string twos_complement(string const& binary)
// Note: depuis  C++-17, utiliser string_view plutôt que "string const&"
{
  string output(binary); // commencer par une copie
 
  if (not binary.empty()) {
 
    // inverser tous les bits à gauche du "least-significant 1" (LS1)
 
    // chercher le LS1
 
    size_t i(output.size() - 1);
	/* La condition d'arrêt dans la boucle ci-dessous peut sembler étrange ...
	 Explication : lorsque le --i arrive en dessous de zéro (-1)
	 comme c'est un non-signé, en vertu du complément à deux, il devient
	 une très grande valeur positive
	 (-1  en non-signé pour un size_t vaut 18446744073709551615)
	 et sera de toute façon plus grand que la taille de output
	 ce qui permet de sortir de la boucle !
	 Alternativement, on aurait pu utiliser un int (entier signé) pour i
	 et formuler la condition comme while (i >= 0 and ...)
	*/
    while ((i < output.size()) and (output[i] != '1')) --i;
 
    // chercher le LS1
    --i;
 
    // ensuite inverser ceux qui sont avant
    while (i < output.size()) {
      if (output[i] == '1') {
        output[i] = '0';
      } else {
        output[i] = '1';
      }
      --i;
    }
  }
 
  return output;
}
 
/* ======================================================================
 * 4ème algorithme
 * conversion d'un décimal (signé) en binaire, en utilisant les algorithmes précédents
 */
string binary(int n)
{
  // Ajouter le bit de signe à la représentation "unsigned" correspondante
  if (n < 0) {
    return "1" + twos_complement(binary_nonnegative(-n));
  }
  return "0" + binary_nonnegative(n);
}
 
/* ======================================================================
 * 5ème algorithme
 * conversion d'un binaire (avec bit de signe) en décimal
 */
int decimal(string const& binary)
// Note: depuis C++-17,  string_view au lieu de  "string const&"
{
  // teste le bit de signe
  if (not binary.empty() and (binary[0] == '1')) {
    return -decimal_nonnegative(twos_complement(binary));
  }
 
  return decimal_nonnegative(binary);
}
 
/* ======================================================================
 * Le reste du code ne constitue pas le sujet principal de l'exercice
 * il s'agit d'utilitaires permettant de tester
 */
 
/* ======================================================================
 * Tool function: test an int value and its binary writing
 */
void test(int n)
{
  const string result(binary(n));
  cout << n << " is " << result << endl;
  cout << "and " << result << " is indeed " << decimal(result) << '.' << endl;
}
 
/* ======================================================================
 * Tool function: ask for some integer.
 */
int require_int()
{
  int value(0);
  cout << "Enter some integer: ";
  cin >> value;
  return value;
}
 
/* ======================================================================
 * Tool function: ask to continue or not.
 */
bool go_ahead() {
  char read('x');
  do {
    cout << "Shall we continue (y/n)? ";
    cin >> read;
  } while ((read != 'y') and (read != 'Y') and
           (read != 'n') and (read != 'N'));
  return (read == 'y') or (read == 'Y');
}
 
// ======================================================================
int main()
{
  const int t(42);
  cout << binary_nonnegative(t) << endl;
  test(t);
  test(-t);
 
  do {
    test(require_int());
  } while (go_ahead());
 
  return 0;
}

Dernière mise à jour : $Date: 2025/10/31 18:10 $