Si vous êtes un programmeur Go, vous avez probablement entendu parler des itérateurs à plusieurs reprises dans Go 1.22, et surtout dans Go 1.23 . Mais peut-être que vous vous grattez encore la tête, en vous demandant pourquoi ils sont utiles ou quand vous devriez les utiliser. Eh bien, vous êtes au bon endroit ! Commençons par examiner comment fonctionnent les itérateurs dans Go et pourquoi ils peuvent être si utiles.
Imaginez que nous avons une liste de nombres et que nous voulons doubler chaque nombre. Nous pourrions le faire en utilisant une fonction simple comme celle ci-dessous :
package main import ( "fmt" ) func NormalTransform[T1, T2 any](list []T1, transform func(T1) T2) []T2 { transformed := make([]T2, len(list)) for i, t := range list { transformed[i] = transform(t) } return transformed } func main() { list := []int{1, 2, 3, 4, 5} doubleFunc := func(i int) int { return i * 2 } for i, num := range NormalTransform(list, doubleFunc) { fmt.Println(i, num) } }
Voici ce qui se passe lorsque vous exécutez ce code :
0 2 1 4 2 6 3 8 4 10
Assez simple, non ? Il s'agit d'une fonction Go générique de base qui prend une liste de n'importe quel type T1, applique une fonction de transformation à chaque élément et renvoie une nouvelle liste avec la liste transformée de n'importe quel type T2. Facile à comprendre si vous connaissez Go génériques !
Mais et si je vous disais qu'il existe une autre façon de gérer cela : en utilisant un itérateur ?
Voyons maintenant comment utiliser un itérateur pour la même transformation :
package main import ( "fmt" ) func IteratorTransform[T1, T2 any](list []T1, transform func(T1) T2) iter.Seq2[int, T2] { return func(yield func(int, T2) bool) { for i, t := range list { if !yield(i, transform(t)) { return } } } } func main() { list := []int{1, 2, 3, 4, 5} doubleFunc := func(i int) int { return i * 2 } for i, num := range NormalTransform(list, doubleFunc) { fmt.Println(i, num) } }
Avant de l'exécuter, vous devez vous assurer que votre version Go est 1.23. Le résultat est exactement le même :
0 2 1 4 2 6 3 8 4 10
Mais attendez, pourquoi aurions-nous besoin d'un itérateur ici ? N'est-ce pas plus compliqué ? Examinons les différences.
À première vue, les itérateurs semblent un peu trop conçus pour quelque chose d'aussi simple que transformer une liste. Mais lorsque vous effectuez des benchmarks, vous commencez à comprendre pourquoi ils valent la peine d’être pris en compte !
Évaluons les deux méthodes et voyons comment elles fonctionnent :
package main import ( "testing" ) var ( transform = func(i int) int { return i * 2 } list = []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} ) func BenchmarkNormalTransform(b *testing.B) { for i := 0; i < b.N; i++ { NormalTransform(list, transform) } } func BenchmarkIteratorTransform(b *testing.B) { for i := 0; i < b.N; i++ { IteratorTransform(list, transform) } }
Voici le résultat initial du benchmark :
BenchmarkNormalTransform-8 41292933 29.49 ns/op BenchmarkIteratorTransform-8 1000000000 0.3135 ns/op
Waouh ! C'est une énorme différence ! Mais attendez, il y a un peu d’injustice ici. La fonction NormalTransform renvoie une liste entièrement transformée, tandis que la fonction IteratorTransform configure uniquement l'itérateur sans encore transformer la liste.
Rendons les choses équitables en parcourant entièrement l'itérateur :
func BenchmarkIteratorTransform(b *testing.B) { for i := 0; i < b.N; i++ { for range IteratorTransform(list, transform) { } } }
Maintenant, les résultats sont plus raisonnables :
BenchmarkNormalTransform-8 40758822 29.16 ns/op BenchmarkIteratorTransform-8 53967146 22.39 ns/op
D'accord, l'itérateur est un peu plus rapide. Pourquoi? Parce que NormalTransform crée une liste entière transformée en mémoire (sur le tas) avant de la renvoyer, tandis que l'itérateur effectue la transformation au fur et à mesure que vous la parcourez, économisant du temps et de la mémoire.
En savoir plus sur Stack et Heap ici
La vraie magie de l'itérateur se produit lorsque vous n'avez pas besoin de traiter toute la liste. Comparons un scénario où nous souhaitons trouver le chiffre 4 uniquement après avoir transformé la liste :
func BenchmarkNormalTransform(b *testing.B) { for i := 0; i < b.N; i++ { for _, num := range NormalTransform(list, transform) { if num == 4 { break } } } } func BenchmarkIteratorTransform(b *testing.B) { for i := 0; i < b.N; i++ { for _, num := range IteratorTransform(list, transform) { if num == 4 { break } } } }
Les résultats parlent d'eux-mêmes :
package main import ( "fmt" ) func NormalTransform[T1, T2 any](list []T1, transform func(T1) T2) []T2 { transformed := make([]T2, len(list)) for i, t := range list { transformed[i] = transform(t) } return transformed } func main() { list := []int{1, 2, 3, 4, 5} doubleFunc := func(i int) int { return i * 2 } for i, num := range NormalTransform(list, doubleFunc) { fmt.Println(i, num) } }
Dans ce cas, l'itérateur est beaucoup plus rapide ! Pourquoi? Étant donné que l’itérateur ne transforme pas la liste entière : il s’arrête dès qu’il trouve le résultat que vous recherchez. D'un autre côté, NormalTransform transforme toujours la liste entière, même si nous ne nous soucions que d'un seul élément.
Alors, pourquoi utiliser un itérateur dans Go ?
Itérateurs : ils sont rapides, flexibles et amusants, une fois que vous les maîtrisez !
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!