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

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

Patricia Arquette
Libérer: 2024-10-25 02:28:02
original
294 Les gens l'ont consulté

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)
    }
}
Copier après la connexion
Copier après la connexion

Voici ce qui se passe lorsque vous exécutez ce code :

0 2
1 4
2 6
3 8
4 10
Copier après la connexion
Copier après la connexion

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)
    }
}
Copier après la connexion

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
Copier après la connexion
Copier après la connexion

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)
    }
}
Copier après la connexion

Voici le résultat initial du benchmark :

BenchmarkNormalTransform-8      41292933                29.49 ns/op
BenchmarkIteratorTransform-8    1000000000               0.3135 ns/op
Copier après la connexion

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) {
        }
    }
}
Copier après la connexion

Maintenant, les résultats sont plus raisonnables :

BenchmarkNormalTransform-8      40758822                29.16 ns/op
BenchmarkIteratorTransform-8    53967146                22.39 ns/op
Copier après la connexion

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
            }
        }
    }
}
Copier après la connexion

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)
    }
}
Copier après la connexion
Copier après la connexion

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!

source:dev.to
Déclaration de ce site Web
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
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal