Aller au menu - Aller au contenu

Icône Les tables de hachage

Mise à jour : 16/12/2010
Difficulté : Facile Facile Creative Commons BY-NC-SA
230 visites depuis 7 jours, dont 29 sur ce chapitre classé 394/786
QUOI ??? On en a pas fini avec les hashs depuis le dernier chapitre ?

Rassurez-vous, ici, on ne va pas parler de hachage mais de tables de hachage ! Il s'agit de tableaux qui, au lieu de contenir leurs valeurs à l'index 0, 1, 2, etc., contiennent leurs valeurs dans des index identifiés par une chaine de caractères (ou autre chose, on verra ça après) ! Eh oui ! Vous pourrez donc accéder aux données en faisant tableau["id"] au lieu de tableau[1] . Quel avantage ? Vous n'aurez plus besoin de stocker de valeurs pour retrouver vos données, dans le cas d'un tableau qui contient des données pas forcément prévisibles !
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

Théorie

Pour faire original, on va commencer par la théorie ( :-° ) !

Une table de hachage est un tableau dont l'index est représenté non pas par un nombre, comme d'habitude, mais par une chaîne de caractères (dans la plupart des cas). Ainsi, pour un tableau contenant dans l'ordre le prénom, le nom et l'âge d'une personne, on peut avoir le format suivant :
Tableau classique
IDContenu
0 Jean
1 Dupont
2 25

Table de hachage
CléContenu
prenom Jean
nom Dupont
age 25


NB : Dans une table de hachage, on appelle l'ID "clé".

C'est cool tout ça, mais... Je vois franchement pas le rapport avec le hachage.

En fait, l'ordinateur ne peut faire que des index numériques (avec des nombres). Le principe de la table de hachage est donc simple : hacher la clé pour avoir un index numérique ! Ainsi, le tableau suivant serait plus complet (dans ce code, on considère qu'il existe une fonction hacher(QString), qui prend en paramètre une chaine à hacher) :

Table de hachage
CléIDContenu
prenom hacher("prenom") Jean
nom hacher("nom") Dupont
age hacher("age") 25


Aucun rapport avec la sécurité, la cryptographie, et tout ça ?

Non ! Les tables de hachage n'ont rien à voir avec !

Avec Qt

Prêts à passer à la pratique ?

Qt gère les tables de hachage avec la classe QHash.

Pour commencer, voyons le prototype du constructeur :

Code : C++
1
QHash ()


Bon, ok, on pouvait pas faire plus simple...
Seulement, sa définition se fait un peu comme une QList (un exemple de QList ?) :

Code : C++
1
QList<QWidget*> liste; // crée une QList contenant des QWidget


Pour créer un QHash, il faut donc lui indiquer le type des valeurs qui seront stockées. Mais pas seulement ! Il faut aussi lui indiquer de quel type sera la clé ! Eh oui, on peut créer des tables de hachage dont les clés sont... n'importe quel type de variables, objets compris ! Les clés peuvent donc par exemple être des pointeurs sur des QPushButtons, ça ne posera aucun problème (eh oui, c'est ça que je vous cachais depuis tout à l'heure :-° ) !
Dans la plupart des cas, la clé reste une chaîne de caractères.

Pour créer une table de hachage, on aura donc un code ressemblant à celui-ci :

Code : C++
1
2
3
4
QHash<typeCle, typeValeur> table1; // clés de type typeCle, valeurs de type typeValeur
QHash<QString, int> table2; // clés de type QString, valeurs de type int
QHash<QString, QWidget*> table3; // clés de type QString, valeurs de type pointeur sur QWidget
QHash<QPushButton*, int> table4; // clés de type pointeur sur QPushButton, valeurs de type int


Ce code créera 4 tables de hachage (explications dans les commentaires).

J'imagine qu'ensuite, vous avez l'intention d'ajouter des valeurs, en supprimer, les lire, etc., n'est-ce pas ? Eh bien, il y a plusieurs manières de faire. On va commencer par... la première.

Méthode 1 : comme avec un tableau



La première méthode est de faire comme s'il s'agissait d'un simple tableau.

Cette méthode est déconseillée dans le cas où on lit une case qui peut ne pas exister, car la case inexistante est alors créée, ce qui prend de la mémoire pour rien. Je vous conseille donc de prendre l'habitude d'utiliser la 2nde méthode, même si connaître celle-ci peut être utile pour lire le code de quelqu'un d'autre par exemple.

Dans cette méthode, nous allons considérer qu'on a affaire à un tableau classique (mais extensible quand même, vous pouvez donc ajouter une valeur). Nous allons donc utiliser l'objet suivant :

Code : C++
1
QHash<QString, int> table;


Tiens, d'ailleurs, normalement, vous devriez être capables de me dire quel type de clés et quel type de valeurs attend cette table !

Solution :

Secret (cliquez pour afficher)
  • Les clés seront de type QString
  • Les valeurs seront de type int

Pour ajouter un item dans un tableau de ce genre, on ferait donc :

Code : C++
1
table["reponse"]=42; // la clé est "reponse", la valeur est "42".


Le code pour modifier la valeur associée à la clé "réponse" est exactement le même. Lorsqu'on modifie pour la première fois la valeur associée à une clé, on crée cet item.

Et pour supprimer un item ?

Ah ben ça... Avec cette méthode on peut pas :-° . Avec la méthode suivante, on verra ça !

Méthode 2 : avec des méthodes



Oui, je sais, le titre est un peu bizarre. Par "avec des méthodes", je veux dire "avec des fonctions contenues dans une classe".

Nous allons réutiliser notre QHash de tout à l'heure :

Code : C++
1
QHash<QString, int> table;


Il y a des méthodes pour faire ce qu'on a vu en 1, et même plus !

Ajouter un item



Nous utiliserons pour cela la méthode insert :

Code : C++
1
iterator QHash::insert ( const Key & key, const T & value )


Cette méthode prend en premier paramètre la nouvelle clé (key) et en second paramètre la nouvelle valeur (value). Notez que si l'item est déjà existant, la méthode remplacera l'ancienne valeur par la nouvelle. On utilisera donc la même méthode pour modifier cette valeur.

C'est quoi ce "iterator" que ça retourne ? Et c'est quoi ce "T" ?

Pour le "iterator", nous verrons ça après ^^ !
Et pour ce qui est du "T", il s'agit du type que vous avez défini pour les valeurs de cette table de hachage, ici "int" (QHash<QString, int> table). Il s'agit d'un template.

Donc, si vous avez bien compris, pour ajouter à notre table de hachage "table" l'item de valeur "42" et de clé "reponse", le code sera...

Secret (cliquez pour afficher)
Code : C++
1
table.insert("reponse", 42);


Tester l'existence d'une clé



Pour vérifier si une clé existe dans une table de hachage, on utilise la méthode contains, qui prend en paramètre la clé recherchée, et renvoie true si la clé existe, sinon false.

Exemple :

Code : C++
1
2
3
4
if(table.contains("reponse"))
    // la clé "reponse" existe
else
    // la clé "reponse" n'existe pas


Récupérer le nombre d'items contenus dans la table de hachage



On utilise la méthode count :

Code : C++
1
2
int nombre=table.count();
//nombre = nombre d'items dans "table"


Récupérer une valeur



La méthode est value :

Code : C++
1
const T QHash::value ( const Key & key ) const


Cette méthode prend pour paramètre la clé (key) de la valeur qu'on cherche, et retourne la valeur. Elle s'utilise très simplement :

Code : C++
1
2
3
table.insert("reponse", 42); // pour avoir quelque chose à lire
int reponse=table.value("reponse");
// reponse = 42


Dans le cas où la clé n'existe pas, cette méthode renvoie la valeur par défaut du type de la valeur.

Gné ? o_O

En fait, c'est pas compliqué du tout : ça renvoie la valeur par défaut, celle qui est donnée à une variable qui vient d'être créée. Seulement, cette valeur est différente selon le type de valeurs que prend la table de hachage. Par exemple, pour une QString, la valeur par défaut est une chaine vide, alors que pour un int, la valeur par défaut est 0.

On peut aussi donner la valeur de retour qu'on veut dans le cas où la clé n'existe pas avec la méthode surchargée :

Code : C++
1
const T QHash::value ( const Key & key, const T & defaultValue ) const


Exactement pareil, sauf que le 2ème paramètre devient la valeur de retour par défaut. Elle peut s'utiliser comme ça :

Code : C++
1
2
3
4
table.insert("reponse", 42); // pour avoir quelque chose à lire
int reponse=table.value("reponse", -1);
QLabel label;
label.setText(QString::number(reponse)); // affichera 42, ou -1 si un problème est survenu.


Récupérer une clé



Je ne vais pas m'étendre sur le sujet, tout ce qui a été dit sur la récupération des valeurs est valable pour celle des clés, si ce n'est que la méthode est key et que son premier paramètre est une valeur au lieu d'une clé (en même temps, logique :-° ).

Supprimer un item



La méthode utilisée est delete :

Code : C++
1
int QHash::remove ( const Key & key )


On l'utilise donc en lui passant en paramètre la clé de l'item à supprimer. Cette méthode renvoie le nombre d'items supprimés.

Mais... Elle renvoie donc 1 si l'item a été supprimé, ou 0 si il n'existe pas, donc ! Pourquoi ne pas avoir mis de booléen ?


Eh bien, parce que... Une clé peut contenir plusieurs valeurs ! Eh oui ! Aussi bizarre que ça puisse paraître, c'est possible !

Ajouter une valeur à une clé



Pour avoir plusieurs valeurs par clé, on utilise non pas insert, mais insertMulti :

Code : C++
1
iterator QHash::insertMulti ( const Key & key, const T & value )


Cette méthode marche exactement comme insert, mais au lieu de remplacer si la valeur est déjà existante, elle "ajoute" la valeur.

Récupérer DES valeurs



Dans ce cas, value() retourne quoi ?

value retourne la dernière valeur ajoutée. Pour obtenir une liste de toutes les valeurs pour une clé donnée, on utilise values (attention au 'S' à la fin) :

Code : C++
1
QList<T> QHash::values ( const Key & key ) const


Cette méthode marche comme value, mais elle retourne une QList contenant toutes les valeurs pour la clé key.

Méthode de lecture : les itérateurs



Vous vous souvenez de cet "iterator" en retour de insert ? Le moment est venu de vous expliquer ce que c'est. Un "iterator" (in english) se traduit par un itérateur.

Alors là, on est bien avancés ! Mes pensées se résument en un mot : gné ? o_O

Pour vous définir ça, je pense que Wikipedia sera très bien :

Citation
Un itérateur est un objet qui permet de parcourir tous les éléments contenus dans un autre objet, le plus souvent un conteneur (liste, arbre, etc).

Comme il est dit dans la définition, c'est un objet à part entière. La classe qui gère ça est QHashIterator.

Voyons d'abord son constructeur :

Code : C++
1
QHashIterator::QHashIterator ( const QHash<Key, T> & hash )


Il prend donc en paramètre le QHash qu'il devra parcourir, mais devra, comme QHash, être déclaré avec un template :

Code : C++
1
QHashIterator<QString, int> iterator(table);


Le gros avantage est la possibilité de faire des tests sur les positions précédentes et suivantes :

Les tables de hachage ne sont pas ordonnées. Ainsi, on ne peut pas vraiment prévoir l'ordre des valeurs.


Clé et valeur actuelle


On peut avoir la clé et la valeur actuelle en utilisant key() et value() :

Code : C++
1
2
3
4
5
// prototype de key
const Key & QHashIterator::key () const

// prototype de value()
const T & QHashIterator::value () const


Tester l'existence des items suivants/précédents



Je ne mettrai qu'un prototype pour deux fonctions, étant donné que celles-ci marchent de la même manière, à la différence qu'une agit sur l'item précédent, et l'autre sur le suivant.


On peut vérifier l'existence d'un item suivant ou précédent respectivement avec hasNext et hasPrevious :

Code : C++
1
bool QHashIterator::hasNext () const


La méthode retourne donc un booléen (true si l'item existe, sinon false), et ne prend aucun paramètre.

Avancer/reculer la position actuelle



Pour déplacer la position actuelle de l'itérateur, on utilise next et previous :

Code : C++
1
Item QHashIterator::next ()


Donc, la méthode ne prend aucun paramètre, et retourne... un Item, qui contient la valeur et la clé de l'item sur lequel on se trouve maintenant. Ces variables s'obtiennent respectivement avec value() et key().

Item précédent/suivant



Les méthodes next() et previous() retournent un Item contenant la valeur et la clé de la nouvelle valeur, après déplacement. Avec les méthodes peekNext() et peekPrevious(), on obtient la même valeur de retour, mais sans se déplacer !

Code : C++
1
Item QHashIterator::peekNext () const


Aller au début/à la fin



Pour aller au début (avant le premier item) ou à la fin (après le dernier item), on utilise les méthodes toFront() (début) ou toBack() (fin) :

Code : C++
1
void QHashIterator::toBack ()


Rechercher vers l'avant/vers l'arrière



On peut également faire une recherche partant d'avant ou après l'item actuel avec les méthodes findNext() et findPrevious() qui prennent en paramètre la valeur à rechercher :

Code : C++
1
bool QHashIterator::findNext ( const T & value )


La méthode retourne un booléen indiquant si la valeur value a été trouvée ou non.
L'utilisation est à peine différente selon le sens de la recherche :
  • findNext() : vers l'avant
    La recherche commence à l'item suivant l'item actuel. Si l'item est trouvé, la position courante après la fonction est juste après l'item trouvé. Sinon, la position après la fonction est après le dernier item (comme après un toBack()).
  • findPrevious() : vers l'arrière
    La recherche commence à l'item avant l'item actuel. Si l'item est trouvé, la position courante après la fonction est juste avant l'item trouvé. Sinon, la position après la fonction est avant le premier item (comme après un toFront()).


Récupérer toutes les valeurs : foreach()



Alors là, rien de plus simple : il s'agit d'une boucle spéciale qui est exécutée pour toutes les valeurs de la table, mais qui ne permet pas de voir la clé associée.
On l'utilise ainsi :

Code : C++
1
2
3
4
foreach(int valeur, table)
{
    quelqueChose(valeur); // "valeur" contient la valeur actuelle
}

Q.C.M.

Dans une table de hachage, la clé doit être...
La classe de Qt gérant les tables de hachage est...
Pour lire des données dans une table de hachage, je vous déconseille d'utiliser...
Un itérateur, c'est...

Statistiques de réponses au QCM

Eh bien voilà, normalement (enfin je l'espère), vous maîtrisez les tables de hachage !
Je ne vois pas grand chose de plus à dire à ce sujet...

Par contre, je peux vous dire que le prochain chapitre est... un TP ! Prêts ? Alors cliquez sur "suivant" !
Chapitre précédent Sommaire Chapitre suivant

Partager

2 commentaires pour "Les tables de hachage"
Note moyenne : 3.67 / 4 (18 votes)
Pseudo Commentaire
Hors ligne Anonyme # Posté le 17/12/2010 à 18:34:43

Il y a dans cette sous-partie le code suivant :

Code : C++
1
QList<QWidget> liste; // crée une QList contenant des QWidget


Ce qui est totalement absurde et faux ! Pourquoi ?

Étant donné que QWidget est une classe à sémantique d'entité, elle ne possède pas de constructeur de copie ni même d'opérateur d'affectation. Quant à la classe template QList, elle fonctionne de la même manière que std::list, il faut donc que ses arguments templates respectent certaines conditions selon le modèle dont la classe est implémenté. Dans le cas présent, les arguments templates doivent posséder un constructeur de copie, et certains opérateurs...

Pour corriger le code dont il est question, l'usage des pointeurs est indéniable. On obtiendrait donc ceci :

Code : C++
1
QList< QWidget* > liste; // Présence du *
Hors ligne tobast # Posté le 17/12/2010 à 18:45:44
if(!geek) exit(EXIT_FAILURE);
Avatar

Tu as en effet raison !

Je viens d'envoyer ma première re-validation, mais je n'ai plus le temps de corriger ça maintenant... Je modifie ça dès que j'en aurai le temps !

Merci à toi d'avoir signalé ça !
@+, Tobast
 

Voir tous les commentaires