Retour au cours

backend / go

Maps en profondeur

Leçon 81 exercice

Explication

Ce que vous allez apprendre

  • Créer et manipuler une map avec make() et la syntaxe littérale
  • Utiliser le "comma ok idiom" pour distinguer une clé absente d'une valeur zéro présente
  • Comprendre pourquoi l'ordre d'itération d'une map n'est jamais garanti
  • Modifier une struct stockée dans une map malgré la contrainte d'inadressabilité
  • Utiliser une map comme un set, ou pour regrouper des valeurs par clé

Dans quel contexte ?

Un développeur écrit une fonction qui compte le nombre de visites par page sur un site web, à partir des logs du jour. Il stocke ce comptage dans une map[string]int, incrémente une valeur à chaque ligne de log, puis découvre que le rapport affiché change d'ordre à chaque exécution du programme — alors que les données sont identiques. Ce comportement n'est pas un bug : c'est une caractéristique volontaire des maps Go.

D'abord, une map associe des clés à des valeurs, comme un dictionnaire

make(map[string]int) crée une map vide prête à recevoir des paires clé-valeur. On peut aussi l'initialiser directement avec une syntaxe littérale, comme map[string]int{"Alice": 90, "Bob": 85}.

Accéder à une clé absente ne provoque jamais d'erreur : Go retourne simplement la zero value du type de la valeur (0 pour un int). C'est pratique, mais ça crée une ambiguïté qu'il faut savoir lever.

Une fois cette ambiguïté identifiée, voici comment la résoudre

Le "comma ok idiom" — valeur, existe := maMap[cle] — retourne un second booléen qui indique si la clé était réellement présente. C'est le seul moyen fiable de distinguer "cette clé vaut 0" de "cette clé n'existe pas du tout".

SituationSans comma okAvec comma ok
Clé présente avec valeur 0Indiscernable d'une clé absenteage, ok := m["x"]ok == true
Clé absenteRetourne silencieusement la zero valueok == false
Suppressiondelete(m, cle)Retire réellement la paire

Prérequis

Il faut connaître les slices et les boucles for range (leçon précédente), la syntaxe d'itération sur une map étant très proche.

Il reste une randomisation volontaire à intégrer

Depuis ses débuts, Go randomise délibérément l'ordre d'itération d'une map lors d'un for range. Ce choix évite que du code s'appuie accidentellement sur un ordre qui ne serait de toute façon jamais garanti par la spécification du langage — un ordre stable dans une implémentation aurait fini par devenir une dépendance cachée et fragile.

Piège fréquent

Un affichage ou un test qui dépend de l'ordre d'itération d'une map échouera de façon imprévisible. Pour un ordre déterministe, il faut explicitement extraire les clés dans un slice, les trier avec sort.Strings (ou équivalent), puis itérer sur ce slice trié.

Enfin, un détail syntaxique qui surprend : l'inadressabilité des champs

Une struct stockée comme valeur dans une map n'est pas "adressable" : inventaire["P1"].Prix = 39.9 ne compile pas. Il faut récupérer la struct entière dans une variable, la modifier, puis la réassigner intégralement dans la map — une limitation qui pousse souvent à stocker des pointeurs vers des structs plutôt que les structs elles-mêmes.

Bonne pratique

Une map non initialisée (var m map[string]int, valant nil) se lit sans erreur mais panique à l'écriture. Toujours l'initialiser avec make() avant toute tentative d'écriture, même si tu ne prévois d'y insérer des valeurs que plus tard dans le code.

Maintenant que tu maîtrises les deux collections centrales de Go, il te manque un dernier concept pour comprendre pleinement comment les données circulent entre fonctions : les pointeurs, sujet de la prochaine leçon.

Commandes & code

Maps en profondeur

Table de hachage native : clés-valeurs, non ordonnée, référence (comme les slices).

go
package main

import (
	"fmt"
	"sort"
)

func main() {
	// Creation avec make ou litteral
	ages := make(map[string]int)
	ages["Alice"] = 30
	ages["Bob"] = 25

	scores := map[string]int{
		"Alice": 90,
		"Bob":   85,
	}
	fmt.Println(ages, scores)

	// Acces avec verification d'existence (le "comma ok" idiom)
	age, existe := ages["Carla"]
	fmt.Println(age, existe) // 0 false : zero value si absent

	if age, ok := ages["Alice"]; ok {
		fmt.Println("Alice a", age, "ans")
	}

	// Suppression
	delete(ages, "Bob")

	// Iteration : ORDRE NON GARANTI (volontairement randomise par Go)
	for nom, age := range ages {
		fmt.Println(nom, age)
	}

	// Pour un ordre deterministe : trier les cles explicitement
	cles := make([]string, 0, len(scores))
	for k := range scores {
		cles = append(cles, k)
	}
	sort.Strings(cles)
	for _, k := range cles {
		fmt.Println(k, scores[k])
	}

	// Map avec struct en valeur
	type Produit struct {
		Nom string
		Prix float64
	}
	inventaire := map[string]Produit{
		"P1": {Nom: "Clavier", Prix: 49.9},
	}
	fmt.Println(inventaire["P1"].Nom)
	// inventaire["P1"].Prix = 39.9  // ERREUR de compilation : champ non adressable
	p := inventaire["P1"]
	p.Prix = 39.9
	inventaire["P1"] = p // il faut reassigner la struct entiere

	// Map comme set (idiome tres courant en Go)
	visites := make(map[string]bool)
	visites["/home"] = true
	visites["/about"] = true
	if visites["/home"] {
		fmt.Println("deja visite")
	}

	// Map de slices : regrouper des valeurs par cle
	groupes := make(map[string][]string)
	groupes["fruits"] = append(groupes["fruits"], "pomme") // marche meme si absent
	groupes["fruits"] = append(groupes["fruits"], "poire")
	fmt.Println(groupes)

	// nil map : lecture OK (zero value), ecriture = panic
	var nilMap map[string]int
	fmt.Println(nilMap["x"]) // 0, pas de panic
	// nilMap["x"] = 1        // panic: assignment to entry in nil map
}

Résumé

  • L'itération sur une map n'a aucun ordre garanti : trier les clés si besoin.
  • v, ok := m[k] distingue "absent" de "valeur zéro présente".
  • Les champs d'une struct stockée dans une map ne sont pas adressables directement.
  • Une map nil se lit sans erreur mais panique à l'écriture — toujours make() avant d'écrire.

Exercices pratiques

1 disponible
1

Mission : un tableau de bord de visites qui panique et ment

Objectif : Corriger un panic sur map nil et une confusion clé absente/valeur zéro, puis rendre un rapport de statistiques déterministe.

Contexte

Un tableau de bord compte les visites par page à partir des logs du jour dans une map[string]int. Le service crashe parfois au démarrage avec panic: assignment to entry in nil map, et une page jamais visitée s'affiche parfois avec le même score qu'une page visitée exactement 0 fois par un bot bloqué.

Résoudre l’exercice →