Maison  >  Article  >  développement back-end  >  Comprendre les itérateurs dans Go : une plongée amusante !

Comprendre les itérateurs dans Go : une plongée amusante !

Patricia Arquette
Patricia Arquetteoriginal
2024-10-25 02:28:02184parcourir

Understanding Iterators in Go: A Fun Dive!

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.

Une transformation simple : pas encore d'itérateur

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 ?

Entrez l'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.

Pourquoi utiliser un itérateur ?

À 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.

Conclusion : quand utiliser les itérateurs ?

Alors, pourquoi utiliser un itérateur dans Go ?

  • Efficacité : les itérateurs peuvent économiser du temps et de la mémoire en ne traitant pas la liste entière si vous n'en avez pas besoin.
  • Flexibilité : Ils vous permettent de gérer efficacement de grands ensembles de données, en particulier lorsque vous travaillez avec des flux de données ou lorsque vous devez vous arrêter plus tôt. Mais gardez à l’esprit que les itérateurs peuvent être un peu plus difficiles à comprendre et à mettre en œuvre. Utilisez-les lorsque vous avez besoin d'une amélioration supplémentaire des performances, en particulier dans les scénarios où vous n'avez pas besoin de travailler avec une liste complète à l'avance.

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!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn