Licence CC BY-NC-ND

Les hachages

Nous venons de terminer un chapitre sur les conteneurs. Il est maintenant temps de faire un chapitre sur les conteneurs.

Quoi, un autre chapitre sur les conteneurs ? Mais, nous en avons déjà fait un, non ?

Oui, nous avons fait le chapitre sur les tableaux. Dans ce chapitre, nous allons aborder un autre type de conteneurs, les tableaux associatifs aussi appelés hashs ou encore hachages.

Des tableaux associatifs

Qu’est-ce qu’un hachage

Nous avons dit que nous allions voir un nouveau type de conteneurs. En fait, un hachage n’est rien d’autre qu’un tableau spécial. Le nom tableau associatif peut nous aider à comprendre en quoi ils sont spéciaux. En fait, au lieu d’associer une valeur à un nombre entier comme dans un tableau normal, nous allons associer une valeur à… ce que nous voulons. Nous pourrons par exemple associer à une chaîne de caractères une autre chaîne de caractères.

tab[2]      # Un tableau normal.
hash['deux'] # Ce qu’on peut faire avec un hachage.

L’idée générale est simple à comprendre.

Mais, à quoi ça sert ?

Bonne question. Supposons que notre but soit de stocker des informations sur une personne. Nous voulons son nom, son prénom et son âge. Nous pourrions créer un tableau normal.

person = ['nom de la personne', 'prénom de la personne', 'âge de la personne']

Mais ce ne serait pas très évident à utiliser. Un tableau associatif serait bien mieux. On aurait un tableau associatif à trois cases.

  • la case 'last_name' associée à son nom ;
  • la case 'first_name' associée à son prénom ;
  • la case 'age' associée à son âge.

Ce sera non seulement plus facile à écrire, mais aussi à lire (si on passe beaucoup de temps à écrire un programme, on passe encore plus de temps à le lire).

On appelle clé (key en anglais) ce qu’on a choisi comme identifiant, et on appelle valeur ce à quoi on accède grâce à la clé (les éléments du hachage).

Dans notre exemple, last_name, first_name et age seraient donc des clés, alors que le nom de la personne, son prénom et son âge seraient des valeurs.

Déclarer un hachage

Maintenant que nous savons ce que sont les hachages et que nous savons à quoi ils peuvent servir, il ne nous reste plus qu’à les utiliser. Pour déclarer un hachage, il faut utiliser des accolades à la place des crochets du tableau. Pour déclarer un hachage vide, il faut donc utiliser ceci.

hash = {}

On peut l’afficher avec print.

hash = {}
print hash

Code qui nous affiche bien entendu {}.

De plus, pour indiquer qu’on associe une valeur à une autre, il faut utiliser =>. Les éléments sont, comme pour les tableaux, séparés par une virgule. Déclarons le hachage de notre exemple précédent.

hash = { 'last_name'  => 'Mon nom',
         'first_name' => 'Mon prénom',
         'age'        => 2015 }
print hash

Cette fois, on obtient affiché à l’écran : {"last_name"=>"Mon nom", "first_name"=>"Mon prénom", "age"=>2015}.

Contrairement aux tableaux, les hachages ne peuvent pas être déclarés sans utiliser les accolades, elles sont obligatoires.

Nous avons dit que nous pouvions utiliser n’importe quoi comme clé. Faisons un hachage avec des nombres et des chaînes de caractères comme clé.

hash = { 'abc' => 'dcvs',
         2     => 'deux',
         3.4   => 23 }
print hash

Et on obtient : {"abc"=>"dcvs", 2=>"deux", 3.4=>23}.

Opérations sur les hachages

Accéder à un élément

Si nous retournons à la partie précédente, nous verrons que nous l’avons déjà fait. Comme pour les tableaux, il faut utiliser les crochets, et écrire entre eux la clé de l’élément auquel on veut accéder. Nos clés peuvent être tout et n’importe quoi.

hash = { 'last_name'  => 'Mon nom',
         'first_name' => 'Mon prénom',
         'age'        => 2015 }
print hash['last_name']

Grâce à ce code, nous affichons la chaîne « Mon nom ».

On peut se dire que nous sommes en train de radoter, mais ce n’est pas de notre faute si les tableaux et les hachages se ressemblent tant. En fait, ils se ressemblent tellement que même en essayant d’accéder à une valeur associée à une clé qui n’existe pas, on a la même réponse.

hash = { 'last_name'  => 'Mon nom',
         'first_name' => 'Mon prénom',
         'age'        => 2015 }
print hachage['Last_name']

Ce code affiche en effet nil, tout comme print tab[5] lorsque tab a moins de 6 cases.

Encore une fois on se répète, mais ce n’est pas parce que cet accès ne provoque pas d’erreur qu’on peut le faire. Il faut essayer d’éviter toutes erreurs de ce type.

Ajout d’éléments

Les opérateurs + et << ne marchent pas avec les hachages. En fait, la seule manière de rajouter un élément est d’utiliser les crochets pour modifier un élément qui n’existe pas encore.

hash = { 'last_name'  => 'Mon nom',
         'first_name' => 'Mon prénom',
         'age'        => 2015 }
hash['other'] = "nouveau"

print hash

La clé 'other' n’existe pas encore, on lui assigne une valeur.

D’ailleurs, qu’est-ce qui se passe si on assigne deux fois une valeur à la même clé lorsqu’on déclare un hachage ?

Faisons le test.

hash = { 'last_name'  => 'Mon nom',
         'first_name' => 'Mon prénom',
         'age'        => 2015 
         'last_name'  => 'Mon nouveau nom' }
print hash

Et le résultat.

{"last_name"=>"Mon nouveau nom", "first_name"=>"Mon prénom", "age"=>2015}

La valeur qui a été gardée pour la clé est la dernière que l’on avait associée. On ne peut donc associer qu’une seule valeur à une clé.

Pour faire une analogie avec un autre langage, les hachages de Ruby sont les dictionnaires de Python.

Parcourir le hachage

Là on va arrêter de radoter ! Parcourir un hachage ne se fait pas du tout de la même manière que parcourir un tableau. Et c’est bien normal, on ne peut pas parcourir un hachage grâce aux indices puisqu’il n’y a pas d’indices. En fait, la seule méthode commune est celle qui consiste à parcourir directement le hachage avec in.

hash = { 'last_name'  => 'Mon nom',
         'first_name' => 'Mon prénom',
         'age'        => 2015 }

for i in hash
  print "#{i}\n"
end

On déclare notre hachage, et on le parcourt avec une boucle for, pour finalement obtenir ceci.

["last_name", "Mon nom"]
["first_name", "Mon prénom"]
["age", 2015]

Et là, nous sommes au regret de dire que tout comme pour les tableaux, nous n’allons pas utiliser cette technique, nous allons plutôt utiliser des méthodes.

Parcourir les clés et les valeurs

Les hachages ont, comme les tableaux, une méthode each. Cependant, celle des hachages permet de parcourir les clés et les valeurs. Voyons un exemple.

hash = { 'last_name'  => 'Mon nom',
         'first_name' => 'Mon prénom',
         'age'        => 2015 }

hash.each { |key, value| puts "La valeur #{value} est associée à la clé #{key}." }

On parcourt tout le hachage en stockant chaque clé et chaque valeur dans les variables key et value.

La méthode each_with_index existe également pour les hachages. Cependant, avec elle, les clés sont associées à des indices. Ainsi, avec le code qui suit, nous obtiendrons « La valeur ["last_name", "Mon nom"] est associée à l’indice 0 ».

hash = { 'last_name'  => 'Mon nom',
         'first_name' => 'Mon prénom',
         'age'        => 2015 }

hash.each_with_index { |v, i| puts "La valeur #{v} est associée à l’indice #{i}." }
Parcourir les valeurs

Avoir les valeurs et les clés c’est bien, mais ce serait bien de ne récupérer que les valeurs. Pour cela, nous allons utiliser la méthode each_value.

hash = { 'last_name'  => 'Mon nom',
         'first_name' => 'Mon prénom',
         'age'        => 2015 }

hash.each_value { |value| puts "La valeur #{value} est dans le hachage." }

Le code se passe de description. Nous devrions pouvoir le comprendre tranquillement.

Parcourir les clés

C’est plus rare de vouloir accéder aux clés d’un hachage, mais cela arrive. Heureusement, la méthode each_key est là pour nous aider à faire cela.

hash = { 'last_name'  => 'Mon nom',
         'first_name' => 'Mon prénom',
         'age'        => 2015 }

hash.each_key { |clé| puts "La clé #{key} est une des clés du hachage." }

Hachages et tableaux

Bon, maintenant que nous avons vu les hachages et certaines opérations possibles sur eux, il nous faut savoir quand les utiliser. Nous n’avons en effet vu qu’un seul exemple jusqu’à maintenant, et nous l’avons utilisé pour toutes nos opérations.

Quelles sont les avantages et les inconvénients des hachages face aux tableaux ? Lequel choisir ?

C’est ce que nous allons voir dans cette partie. Pour cela, nous allons nous attarder sur trois points :

  • la clarté ;
  • l’ordre ;
  • la flexibilité.
La clarté

Sur ce point, les hachages sont mieux que les tableaux. En effet, alors que les clés des tableaux ne sont que des nombres, avec les hachages, on peut choisir tout et n’importe quoi comme clé (même des nombres donc). Les hachages permettent donc de toujours avoir des clés qui ont un sens.

L’ordre

Sur ce point, les tableaux sont nettement mieux que les hachages. En effet, les tableaux disposent d’un ordre déjà établi du fait de leur définition (l’élément d’indice 0, l’élément d’indice 1, etc.). Au contraire, les clés des hachages ne permettent pas d’établir un ordre clair. Comment Ruby ferait-il pour savoir que l’élément associé à telle clé doit venir avant l’élément associé à telle autre clé ?

Cette absence d’ordre dans les hachages est d’ailleurs ce qui produit l’impossibilité de certaines actions possibles sur les tableaux. En effet, nous avons vu par exemple que l’opérateur + ne s’utilisait pas sur les hachages. Quelle est la raison de cela ? En fait, avec deux tableaux, l’opérateur + ajoute les éléments du deuxième tableau aux éléments du premier tableau en conservant l’ordre. Ainsi, en faisant [2, 3] + [4, 5], on obtient bien [2, 3, 4, 5] et non [2, 3, 5, 4] ni [4, 5, 2, 3]. Cela n’est pas possible avec les hachages. De même, l’opérateur << qui ajoute un élément à la fin d’un tableau n’est pas disponible avec les hachages (on ne sait pas où est la fin d’un hachage).

Et si on donnait comme clé à nos hachages des nombres, ça marcherait, non ?

Allons-y, testons ce code.

hash = { 1 => 1,
         2 => 2,
         3 => 3 }

hash.each { |key, value| puts "#{key} : #{value}" }

Les éléments sont affichés dans le bon ordre. Super !

Mais, regardons maintenant ce code.

hash = { 1 => 1,
         2 => 2,
         3 => 3 }

hash[5] = 5
hash[4] = 4

hash.each { |key, value| puts "#{key} : #{value}" }

La clé 5 est passée avant la clé 4. On perd notre ordre, et pourtant, nous n’avons fait rien d’autre qu’un simple ajout de valeurs.

Il ne faut pas essayer de faire passer un tableau pour un hachage. Les propriétés d’ordre du tableau ne seraient pas du tout respectées.

La flexibilité

Voyons un peu les différentes opérations possibles sur les tableaux et les hachages et comparons-les :

  • l’ajout d’élément est possible sur les deux ;
  • la modification est possible sur les deux ;
  • la concaténation n’est possible que sur les tableaux mais correspond juste à une suite d’ajouts d’éléments.

Les hachages et les tableaux ont l’air aussi flexibles l’un que l’autre. Les opérations qu’on peut effectuer sur les deux sont complètes et permettent de modifier radicalement la structure.

Finalement, aucune structure n’est mieux que l’autre. Elles ont toutes les deux leurs points forts et leurs points faibles. Tout simplement, parce qu’elles ne sont pas adaptées aux même situations. Les tableaux sont utiles dans certains cas, les hachages dans d’autres.

Exercices

Plutôt que de faire plusieurs exercices, nous allons en faire un seul en plusieurs étapes. Notre but va être de gérer une liste d’élèves. Un élève aura un nom, un prénom et un âge (nous pourrons tester que l’âge de l’élève est valide, mais ce n’est pas obligatoire). Notre programme devra proposer à l’utilisateur trois choix :

  • ajouter un élève ;
  • afficher la liste des élèves ;
  • quitter.

Un exemple.

1. Ajouter un élève.
2. Afficher la liste des élèves.
3. Quitter.

Votre choix ? 1

Nom : ZdS
Prénom : Clem
Âge : 2

L’élève ZdS Clem a été ajouté.

1. Ajouter un élève.
2. Afficher la liste des élèves.
3. Quitter.

Votre choix ? 1

Nom : SdZ
Prénom : Zozor
Age : 10

L’élève SdZ Zozor a été ajouté.

1. Ajouter un élève.
2. Afficher la liste des élèves.
3. Quitter.

Votre choix ? 2

- SdZ Zozor
- ZdS Clem

Correction.

Nous allons faire un tableau dont les éléments sont des hachages. Ces hachages seront nos différents élèves. Notre programme consistera alors à gérer ce tableau.

Nous allons faire une méthode menu qui affiche le menu et renvoie le choix de l’utilisateur, une méthode add_student qui ajoute un élève au tableau, et une méthode print_students qui affiche la liste des élèves.

def add_student(tab)
  print "\nNom : "
  last_name = gets.chomp
  print 'Prénom : '
  first_name = gets.chomp
  print 'Âge : '
  age = gets.chomp.to_i
  tab << { 'last_name'    => nom,
           'first_name' => prénom,
           'âge'    => âge }
  print "\nL’élève #{last_name} #{first_name} a été ajouté.\n\n"
end

def print_students(tab)
  tab.each { |e| puts "- #{e['last_name']} #{e['first_name']}" }
  print "\n\n"
end

def menu
  print "1. Ajouter un élève.\n2. Afficher la liste des élèves.\n3. Quitter.\n\n"
  print 'Votre choix ? '
  return gets.chomp.to_i
end

tab = []
choice = 0

while choice != 3
  choice = menu
  if choice == 1 then
    add_student(tab)
  elsif choice == 2 then 
    print_students(tab)
  end
end

Maintenant, rajoutons une fonctionnalité pour lire les informations d’un élève en particulier, pour une sortie de ce genre.

1. Ajouter un élève.
2. Afficher la liste des élèves.
3. Informations d’un élève.
4. Quitter. 

Votre choix ? 3

Nom de l’élève : ZdS

L’élève Zds Clem a 2 ans.

Si plusieurs élèves ont le même nom, les descriptions de tous ces élèves devront être affichées.

Correction.

Pour cela, nous allons créer une méthode informationsÉlève. Il ne faudra pas non plus oublier de mettre à jour la méthode choisir et le code principal. On a le code suivant.

def student_data(tab)
  puts 'Nom de l’élève : '
  last_name = gets.chomp
  print "\n\n"
  tab.each do |e| 
    puts "#{e["last_name"]} #{e["first_name"]} a #{e["age"]} ans." if e["last_name"] == last_name
  end
  print "\n\n"
end

Nous pouvons encore ajouter d’autres options à notre menu, comme la possibilité de supprimer un élève. Nous pouvons également complexifier notre structure en ajoutant, par exemple, la liste des matières suivies par chaque élève à son hachage (cette liste serait un tableau). Nous aurions alors un tableau contenant un hachage qui contient lui-même un tableau.


Et voilà c’est la fin de ce chapitre qui sera très utile pour la suite, les hachages étant un élément important de Ruby.

  • Un hachage est un ensemble d’éléments indexés par ce qu’on veut (on peut associer chaque élément à ce qu’on veut) et se trouve pour cela appelé tableau associatif.
  • Pour parcourir un hachage, on utilise la méthode each. Les méthodes each_value et each_key nous permettent respectivement de parcourir les valeurs et les clés.
  • Les hachages et les tableaux sont complémentaires et il faut choisir lequel utiliser en fonction des besoins et de la situation.