• Aucun résultat trouvé

Personnalisation à la demande

Dans le document La programmation en C++ moderne (Page 144-149)

i Le cas de std::list

II.10.2. Personnalisation à la demande

Jusqu’ici, nos algorithmes sont figés. Par exemple, std::sort ne trie que dans l’ordre croissant.

Mais les concepteurs de la bibliothèque standard ont pensé à ça et ont écrit leurs algorithmes de telle manière qu’on peut personnaliser leur comportement. Mine de rien, cette possibilité les rend encore plus intéressants! Voyons ça ensemble.

II.10.2.1. Le prédicat, à la base de la personnalisation des algorithmes

Un prédicat est une expression qui prend, ou non, des arguments et renvoie un booléen. Le résultat sera donctrue oufalse, si la condition est vérifiée ou non. Littéralement, un prédicat répond à des questions ressemblant à celles ci-dessous.

— Est-ce que l’élément est impair?

— Est-ce que les deux éléments aet b sont égaux?

— Est-ce que les deux éléments sont triés par ordre croissant?

La très grande majorité des algorithmes utilisent les prédicats, même ceux vu plus haut, sans que vous le sachiez. Par exemple,std::sortutilise, par défaut, le prédicat<, qui compare si un élémenta est strictement inférieur à un autre élément b, ce qui explique pourquoi l’algorithme trie une collection dans l’ordre croissant. Mais il est tout à fait possible de lui indiquer de faire autre chose.

La bibliothèque standard contient déjà quelques prédicats, regroupés dans le fichier d’en-tête

<functional>. Nous apprendrons, plus tard dans ce tutoriel, à écrire nos propres prédicats.

II.10.2.2. Trier une liste dans l’ordre décroissant

Faisons connaissance avec std::greater. Il s’agit d’un foncteur, c’est-à-direun objet qui agit comme une fonction. Les détails internes ne nous intéressent pas pour l’instant, nous y reviendrons plus tard, quand nous aurons les connaissances requises.

Ce foncteur compare deux éléments et retournetruesi le premier est plus grand que le deuxième, sinon false. Voici quelques exemples. Notez qu’on doit préciser, entre chevrons <>, le type des objets comparés.

7 // On peut l'utiliser directement.

8 std::cout << std::greater<int>{}(2, 3) << std::endl;

9 std::cout << std::greater<int>{}(6, 3) << std::endl;

10 std::cout << std::greater<double>{}(0.08, 0.02) << std::endl;

1112 // Ou bien à travers une variable, qu'on appelle comme une fonction.

13 std::greater<int> foncteur {};

14 std::cout << foncteur(-78, 9) << std::endl;

15 std::cout << foncteur(78, 8) << std::endl;

1617 return 0;

18 }

Afin de personnaliser le comportement de std::sort, notre fonction de tri vue plus haut, il suffit de lui passer ce foncteur. Observez par vous-mêmes.

1 #include <algorithm>

8 std::vector<int> tableau { -8, 5, 45, -945 };

9 std::sort(std::begin(tableau), std::end(tableau), std::greater<int> {});

10

11 for (auto x : tableau)

12 {

13 std::cout << x << std::endl;

14 }

1516 return 0;

17 }

II.10.2.3. Somme et produit

Dans le fichier d’en-tête<numeric>, il existe une fonction répondant au doux nom destd::ac cumulate, qui permet de faire la somme d’un ensemble de valeurs, à partir d’une valeur de départ. Ça, c’est l’opération par défaut, mais on peut très bien lui donner un autre prédicat, comme std::multiplies, afin de calculer le produit des éléments d’un ensemble.

1 #include <functional>

8 std::vector<int> const nombres { 1, 2, 4, 5, 10 };

109 // On choisit 0 comme élément de départ pour calculer la somme, car 0 est l'élément neutre de l'addition.

11 auto somme { std::accumulate(std::cbegin(nombres), std::cend(nombres), 0) };

12 std::cout << somme << std::endl;

1314 // On choisit 1 comme élément de départ pour calculer le produit, car 1 est l'élément neutre de la multiplication.

15 auto produit { std::accumulate(std::cbegin(nombres), std::cend(nombres), 1, std::multiplies<int> {}) };

16 std::cout << produit << std::endl;

1718 return 0;

19 }

II.10.2.4. Prédicats pour caractères

Il existe, dans le fichier d’en-tête <cctype>, des prédicats vérifiant des conditions uniquement sur un caractère. Ils permettent de vérifier si un caractère est une lettre, un chiffre, un signe de ponctuation, etc. Voyez par vous-mêmes.

1 #include <cctype>

8 std::cout << "Est-ce que " << lettre << " est une minuscule ? "

<< islower(lettre) << std::endl;

9 std::cout << "Est-ce que " << lettre << " est une majuscule ? "

<< isupper(lettre) << std::endl;

10 std::cout << "Est-ce que " << lettre << " est un chiffre ? " <<

isdigit(lettre) << std::endl;

1112 char const chiffre { '7' };

1314 std::cout << "Est-ce que " << chiffre << " est un chiffre ? "

<< isdigit(chiffre) << std::endl;

15 std::cout << "Est-ce que " << chiffre <<

" est un signe de ponctuation ? " << ispunct(chiffre) <<

std::endl;

16

17 return 0;

18 }

i

Particularité héritée

Ces fonctions sont héritées du C, c’est pour cela qu’elles ne sont pas précédées de l’habituel std::. C’est également la raison pour laquelle elles renvoient des entiers et non des booléens, comme on aurait pu s’y attendre.

On peut faire des choses intéressantes avec ça. Par exemple, en utilisant l’algorithmestd::all_of, qui vérifie que tous les éléments d’un ensemble respectent une propriété, on peut vérifier si tous les caractères d’une chaîne sont des chiffres, pour valider qu’il s’agit d’un numéro de téléphone.

1 #include <algorithm>

8 std::string const numero { "0185017204" };

9 if (std::all_of(std::cbegin(numero), std::cend(numero), isdigit))

10 {

11 std::cout << "C'est un numéro de téléphone correct." <<

std::endl;

12 }

13 else

14 {

15 std::cout << "Entrée invalide." << std::endl;

16 }

17

18 return 0;

19 }

i

Nos propres prédicats

Plus loin dans le cours, nous apprendrons à écrire nos propres prédicats, ce qui nous donnera encore plus de pouvoir sur nos algorithmes. Mais patience.

II.10.3. Exercices

II.10.3.1. Palindrome

Vous devez tester si une chaîne de caractères est un palindrome, c’est-à-dire un mot pouvant être lu indifféremment de gauche à droite ou de droite à gauche. Un exemple: «kayak.»

Correction palindrome

II.10.3.2.

string_trim

 — Suppression des espaces

Allez, un exercice plus dur, pour vous forger le caractère. Le but est de reproduire une fonction qui existe dans de nombreux autres langages, comme C# ou Python, et qui permet de supprimer les espaces, au début et à la fin d’une chaîne de caractères. Cela signifie non seulement les espaces ' 'mais aussi les tabulations '\t', les retours à la ligne'\n', etc. Pour vous aider, vous aller avoir besoin de deux choses.

— Le prédicatisspace, pour savoir si un caractère est un type d’espace ou non.

— L’algorithme std::find_if_not, qui permet de trouver, dans un ensemble délimité par deux itérateurs, le premier caractère qui ne respecte pas une condition.

Pour vous faciliter la vie, découper l’exercice en deux. Faites d’abord la fonction permettant de supprimer tous les espaces à gauche, puis pour ceux à droite. Bon courage.

Correction string_trim

II.10.3.3. Couper une chaîne

Une autre opération courante, et qui est fournie nativement dans d’autres langages comme Python ou C#, consiste à découper une chaîne de caractères selon un caractère donné. Ainsi, si je coupe la chaîne "Salut, ceci est une phrase." en fonction des espaces, j’obtiens en résultat ["Salut,", "ceci", "est", "une", "phrase.].

Le but est donc que vous codiez un algorithme qui permet de faire ça. Vous allez notamment avoir besoin de la fonctionstd::distance, qui retourne la distance entre deux itérateurs, sous forme d’un nombre entier.

Correction découpage de chaînes

II.10.3.4. En résumé

— Les itérateurs sont des abstractions représentant un pointeur sur un élément d’un conteneur. Ils rendent ainsi l’usage du conteneur plus transparent.

— Deux fonctions,std::beginetstd::end, sont extrêmement utilisées avec les itérateurs, car retournant un pointeur sur, respectivement, le début et la fin d’une collection.

— Les itérateurs demandent de la rigueur pour ne pas manipuler des éléments qui sont hors de la collection, ce qui entrainerait des comportements indéterminés.

— La bibliothèque standard contient des algorithmes, déjà programmés et très optimisés, qu’on peut appliquer à des collections.

— Nous pouvons les personnaliser grâce à des prédicats fournis dans la bibliothèque standard.

Dans le document La programmation en C++ moderne (Page 144-149)