From 51277075835f995c3a4c333bfe189dbff49c0bba Mon Sep 17 00:00:00 2001 From: Orestis <orestis.malaspinas@pm.me> Date: Tue, 17 Jan 2023 13:46:01 +0100 Subject: [PATCH] added cours 13 stuff --- slides/cours_13.md | 605 +++++++++++++++++++++++++++++++++++++++ slides/figs/fig_hash.png | Bin 0 -> 18046 bytes 2 files changed, 605 insertions(+) create mode 100644 slides/cours_13.md create mode 100644 slides/figs/fig_hash.png diff --git a/slides/cours_13.md b/slides/cours_13.md new file mode 100644 index 0000000..8cbbf96 --- /dev/null +++ b/slides/cours_13.md @@ -0,0 +1,605 @@ +--- +title: "Tables de hachage" +date: "2023-01-18" +--- + +# Tableau vs Table + +## Tableau + +* Chaque élément (ou valeur) est lié à un indice (la case du tableau). + +```C +annuaire tab[2] = { + "+41 22 123 45 67", "+41 22 234 56 78", ... +}; +tab[1] == "+41 22 123 45 67"; +``` + +## Table + +* Chaque élément (ou valeur) est lié à une clé. + +```C +annuaire tab = { +// Clé , Valeur + "Paul", "+41 22 123 45 67", + "Orestis", "+41 22 234 56 78", +}; +tab["Paul"] == "+41 22 123 45 67"; +tab["Orestis"] == "+41 22 234 56 78"; +``` + +# Table + +## Définition + +Structure de données abstraite où chaque *valeur* (ou élément) est associée à une *clé* (ou +argument). + +On parle de paires *clé-valeur* (*key-value pairs*). + +## Donnez des exemples de telles paires + +. . . + +* Annuaire (nom-téléphone), +* Catalogue (objet-prix), +* Table de valeur fonctions (nombre-nombre), +* Index (nombre-page) +* ... + +# Table + +## Opérations principales sur les tables + +* Insertion d'élément (`insert(clé, valeur)`{.C}), insère la paire `clé-valeur` +* Consultation (`get(clé)`{.C}), retourne la `valeur` correspondant à `clé` +* Suppression (`remove(clé)`{.C}), supprime la paire `clé-valeur` + +## Structure de données / implémentation + +Efficacité dépend de différents paramètres: + +* taille (nombre de clé-valeurs maximal), +* fréquence d'utilisation (insertion, consultation, suppression), +* données triées/non-triées, +* ... + +# Consultation séquentielle (`sequential_get`) + +## Séquentielle + +* table représentée par un (petit) tableau ou liste chaînée, +* types: `key_t` et `value_t` quelconques, et `key_value_t` + + ```C + typedef struct { + key_t key; + value_t value; + } key_value_t; + ``` +* on recherche l'existence de la clé séquentiellement dans le tableau, on + retourne la valeur. + +# Consultation séquentielle (`sequential_get`) + +## Implémentation? Une idée? + +. . . + +```C +bool sequential_get(int n, key_value_t table[n], key_t key, + value_t *value) +{ + int pos = n - 1; + while (pos >= 0) { + if (key == table[pos].key) { + *value = table[pos].value; + return true; + } + pos--; + } + return false; +} +``` + +. . . + +## Inconvénient? + +# Consultation séquentielle (`sequential_get`) + +## Exercice: implémenter la même fonction avec une liste chaînée + +Poster le résultat sur matrix. + +# Consultation dichotomique (`binary_get`) + +## Dichotomique + +* table représentée par un (petit) tableau trié par les clés, +* types: `key_t` et `value_t` quelconques, et `key_value_t` +* on recherche l'existence de la clé par dichotomie dans le tableau, on + retourne la valeur, +* les clés possèdent la notion d'ordre (`<, >, =` sont définis). + +# Consultation dichotomique (`binary_get`) + +\footnotesize + +## Implémentation? Une idée? + +. . . + +```C +bool binary_get1(int n, value_key_t table[n], key_t key, value_t *value) { + int top = n - 1, bottom = 0; + while (top > bottom) { + int middle = (top + bottom) / 2; + if (x > table[middle]) { + bottom = middle+1; + } else { + top = middle; + } + } + if (key == table[top].key) { + *value = table[top].value; + return true; + } else { + return false; + } +} +``` + +# Consultation dichotomique (`binary_get`) + +\footnotesize + +## Autre implémentation + +```C +bool binary_get2(int n, key_value_t table[n], key_t key, value_t *value) { + int top = n - 1, bottom = 0; + while (true) { + int middle = (top + bottom) / 2; + if (key > table[middle].key) { + bottom = middle + 1; + } else if (key < table[middle].key) { + top = middle; + } else { + *value = table[middle].value; + return true; + } + if (top < bottom) { + break; + } + } + return false; +} +``` + +## Quelle est la différence avec le code précédent? + +# Transformation de clé (hashing) + +## Problématique: Numéro AVS (13 chiffres) + +* Format: 106.3123.8492.13 + + ``` + Numéro AVS | Nom + 0000000000000 | ------- + ... | ... + 1063123849213 | Paul + ... | ... + 3066713878328 | Orestis + ... | ... + 9999999999999 | ------- + ``` + +## Quelle est la clé? Quelle est la valeur? + +. . . + +* Clé: Numéro AVS, Valeur: Nom. + +## Nombre de clés? Nombre de citoyens? Rapport? + +. . . + +* $10^{13}$ clés, $10^7$ citoyens, $10^{-5}$ ($10^{-3}\%$ de la table est + occupée) $\Rightarrow$ *inefficace*. +* Pire: $10^{13}$ entrées ne rentre pas dans la mémoire d'un + ordinateur. + +# Transformation de clé (hashing) + +## Problématique 2: Identificateurs d'un programme + +* Format: 8 caractères (simplification) + + ``` + Identificateur | Adresse + aaaaaaaa | ------- + ... | ... + resultat | 3aeff + compteur | 4fedc + ... | ... + zzzzzzzz | ------- + ``` + +## Quelle est la clé? Quelle est la valeur? + +. . . + +* Clé: Identificateur, Valeur: Adresse. + +## Nombre de clés? Nombre d'identificateur d'un programme? Rapport? + +. . . + +* $26^{8}\sim 2\cdot 10^{11}$ clés, $2000$ identificateurs, $10^{-8}$ ($10^{-6}\%$ de la table est + occupée) $\Rightarrow$ *un peu inefficace*. + +# Fonctions de transformation de clé (hash functions) + +* La table est représentée avec un tableau. +* La taille du tableau est beaucoup plus petit que le nombre de clés. +* On produit un indice du tableau à partir d'une clé: +$$ +h(key) = n,\quad n\in\mathbb{N}. +$$ +En français: on transforme `key` en nombre entier qui sera l'indice dans le +tableau correspondant à `key`. + +## La fonction de hash + +* La taille du domaine des clés est beaucoup plus grand que le domaine des + indices. +* Plusieurs indices peuvent correspondre à la **même clé**: + * Il faut traiter les **collisions**. +* L'ensemble des indices doit être plus petit ou égal à la taille de la table. + +## Une bonne fonction de hash + +* Distribue uniformément les clés sur l'ensemble des indices. + +# Fonctions de transformation de clés: exemples + +## Méthode par troncature + +\begin{align*} +&h: [0,9999]\rightarrow [0,9]\\ +&h(key)=\mbox{troisième chiffre du nombre.} +\end{align*} + +``` +Key | Index +0003 | 0 +1123 | 2 \ +1234 | 3 |-> collision. +1224 | 2 / +1264 | 6 +``` + +## Quelle est la taille de la table? + +. . . + +C'est bien dix oui. + +# Fonctions de transformation de clés: exemples + +## Méthode par découpage + +Taille de l'index: 3 chiffres. + +``` +key = 321 991 24 -> 321 + 991 + + 24 + ---- + 1336 -> index = 336 +``` + +## Devinez l'algorithme? + +. . . + +On part de la gauche: + +1. On découpe la clé en tranche de longueur égale à celle de l'index. +2. On somme les nombres obtenus. +3. On tronque à la longueur de l'index. + +# Fonctions de transformation de clés: exemples + +## Méthode multiplicative + +Taille de l'index: 2 chiffres. + +``` +key = 5486 -> key^2 = 30096196 -> index = 96 +``` + +On prend le carré de la clé et on garde les chiffres du milieu du résultat. + +# Fonctions de transformation de clés: exemples + +## Méthode par division modulo + +Taille de l'index: `N` chiffres. + +``` +h(key) = key % N. +``` + +## Quelle doit être la taille de la table? + +. . . + +Oui comme vous le pensiez au moins `N`. + +# Traitement des collisions + +## La collision + +``` +key1 != key2, h(key1) == h(key2) +``` + +## Traitement (une idée?) + +. . . + +* La première clé occupe la place prévue dans le tableau. +* La deuxième (troisième, etc.) est placée ailleurs de façon **déterministe**. + +Dans ce qui suit la taille de la table est `table_size`. + +# La méthode séquentielle + +\footnotesize + +## Comment ça marche? + +* Quand l'index est déjà occupé on regarde sur la position suivante, jusqu'à en + trouver une libre. + +```C +index = h(key); +while (table[index].state == OCCUPIED && table[index].key != key) { + index = (index + 1) % table_size; // attention à pas dépasser +} +table[index].key = key; +table[index].state = OCCUPIED; +``` + +## Problème? + +. . . + +* Regroupement d'éléments (clustering). + +# Méthode linéaire + +\footnotesize + +## Comment ça marche? + +* Comme la méthode séquentielle mais on "saute" de `k`. + +```C +index = h(key); +while (table[index].state == OCCUPIED && table[index].key != key) { + index = (index + k) % table_size; // attention à pas dépasser +} +table[index].key = key; +table[index].state = OCCUPIED; +``` + +## Quelle valeur de `k` éviter? + +. . . + +* Une valeur où `table_size` est multiple de `k`. + +Cette méthode répartit mieux les regroupements au travers de la table. + +# Méthode du double hashing + +\footnotesize + +## Comment ça marche? + +* Comme la méthode linéaire, mais `k = h2(key)` (variable). + +```C +index = h(key); +while (table[index].state == OCCUPIED && table[index].key != key) { + index = (index + h2(k)) % table_size; // attention à pas dépasser +} +table[index].key = key; +table[index].state = OCCUPIED; +``` + +## Quelle propriété doit avoir `h2`? + +## Exemple + +```C +h2(key) = (table_size - 2) - key % (table_size -2) +``` + +# Méthode pseudo-aléatoire + +\footnotesize + +## Comment ça marche? + +* Comme la méthode linéaire mais on génère `k` pseudo-aléatoirement. + + ```C + index = h(key); + while (table[index].state == OCCUPIED && table[index].key != key) { + index = (index + random_number) % table_size; + } + table[index].key = key; + table[index].state = OCCUPIED; + ``` + +## Comment s'assurer qu'on va bien retrouver la bonne clé? + +. . . + +* Le germe (seed) de la séquence pseudo-aléatoire doit être le même. +* Le germe à choisir est l'index retourné par `h(key)`. + + ```C + srand(h(key)); + while { + random_number = rand(); + } + ``` + +# Méthode quadratique + +* La fonction des indices de collision est de degré 2. +* Soit $J_0=h(key)$, les indices de collision se construisent comme: + + ```C + J_i = J_0 + i^2 % table_size, i > 0, + J_0 = 100, J_1 = 101, J_2 = 104, J_3 = 109, ... + ``` + +## Problème possible? + +. . . + +* Calculer le carré peut-être "lent". +* En fait on peut ruser un peu. + +# Méthode quadratique + +\footnotesize + +```C +J_i = J_0 + i^2 % table_size, i > 0, +J_0 = 100 + \ + d_0 = 1 + / \ +J_1 = 101 Delta = 2 + \ / + d_1 = 3 + / \ +J_2 = 104 Delta = 2 + \ / + d_2 = 5 + / \ +J_3 = 109 Delta = 2 + \ / + d_3 = 7 + / +J_4 = 116 +-------------------------------------- +J_{i+1} = J_i + d_i, +d_{i+1} = d_i + Delta, d_0 = 1, i > 0. +``` + +# Méthode de chaînage + +## Comment ça marche? + +* Chaque index de la table contient un pointeur vers une liste chaînée + contenant les paires clés-valeurs. + +## Un petit dessin + +``` + + + + + + + + + + + +``` + +# Méthode de chaînage + +## Exemple + +On hash avec la fonction `h(key) = key % 11` (`key` est le numéro de la lettre +de l'alphabet) + +``` + U | N | E | X | E | M | P | L | E | D | E | T | A | B | L | E + 10 | 3 | 5 | 2 | 5 | 2 | 5 | 1 | 5 | 4 | 5 | 9 | 1 | 2 | 1 | 5 +``` + +## Comment on représente ça? (à vous) + +. . . + +{width=80%} + +# Méthode de chaînage + +Avantages: + +* Si les clés sont grandes l'économie de place est importante (les places vides + sont `NULL`). +* La gestion des collisions est conceptuellement simple. +* Pas de problème de regroupement (clustering). + +# Exercice 1 + +* Construire une table à partir de la liste de clés suivante: + ``` + R, E, C, O, U, P, A, N, T + ``` + +* On suppose que la table est initialement vide, de taille $n = 13$. +* Utiliser la fonction $h1(k)= k \mod 13$ où k est la $k$-ème lettre de l'alphabet et un traitement séquentiel des collisions. + +# Exercice 2 + +* Reprendre l'exercice 1 et utiliser la technique de double hachage pour traiter + les collisions avec + +\begin{align*} +h_1(k)&=k\mod 13,\\ +h_2(k)&=1+(k\mod 11). +\end{align*} +* La fonction de hachage est donc $h(k)=(h(k)+h_2(k)) \% 13$ en cas de + collision. + + +# Exercice 3 + +* Stocker les numéros de téléphones internes d'une entreprise suivants dans un +tableau de 10 positions. +* Les numéros sont compris entre 100 et 299. +* Soit $N$ le numéro de téléphone, la fonction de hachage est +$$ +h(N)=N\mod 10. +$$ +* La fonction de gestion des collisions est +$$ +C_1(N,i)=(h(N)+3\cdot i)\mod 10. +$$ +* Placer 145, 167, 110, 175, 210, 215 (mettre son état à occupé). +* Supprimer 175 (rechercher 175, et mettre son état à supprimé). +* Rechercher 35. +* Les cases ni supprimées, ni occupées sont vides. +* Expliquer se qui se passe si on utilise? +$$ +C_1(N,i)=(h(N)+5\cdot i)\mod 10. +$$ + diff --git a/slides/figs/fig_hash.png b/slides/figs/fig_hash.png new file mode 100644 index 0000000000000000000000000000000000000000..92e82b5949e733c846b92a3d6daa7601db740a4e GIT binary patch literal 18046 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%0D3-P+dNyjB6&zkTb>gD^?WqBc z$Z8m+j`wMeIf~QV`Wetb2J<9V`e}9mTVjL;-&M~886wW<p{k=G0$@Cq-7#lz;y0KR z_N@oNc!631fmjBN=Kr^De+T+7S65kGU8-OEY?AG|XmoVcP8kSevc%vi>WRre>FC?Q zkDNFe1d^el=H})?u{z7k%j2m)XLjXz6W&w_0-akTk!S-x{)<k3!sL5v4?FjV(-WXB O=(UW}%Mz)#AO07Ft3|v3 literal 0 HcmV?d00001 -- GitLab