Go intègre un type natif qui implémente des tables de hachage appelées map. Il s'agit d'un type de données composé d'une collection de clés uniques et d'une collection de valeurs pour chacune de ces clés.
Il peut être comparé à un dictionnaire dans d’autres langues, par exemple, qui stocke des paires clé-valeur. Ces valeurs sont accessibles à l'aide de clés, de la même manière que les tableaux et les tranches comme nous l'avons vu dans l'article précédent.
Les index ne sont pas limités à un nombre comme dans les tableaux ou les tranches et les éléments ne sont pas ordonnés, donc si nous imprimons une carte, elle renverra un ordre aléatoire, si nous ne faisons rien pour remplacer son impression et forcer l'ordre souhaité.
Pour déclarer une carte, cela se fait avec map[key]value, où key sera le type que nous voulons que notre clé soit (elle doit être d'un type comparable https://go.dev/ref/spec#Comparison_operators ) et value sera le type avec lequel nous voulons que la carte soit stockée dans chacune des clés, quel que soit son type, d'un int à une structure, ou une autre carte, comme nous le voulons.
Comme pour les tranches, les cartes sont des types référencés, ce qui signifie que la valeur zéro d'une carte sera nulle.
Cela se produit parce qu'en dessous se trouve une table de hachage qui stocke les clés et les valeurs, et elles n'en sont qu'une enveloppe, une abstraction.
Si nous le déclarons comme :
var m map[int]int
sa valeur sera nulle.
Si nous voulons qu'il ait une valeur nulle, nous pouvons utiliser la déclaration :
m := map[int]int{}
Et on peut même l'initialiser comme les slices, en utilisant la fonction make.
m := make(map[string]string)
Faire cela initialisera une carte de hachage avec le pool de mémoire approprié, renvoyant ainsi une carte qui pointe vers cette structure de données.
L'ajout de valeurs à une carte se fait à l'aide d'accolades [] et de l'accolade, tout comme avec des tableaux ou des tranches. Dans cet exemple, nous allons créer une carte avec les clés étant des chaînes et les valeurs étant des entiers, pour stocker les noms et les âges.
ages := make(map[string]int) ages["John"] = 33 ages["Charly"] = 27 ages["Jenny"] = 45 ages["Lisa"] = 19
Si nous voulons y ajouter les valeurs lorsque nous déclarons la carte, nous pouvons utiliser la déclaration courte et tout faire en la même étape :
ages := map[string]int{"John": 33, "Charly": 27, "Jenny": 45, "Lisa": 19}
Pour lire les valeurs, il suffit d'indiquer la clé de notre carte et elle renverra cette valeur. Par exemple, pour connaître l'âge de Lisa, on peut faire :
fmt.Println(ages["Lisa"]) // 19
Si nous essayons d'accéder à une clé qui n'existe pas, la valeur obtenue sera la valeur zéro du type, dans ce cas ce serait "", puisqu'il s'agit d'une chaîne.
Afin de vérifier si un élément existe dans la carte, nous pouvons vérifier si le type est celui par défaut, mais ce n'est pas très fiable, car il existe peut-être mais sa valeur est une chaîne vide ou 0 dans le cas d'un int , qui correspondrait à sa valeur zéro, donc Go nous aide avec les éléments suivants :
val, ok := ages["Randy"]
Si nous égalisons la carte à deux valeurs, la première sera la valeur de cet élément accessible via la clé, dans ce cas "Randy" qui n'existe pas, et la seconde sera un booléen, qui indiquera s'il existe ou non.
Si la valeur ne nous intéresse pas et que nous voulons simplement vérifier l'existence d'une clé, nous pouvons utiliser _ pour ignorer la valeur comme suit :
_, ok := ages["Randy"]
Comme pour les tableaux et les tranches, nous pouvons utiliser la fonction len pour savoir combien d'éléments il y a dans la carte.
fmt.Println(len(ages)) // 4
Si nous voulons modifier une valeur, c'est aussi simple que d'accéder à ladite valeur à l'aide d'une clé et de la faire correspondre avec une autre, et elle sera modifiée.
Si nous déclarons une deuxième carte pointant vers la première, si nous modifions la valeur de la seconde, puisqu'il s'agit d'un type référencé, nous modifierons la valeur de la première, car les deux partagent la même table de hachage en dessous.
ages := map[string]int{"John": 33, "Charly": 27, "Jenny": 45, "Lisa": 19} agesNew := ages agesNew["Bryan"] = 77 fmt.Println(agesNew) // map[Bryan:77 Charly:27 Jenny:45 John:33 Lisa:19] fmt.Println(ages) // map[Bryan:77 Charly:27 Jenny:45 John:33 Lisa:19]
Pour supprimer des éléments d'une carte, Go nous met à disposition une fonction de suppression avec la signature suivante delete(m map[Type]Type1, key Type) qui reçoit une carte et la clé à supprimer.
Dans le cas précédent, si nous voulions éliminer "Lisa" nous le ferions :
delete(ages, "Lisa")
Si nous voulons parcourir le contenu d'une carte, nous pouvons le faire en utilisant un for avec la variation de plage que nous avons déjà vue dans le post sur les tableaux et les tranches.
Comme alors, le premier élément sera l'index, donc la clé, et le second la valeur.
for key, value := range ages { fmt.Printf("%s: %d\n", key, value) } // Output: // Jenny: 45 // Lisa: 19 // John: 33 // Charly: 27
Comme pour les tableaux et les tranches, si seule la valeur nous intéresse, sans la clé, nous pouvons l'omettre en utilisant _.
for _, value := range ages { fmt.Println(value) } // Output: // 19 // 33 // 27 // 45
Et si ce qui nous intéresse est simplement la clé, on peut attribuer la plage à une seule variable pour l'obtenir :
for key := range ages { fmt.Println(key) } // Output: // John // Charly // Jenny // Lisa
Comme je l'ai mentionné dans l'introduction, dans une carte, les informations ne sont pas ordonnées, donc lorsque nous les parcourons en boucle, nous ne pouvons pas spécifier l'ordre qu'elles suivent, et Go ne peut pas non plus garantir que l'ordre entre les exécutions est le même.
Comme nous l'avons vu avec les tableaux et les tranches, dans la bibliothèque standard, il existe un package de tri qui nous aide à trier les éléments : https://pkg.go.dev/sort
En suivant notre exemple avec les âges et en utilisant le tri, nous pouvons trier les clés de la carte avant de la parcourir et ainsi garantir qu'elle sera accessible dans l'ordre.
ages := map[string]int{"John": 33, "Charly": 27, "Jenny": 45, "Lisa": 19} keys := make([]string, 0, len(ages)) for k := range ages { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { fmt.Println(k, ages[k]) } // Output: // Charly 27 // Jenny 45 // John 33 // Lisa 19
Nous déclarons notre carte des âges avec la courte déclaration comme nous l'avons vu précédemment.
Nous créons une chaîne slices pour stocker les clés et utilisons la méthode make avec une longueur de 0, puisque nous n'avons aucune clé pour le moment, mais nous réservons la capacité qu'elle aura en utilisant la méthode len pour la longueur de notre carte.
On parcourt la carte des âges pour conserver ses clés et les ajouter à la tranche créée.
Nous trions les clés par ordre alphabétique avec la fonction sort.Strings.
On parcourt la tranche de clés, déjà commandée, et on accède à la carte avec la clé en question.
De cette façon, nous accéderons à la carte de manière ordonnée et nous pourrons appliquer la logique dont notre programme a besoin.
Ce qu'il faut garder à l'esprit avec les cartes, c'est qu'il n'est pas sûr de les utiliser simultanément. S'il s'agit de lectures simultanées, soit en accédant à une valeur, soit via un for avec une plage, il n'y a aucun problème avec plusieurs goroutines y accédant en même temps.
Le cas problématique est lorsque vous souhaitez mettre à jour la valeur d'une carte simultanément, soit en y ajoutant ou en supprimant des éléments, et en même temps vous la lisez d'un autre côté, par exemple.
Pour résoudre cette situation, il existe plusieurs solutions possibles, que je n'entrerai pas dans les détails, je me contenterai de les mentionner et de vous laisser le soin de les approfondir.
Si nous utilisons le package de synchronisation : https://pkg.go.dev/sync de la bibliothèque standard, nous pouvons contrôler la synchronisation entre les différentes goroutines.
Une utilisation possible est le type RWMutex qui nous permet de verrouiller et déverrouiller les lectures et les écritures sur un type. Donc, si nous avons un type qui contient un sync.RWMutex et une carte, nous pouvons contrôler quand il est accessible.
Un autre type intéressant à étudier au sein du même package de synchronisation est Map, qui nous offre déjà une série de fonctions qui nous aideront à travailler avec notre carte, avec lesquelles au final nous ne pourrons pas travailler de manière native, comme avec la solution précédente.
Selon le cas d'utilisation que nous implémentons, l'un ou l'autre nous sera plus utile, et il n'y en a pas meilleur que l'autre, cela dépendra toujours de ce dont nous avons besoin.
J'espère que tout ce que j'ai essayé d'expliquer dans cet article a été clair, et s'il y a une partie qui n'a pas été complètement claire ou s'il y a des parties que je n'ai pas couvertes et que vous aimeriez que je fasse, partez moi un commentaire ici ou via mes réseaux sociaux que vous avez sur mon profil et je me ferai un plaisir d'y répondre.
Clause de non-responsabilité: Toutes les ressources fournies proviennent en partie d'Internet. En cas de violation de vos droits d'auteur ou d'autres droits et intérêts, veuillez expliquer les raisons détaillées et fournir une preuve du droit d'auteur ou des droits et intérêts, puis l'envoyer à l'adresse e-mail : [email protected]. Nous nous en occuperons pour vous dans les plus brefs délais.
Copyright© 2022 湘ICP备2022001581号-3