Skip to content

Commit 3ad47df

Browse files
authored
Merge pull request #39 from JulesFouchy/S2_TD5
S2 TD5
2 parents b238bac + fc99a42 commit 3ad47df

File tree

1 file changed

+25
-25
lines changed

1 file changed

+25
-25
lines changed

content/TDs/S2/05_hashAndAssociativeTables.md

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ title: TD5 - Hachage et tableaux associatifs
44

55
Dans ce TD nous allons mettre en pratique les notions vues en cours sur les tables de hachage et les tables associatives.
66

7-
## Exercice 1 (fonction de hachage)
7+
## Exercice 1 (Fonction de hachage)
88

99
1. Écrire une fonction de hachage qui prend en paramètre une chaîne de caractères, fait la somme des valeurs ASCII des caractères et renvoie un entier compris entre 0 et un maximum donné nommé `max` (le type de retour du hash doit être `size_t`).
1010
> Nous utiliserons une simple somme des codes ASCII des caractères suivie d'un modulo pour obtenir un entier compris entre 0 et `max`.
@@ -13,14 +13,14 @@ size_t folding_string_hash(std::string const& s, size_t max);
1313
```
1414
1515
> $$
16-
> \text{hash}(s) = \sum_{i=0}^{n-1} (s[i] \mod m)
16+
> \text{hash}(s) = (\sum_{i=0}^{n-1} s[i]) \mod m
1717
> $$
1818
> Avec:
1919
> - $s$ la chaîne de caractères
2020
> - $n$ la taille de la chaîne de caractères
2121
> - $s[i]$ le code ASCII du caractère à l'index $i$ dans la chaîne de caractères
2222
23-
Ce que nous venons de faire s'appel la technique dite de **folding** (pliage en français). Cela consiste à découper notre donnée en plusieurs parties, calculer une valeur(hash) pour chacune de ces parties, sommer ces valeurs et enfin appliquer un modulo pour obtenir un entier compris entre 0 et `max`. Ici on traite une chaîne de caractère, on va donc faire la somme des valeurs de hachage de chaque caractère.
23+
Ce que nous venons de faire s'appelle la technique dite de **folding** (pliage en français). Cela consiste à découper notre donnée en plusieurs parties, calculer une valeur (hash) pour chacune de ces parties, sommer ces valeurs et enfin appliquer un modulo pour obtenir un entier compris entre 0 et `max`. Ici on traite une chaîne de caractère, on va donc faire la somme des valeurs de hachage de chaque caractère.
2424
2525
:::info
2626
On veux se ramener à un entier compris entre 0 et `max` car cette valeur hachée sert généralement d'index dans un tableau (table de hachage). Hors on souhaite un tableau de taille "raisonnable" en mémoire, donc on limite la taille de ce tableau à `max`.
@@ -34,7 +34,7 @@ size_t folding_string_ordered_hash(std::string const& s, size_t max);
3434
```
3535

3636
> $$
37-
> \text{hash}(s) = \sum_{i=0}^{n-1} ((s[i] * (i+1)) \mod m)
37+
> \text{hash}(s) = (\sum_{i=0}^{n-1} s[i] \times (i+1)) \mod m
3838
> $$
3939
>
4040
> Ici j'utilise `i+1` pour éviter que la position 0 (le 1er caractère) ne soit pas prise en compte dans le calcul du hash car multipliée par 0.
@@ -43,21 +43,21 @@ size_t folding_string_ordered_hash(std::string const& s, size_t max);
4343

4444
> Voila le prototype de la fonction à écrire:
4545
```cpp
46-
size_t polynomial_rolling_hash(const std::string& s, size_t p, size_t m);
46+
size_t polynomial_rolling_hash(std::string const& s, size_t p, size_t m);
4747
```
4848
4949
> Nous allons utiliser la technique dite de **polynomial rolling hash**. Cette technique consiste à calculer le hash d'une chaîne de caractères en incorporant la position d'une manière plus complexe pour éviter les collisions. On va donc multiplier la valeur de chaque caractère par une puissance de `p` qui dépend de la position du caractère dans la chaîne de caractères.
5050
> Pour cela, nous allons utiliser la formule suivante:
5151
> $$
52-
> \text{hash}(s) = \sum_{i=0}^{n-1} ((s[i] \times p^i) \mod m)
52+
> \text{hash}(s) = (\sum_{i=0}^{n-1} s[i] \times p^i) \mod m
5353
> $$
5454
>
5555
> Avec:
5656
> - $p$ un nombre (généralement un nombre premier)
5757
> - $m$ un nombre (généralement une puissance de 2)
5858
5959
:::warning
60-
On ne veux pas utiliser la fonction [`std::pow`](https://en.cppreference.com/w/c/numeric/math/pow) de la bibliothèque standard car elle est "lente" est fonctionne avec des **flottants** (ce qui nous ferait faire des conversions inutiles).
60+
On ne veux pas utiliser la fonction [`std::pow`](https://en.cppreference.com/w/c/numeric/math/pow) de la bibliothèque standard car elle est "lente" et fonctionne avec des **flottants** (ce qui nous ferait faire des conversions inutiles).
6161
6262
De plus on ne veux pas recalculer la puissance de `p` à chaque itération car c'est des calculs inutiles. Admettons qu'il faille $n-1$ multiplications pour calculer $p^n$ ($p \times p \times p = p^3$) et que l'on recalculait à chaque fois la puissance. Pour une chaîne de taille $n$ on ferait donc $1 + 2 + \ldots + (n-2) + (n-1)$ multiplications. On peut simplifier cette somme en $n(n-1)/2$ ce qui est de l'ordre de $n^2$ multiplications. On veut éviter ça.
6363
@@ -66,7 +66,7 @@ On va donc utiliser une variable `power` (initialisée à 1 au début) et multip
6666
:::
6767
6868
:::tip choix de `p` et `m`
69-
Le choix de `p` et `m` va influencer la qualité (collision) et les performances de notre fonction de hachage. Sans rentrer dans les détails, on choisit généralement `p` un **nombre premier** et `m` un nombre suffisamment grand pour éviter les collisions tout en restant petit pour rester performant et éviter des erreur numériques. Par exemple, on peut choisir `p` = **31** et `m` = **10^9 + 9** (que l'on peut noter `1e9 + 9` en C++ ou tout simplement `1000000009`).
69+
Le choix de `p` et `m` va influencer la qualité (probabilité de collision) et les performances de notre fonction de hachage. Sans rentrer dans les détails, on choisit généralement `p` un **nombre premier** et `m` un nombre suffisamment grand pour éviter les collisions tout en restant petit pour rester performant et éviter des erreur numériques. Par exemple, on peut choisir `p` = **31** et `m` = **10^9 + 9** (que l'on peut noter `1e9 + 9` en C++ ou tout simplement `1000000009`).
7070
:::
7171
7272
## Exercice 2 (Analyse du nombre d'insectes)
@@ -95,7 +95,7 @@ enum class Insect {
9595
Je vous fournis également une liste des valeurs de l'énumération `Insect` sous forme de vecteur (pour pouvoir itérer sur les valeurs de l'énumération et éviter de faire des `static_cast` pour obtenir les valeurs de l'énumération à partir d'un entier (index) ou encore avoir le nombre d'éléments de l'énumération). Rappels sur les cast avec enum [ici](/Lessons/S1/Variables/#cast-et-enum).
9696
```cpp
9797
#include <vector>
98-
const std::vector<Insect> insect_values {
98+
std::vector<Insect> const insect_values {
9999
Insect::ClassicBee,
100100
Insect::Ladybug,
101101
Insect::Butterfly,
@@ -118,7 +118,7 @@ Il n'existe pas de fonction dans la bibliothèque standard C++ pour obtenir le n
118118
#include <unordered_map>
119119
#include <string>
120120

121-
const std::unordered_map<Insect, std::string> insect_to_string = {
121+
std::unordered_map<Insect, std::string> const insect_to_string = {
122122
{Insect::ClassicBee, "ClassicBee"},
123123
{Insect::Ladybug, "Ladybug"},
124124
{Insect::Butterfly, "Butterfly"},
@@ -138,7 +138,7 @@ Cette liste se présente sous forme d'un vecteur avec les nombres d'individus at
138138

139139
```cpp
140140
#include <vector>
141-
const std::vector<int> expected_insect_counts {
141+
std::vector<int> const expected_insect_counts {
142142
75, // ClassicBee
143143
50, // Ladybug
144144
100, // Butterfly
@@ -166,25 +166,25 @@ Voilà une fonction qui génère une liste d'observations aléatoires pour simul
166166
#include <iterator>
167167

168168
std::vector<std::pair<Insect, int>> get_insect_observations(
169-
const size_t number_of_observations,
169+
size_t const number_of_observations,
170170
std::vector<float> const& insect_probabilities,
171-
const unsigned int seed = std::random_device{}()) {
171+
unsigned int const seed = std::random_device{}()
172+
) {
172173
// Create a random engine with a given seed
173-
std::default_random_engine random_engine(seed);
174+
std::default_random_engine random_engine{seed};
174175

175-
auto randInsectIndex { std::bind(std::discrete_distribution<size_t>{insect_probabilities.begin(), insect_probabilities.end()}, random_engine) };
176+
auto rand_insect_index { std::bind(std::discrete_distribution<size_t>{insect_probabilities.begin(), insect_probabilities.end()}, random_engine) };
176177

177178
std::vector<std::pair<Insect, int>> observations {};
178179
observations.reserve(number_of_observations);
179180

180181
for(size_t i {0}; i < number_of_observations; ++i) {
181-
size_t const random_insect_index { randInsectIndex() };
182+
size_t const random_insect_index { rand_insect_index() };
182183
Insect const random_insect { insect_values[random_insect_index] };
183184
184-
//If we have already seen the same insect, increment the count on the last observation
185-
auto& previous_observation { observations.back() };
186-
if(previous_observation.first == random_insect) {
187-
previous_observation.second++;
185+
// If we have already seen the same insect, increment the count on the last observation
186+
if(!observations.empty() && observations.back().first == random_insect) {
187+
observations.back().second++;
188188
i -= 1;
189189
} else {
190190
observations.push_back({random_insect, 1});
@@ -202,7 +202,7 @@ On utilisera la valeur de l'énumération `Insect` comme **clé** dans la table
202202
:::
203203

204204
:::info
205-
Le paramètre `seed` de la fonction `get_insect_observations` permet de fixer la graine du générateur de nombres aléatoires. Cela permet de reproduire les mêmes observations à chaque exécution du programme. Si vous ne spécifiez pas de graine, le générateur de nombres aléatoires utilisera une graine aléatoire à chaque exécution du programme sinon vous pouvez le fixer à une valeur de votre choix pour obtenir les mêmes observations à chaque exécution.
205+
Le paramètre `seed` de la fonction `get_insect_observations` permet de fixer la graine du générateur de nombres aléatoires. Cela permet de reproduire les mêmes observations à chaque exécution du programme. Si vous ne spécifiez pas de graine, le générateur de nombres aléatoires utilisera une graine aléatoire à chaque exécution du programme.
206206
:::
207207

208208
3. Utiliser de nouveau la fonction `probabilities_from_count` pour obtenir les probabilités des insectes observés. Il faudra au préalable convertir le résultat de la table de hachage en un vecteur de comptage pour pouvoir utiliser la fonction `probabilities_from_count`.
@@ -228,7 +228,7 @@ Vous pouvez utiliser l'include `<iomanip>` pour formater l'affichage des nombres
228228
Ajouter `std::cout << std::fixed << std::setprecision(3);` avant d'afficher les nombres pour afficher les nombres avec 3 chiffres après la virgule.
229229
:::
230230

231-
## Exercice 3 (hash sur une structure)
231+
## Exercice 3 (Hash sur une structure)
232232

233233
Donnons nous les enums et structures suivantes:
234234

@@ -264,7 +264,7 @@ struct Card {
264264
265265
L'idée de cet exercice est de créer une fonction de hachage pour la structure `Card` pour que l'on puisse utiliser cette structure comme clé dans une table de hachage.
266266
267-
la bibliothèque standard C++ fournit une fonction de hachage pour les types de base (entiers, flottants, etc.) et les chaînes de caractères. Mais elle ne fournit pas de fonction de hachage pour nos structures.
267+
La bibliothèque standard C++ fournit une fonction de hachage pour les types de base (entiers, flottants, etc.) et les chaînes de caractères. Mais elle ne fournit pas de fonction de hachage pour nos structures.
268268
269269
De la même façon que l'on a surchargé les opérateurs pour nos structures, on va pouvoir surcharger la fonction de hachage de notre structure.
270270
@@ -330,11 +330,11 @@ std::string card_name(Card const& card) {
330330
unsigned int card_value {(static_cast<unsigned int>(card.value)+2) % 14};
331331
332332
if (card_value < 10) {
333-
name += '0' + card_value;
333+
name += '0' + std::to_string(card_value);
334334
}else if (card_value == 10) {
335335
name += "10";
336336
}else if (card_value == 11) {
337-
name += 'V';
337+
name += 'J';
338338
}else if (card_value == 12) {
339339
name += 'Q';
340340
}else if (card_value == 13) {

0 commit comments

Comments
 (0)