CodeGym /Cours /Go SELF /range — parcourir des chaînes en Go

range — parcourir des chaînes en Go

Go SELF
Niveau 4, Leçon 3
Disponible

1. Pourquoi a-t-on besoin de range sur une chaîne ?

Quand vous voyez range pour la première fois, deux pensées apparaissent généralement. La première : « Oh, c’est joli ». La seconde : « Et pourquoi ne pas simplement utiliser for i := 0; i < len(s); i++ ? ». C’est possible. Mais range résout deux problèmes typiques : il raccourcit le code et aide à éviter les erreurs de bornes, surtout quand vous voulez parcourir quelque chose « caractère par caractère ».

En pratique, range est une façon de dire à Go : « Je veux parcourir les éléments d’une séquence, donne-moi ce qu’il faut pour chaque itération ». Important : aujourd’hui, nous utilisons range uniquement pour les chaînes, même si en Go il s’applique aussi à d’autres types de collections (mais nous ne les abordons pas encore, afin de ne pas transformer la leçon en salade).

2. Syntaxe de for range : indice et valeur

Si le for classique ressemble à « je contrôle moi-même chaque itération », alors range ressemble à « parcours-moi cela à ma place ». En apparence, c’est très simple, mais il y a un détail important : range renvoie généralement deux valeurs. Nous allons donc tout de suite nous entraîner à les lire sans confondre laquelle est laquelle.

La forme de base est la suivante :

for index, value := range s {
    // ...
}
Syntaxe de base de for range sur une chaîne

index est la position de l’élément dans la chaîne, et value est « l’élément lui-même » (pour une chaîne, c’est le code numérique du caractère, nous y reviendrons).

Premier exemple : ce que range nous donne

package main

import "fmt"

func main() {
	s := "Go"

	for i, v := range s {
		fmt.Println(i, v)
	}
	// 0 71		  71 - code du caractère 'G'
	// 1 111	 111 - code du caractère 'o'
}

Ici, on voit deux choses. Premièrement, i commence à 0. Deuxièmement, v ressemble à des nombres mystérieux. Ce n’est pas un bug, c’est une fonctionnalité : pour une chaîne, range ne renvoie pas « un morceau de chaîne », mais la valeur numérique du caractère.

3. index : pourquoi l’indice « saute » parfois

Dans une boucle classique avec compteur, l’indice avance généralement de 0, 1, 2, 3… et cela paraît naturel : nous faisons nous-mêmes i++. Avec range, vous verrez souvent aussi 0, 1, 2, 3… mais pas toujours. Et là, il ne faut pas paniquer ni croire que Go est cassé (il le sera plus tard, quand vous oublierez }).

index dans range sur une chaîne est une position en octets. Tant que vous travaillez avec de l’alphabet latin et des caractères simples, tout semble « régulier ». Mais dès que la chaîne contient des caractères qui prennent plus d’un octet, les indices ne progresseront plus de +1.

Ne nous enfonçons pas dans les encodages (afin de ne pas devenir le cours « UTF‑8 en 15 minutes »), et regardons simplement l’effet. Par exemple, une chaîne du type "Hello, 世界" apparaît souvent dans les exemples Go.

package main

import "fmt"

func main() {
	s := "Hello, 世界"

	for i, v := range s {
		fmt.Println(i, v)
	}
}

Vous verrez que les indices pour l’alphabet latin progressent régulièrement, tandis qu’à l’approche de « 世界 », des sauts peuvent apparaître. Pour l’instant, retenez simplement la règle pratique : index est une position dans la chaîne, mais pas nécessairement le « numéro du caractère ». Pour les tâches du niveau d’aujourd’hui, il suffit d’apprendre à utiliser la forme de la boucle et ses deux valeurs ; les subtilités du texte, nous les laisserons pour plus tard.

Pour aider le cerveau, vous pouvez garder en tête ce mini-schéma :

for i, v := range s {
    i = "où cela se trouve à l’intérieur de la chaîne" (position)
    v = "quel est ce caractère" (son code)
}

4. value : pourquoi c’est un nombre et ce qu’est un « code de caractère »

Quand vous affichez value issu de range avec fmt.Println, vous voyez un nombre. Ce nombre est le code du caractère (au sens Unicode). En Go, pour ces valeurs, on utilise souvent le type rune. On peut considérer rune comme « un nombre qui stocke un caractère ». Formellement, c’est un alias de int32.

Il n’est pas nécessaire de mémoriser tout de suite le mot int32 (vous le rencontrerez plus tard), mais il est important de comprendre l’idée : range sur une chaîne donne le caractère sous forme de nombre, parce qu’à l’intérieur une chaîne est une séquence d’octets, et Go essaie d’être honnête et efficace.

Les apostrophes valent aussi un nombre

En Go, le littéral 'A' est aussi un code numérique de caractère. C’est pratique, car nous pouvons comparer value provenant de range avec de tels littéraux.

Par exemple, comptons combien de fois la lettre 'o' apparaît dans un mot :

package main

import "fmt"

func main() {
	s := "gopher"

	count := 0
	for _, v := range s {
		if v == 'o' {		// 'o' symbole
			count++
		}
	}

	fmt.Println(count) // 1
}

Remarquez : nous avons utilisé _ à la place de l’indice, parce que nous n’en avons pas besoin. C’est un thème important de la section suivante, mais vous voyez déjà comment cela rend le code plus propre.

Petit détecteur de caractères interdits

Parfois, vous devez vérifier si une chaîne contient un certain caractère. C’est une tâche typique de la vie réelle : « le mot de passe ne doit pas contenir d’espace », « le nom ne doit pas contenir @ », « le code ne doit pas contenir de tabulation », etc.

Faisons une vérification simple : la chaîne contient-elle le caractère '#' ?

package main

import "fmt"

func main() {
	s := "go#lang"

	hasHash := false
	for _, v := range s {
		if v == '#' {
			hasHash = true
			break
		}
	}

	fmt.Println(hasHash) // true
}

Ici, vous voyez une combinaison très utile : range fournit un parcours pratique, et break permet de sortir plus tôt dès que la réponse est trouvée. Nous n’entrons pas dans de gros « algorithmes », mais ce schéma vous sera rencontré sans cesse.

5. _ : ignorer les valeurs inutiles

Chez les débutants en Go, le compilateur provoque souvent à la fois agacement et respect. Agacement — parce que « pourquoi se plaint-il pour une broutille ». Respect — parce qu’ensuite vous comprenez qu’il se plaint pour vous éviter de vous tirer une balle dans le pied, même si vous avez vraiment essayé.

L’une de ces « réprimandes » est l’erreur sur les variables inutilisées. Si vous avez écrit for i, v := range s, mais que vous n’utilisez que v, le compilateur dira : "i declared and not used".

Et c’est là que _ apparaît.

_ se lit comme : « je n’ai pas besoin de cette valeur, et c’est volontaire ». Ce n’est pas une variable, c’est une “poubelle” spéciale pour les données.

Ignorer l’indice, utiliser seulement la valeur

Le cas le plus courant pour les chaînes est que vous avez plus souvent besoin du caractère que de la position :

package main

import "fmt"

func main() {
	s := "abc"

	for _, v := range s {
		fmt.Println(v)
	}
	// 97
	// 98
	// 99
}

Ignorer la valeur, utiliser seulement l’indice

Parfois, c’est l’inverse : vous avez besoin des positions. Par exemple, vous voulez simplement « parcourir N fois » la longueur d’une chaîne (oui, cela arrive aussi — par exemple pour afficher des numéros).

Règle importante à retenir : si vous écrivez un seul nom dans range, ce sera l’indice, et non la valeur.

package main

import "fmt"

func main() {
	s := "hello"

	for i := range s {
		fmt.Println(i)
	}
	// 0
	// 1
	// 2
	// 3
	// 4
}

C’est un endroit où beaucoup se trompent : ils écrivent for v := range s en attendant des « caractères », alors qu’ils obtiennent des « indices ». Gardez donc en tête cet indice simple : « un seul nom — c’est l’indice ».

6. Mini-application : TextBuddy

Pour que range ne reste pas un « syntaxe pour la syntaxe », intégrons-le dans une petite application que nous pourrons compléter progressivement dans les prochaines leçons. Ce sera un assistant console « TextBuddy » : il lit un mot (sans espaces), affiche sa longueur, puis imprime un tableau « position → code du caractère ». Cela entraîne à la fois l’entrée/sortie et explique ce que range apporte exactement.

Version 1 : lire un mot et afficher len

package main

import "fmt"

func main() {
	var s string
	fmt.Scan(&s)

	fmt.Println(len(s)) // par exemple : 6
}

Oui, c’est une base extrêmement simple. Mais ce sont précisément de tels « briques » qui permettent ensuite de construire des programmes corrects.

Version 2 : ajouter un parcours avec range

package main

import "fmt"

func main() {
	var s string
	fmt.Scan(&s)

	fmt.Println("len =", len(s))

	for i, v := range s {
		fmt.Println(i, v)
	}
}

Si vous saisissez Go, la sortie ressemblera à ceci :

len = 2
0 71
1 111

Et si vous saisissez quelque chose de « pas tout à fait ASCII », vous pouvez remarquer que len(s) et le nombre d’itérations de range peuvent se comporter de manière inattendue. C’est normal : nous fixons ici la mécanique de range, et nous plongerons plus tard dans les chaînes et Unicode. L’essentiel est que vous sachiez désormais lire ce code et l’écrire vous-même.

Version 3 : tableau et recherche d’un caractère

Ajoutons deux petites améliorations : rendons l’affichage un peu plus clair et ajoutons une vérification « le mot contient-il un tiret '-' ? ».

package main

import "fmt"

func main() {
	var s string
	fmt.Scan(&s)

	hasDash := false

	fmt.Println("index code")
	for i, v := range s {
		fmt.Println(i, v)
		if v == '-' {
			hasDash = true
		}
	}

	fmt.Println("has '-' =", hasDash)
}

C’est toujours court, mais cela ressemble déjà à un « utilitaire utile » : on peut vérifier des identifiants/login qui ne doivent pas contenir certains caractères.

range ne fonctionne pas seulement sur les chaînes

On a très envie de dire tout de suite : « et range fonctionne aussi sur les tableaux, les slices, les maps… » — et c’est vrai. Mais à ce stade du cours, nous faisons volontairement une pause. Aujourd’hui, nous retenons range comme forme de boucle et nous nous entraînons à ne pas confondre index/value, à utiliser correctement _, et à comprendre que les valeurs peuvent être « différentes de ce qu’on attendait ».

Retenez simplement cette phrase, comme une note en marge : range est un outil universel pour parcourir des éléments, mais pour l’instant notre monde, c’est la chaîne. Et c’est déjà suffisant pour écrire du code utile et ne pas avoir peur des programmes des autres.

7. Erreurs typiques lors de l’utilisation de range sur des chaînes

Erreur n°1 : s’attendre à ce que for v := range s donne des « caractères ».
C’est sans doute la confusion la plus fréquente. Dans la forme for one := range s, la variable reçoit l’indice, et non la valeur. Résultat : vous comparez un « indice » à 'a', rien n’est trouvé, et vous avez l’impression que le programme « ne marche pas ». La solution est simple : soit vous écrivez deux valeurs (for i, v := range s), soit vous utilisez explicitement _ (for _, v := range s).

Erreur n°2 : déclarer i, v, mais n’utiliser qu’un seul et obtenir une erreur de compilation.
Go n’aime pas les variables « mortes ». Si vous avez écrit for i, v := range s, mais que i n’est utilisé nulle part, le compilateur arrêtera la compilation. Ce n’est pas du pinaillement : généralement, une variable inutilisée est le signe d’une logique inachevée ou d’une erreur. Si l’indice ne vous est vraiment pas utile, remplacez-le par _.

Erreur n°3 : penser que value est une string.
Dans range sur une chaîne, la valeur est un nombre (le code du caractère), et non une sous-chaîne. C’est pourquoi fmt.Println(v) affiche des nombres, ce qui est surprenant. C’est un comportement normal : une chaîne est constituée d’octets, et range renvoie les caractères sous forme de codes numériques. Formellement, il s’agit d’un rune (un alias de int32).

Erreur n°4 : croire que index est le « numéro du caractère » et augmente toujours de 1.
Pour des lettres latines simples, vous verrez souvent 0, 1, 2, 3… et vous aurez l’impression que ce sera toujours le cas. Mais pour des chaînes contenant des caractères « non simples », l’indice peut sauter. Cela ne signifie pas que range est cassé — cela signifie que la chaîne est stockée en octets, et que certains caractères prennent plus d’un octet (avec des exemples comme "Hello, 世界", cela se voit très bien).

Erreur n°5 : mélanger continue et la mise à jour de l’état dans une logique plus complexe.
Avec range, vous ne mettez généralement pas l’indice à jour manuellement, mais l’état (drapeaux, compteurs, « trouvé/pas trouvé ») doit quand même être mis à jour. Si, à l’intérieur de la boucle, vous faites continue trop tôt, vous pouvez sauter une mise à jour nécessaire d’une variable et obtenir un mauvais résultat. Une bonne technique consiste à garder la mise à jour des variables près de la condition, afin que tout se lise de manière directe.

1
Mission
Go SELF, niveau 4, leçon 3
Bloqué
Traces dans le journal
Traces dans le journal
1
Mission
Go SELF, niveau 4, leçon 3
Bloqué
Premier tiret
Premier tiret
1
Mission
Go SELF, niveau 4, leçon 3
Bloqué
Réparation de la carte
Réparation de la carte
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION