Rumah  >  Artikel  >  pembangunan bahagian belakang  >  Memahami Iterator dalam Go: A Fun Dive!

Memahami Iterator dalam Go: A Fun Dive!

Patricia Arquette
Patricia Arquetteasal
2024-10-25 02:28:02184semak imbas

Understanding Iterators in Go: A Fun Dive!

Jika anda seorang pengaturcara Go, anda mungkin pernah mendengar tentang iterator berkali-kali dalam Go 1.22, dan terutamanya dalam Go 1.23 . Tetapi mungkin anda masih menggaru kepala anda, tertanya-tanya mengapa ia berguna atau bila anda perlu menggunakannya. Nah, anda berada di tempat yang betul! Mari mulakan dengan melihat cara iterator berfungsi dalam Go dan sebab ia boleh menjadi sangat berguna.

Satu Transformasi Mudah: Belum Ada Iterator

Bayangkan kita mempunyai senarai nombor, dan kita mahu menggandakan setiap nombor. Kita boleh melakukan ini menggunakan fungsi mudah seperti di bawah:

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)
    }
}

Inilah yang berlaku apabila anda menjalankan kod ini:

0 2
1 4
2 6
3 8
4 10

Agak mudah, bukan? Ini ialah fungsi Go generik asas yang mengambil senarai mana-mana jenis T1, menggunakan fungsi transformasi pada setiap elemen dan mengembalikan senarai baharu dengan senarai diubah bagi sebarang jenis T2. Mudah difahami jika anda tahu Go generik!

Tetapi bagaimana jika saya memberitahu anda ada cara lain untuk menangani perkara ini—menggunakan lelaran?

Masukkan Iterator!

Sekarang, mari kita lihat bagaimana anda boleh menggunakan lelaran untuk transformasi yang sama:

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)
    }
}

Sebelum menjalankannya, anda mesti memastikan versi Go anda 1.23. Outputnya betul-betul sama:

0 2
1 4
2 6
3 8
4 10

Tetapi tunggu, mengapa kita memerlukan iterator di sini? Bukankah itu lebih rumit? Mari kita gali perbezaannya.

Mengapa Menggunakan Iterator?

Pada pandangan pertama, iterator kelihatan agak terlalu kejuruteraan untuk sesuatu yang mudah seperti mengubah senarai. Tetapi apabila anda menjalankan penanda aras, anda mula melihat sebab ia patut dipertimbangkan!

Mari kita tanda aras kedua-dua kaedah dan lihat prestasinya:

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)
    }
}

Berikut ialah hasil penanda aras awal:

BenchmarkNormalTransform-8      41292933                29.49 ns/op
BenchmarkIteratorTransform-8    1000000000               0.3135 ns/op

Wah! Itulah perbezaan yang besar! Tetapi tunggu-ada sedikit ketidakadilan di sini. Fungsi NormalTransform mengembalikan senarai yang diubah sepenuhnya, manakala fungsi IteratorTransform hanya menyediakan iterator tanpa mengubah senarai lagi.

Mari jadikan ia adil dengan menggelung sepenuhnya melalui lelaran:

func BenchmarkIteratorTransform(b *testing.B) {
    for i := 0; i < b.N; i++ {
        for range IteratorTransform(list, transform) {
        }
    }
}

Kini hasilnya lebih munasabah:

BenchmarkNormalTransform-8      40758822                29.16 ns/op
BenchmarkIteratorTransform-8    53967146                22.39 ns/op

Baiklah, lelarannya lebih pantas. kenapa? Kerana NormalTransform mencipta keseluruhan senarai yang diubah dalam ingatan (pada timbunan) sebelum mengembalikannya, manakala lelaran melakukan transformasi semasa anda mengulanginya, menjimatkan masa dan ingatan.

Baca lebih lanjut tentang Timbunan dan Timbunan di sini

Keajaiban sebenar pengulang berlaku apabila anda tidak perlu memproses keseluruhan senarai. Mari kita tanda aras senario di mana kita hanya mahu mencari nombor 4 selepas menukar senarai:

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
            }
        }
    }
}

Hasilnya bercakap untuk diri mereka sendiri:

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)
    }
}

Dalam kes ini, iterator adalah lebih pantas! kenapa? Kerana iterator tidak mengubah keseluruhan senarai—ia berhenti sebaik sahaja ia menemui hasil yang anda cari. Sebaliknya, NormalTransform masih mengubah keseluruhan senarai, walaupun kami hanya mengambil berat tentang satu item.

Kesimpulan: Bilakah Menggunakan Iterator?

Jadi, mengapa menggunakan iterator dalam Go?

  • Kecekapan: Iterator boleh menjimatkan masa dan memori dengan tidak memproses keseluruhan senarai jika anda tidak memerlukannya.
  • Fleksibiliti: Ia membolehkan anda mengendalikan set data yang besar dengan cekap, terutamanya apabila bekerja dengan aliran data atau apabila anda perlu berhenti awal. Tetapi perlu diingat, iterator boleh menjadi lebih rumit untuk difahami dan dilaksanakan. Gunakannya apabila anda memerlukan peningkatan prestasi tambahan itu, terutamanya dalam senario di mana anda tidak perlu bekerja dengan keseluruhan senarai di muka.

Pelajar: Mereka pantas, fleksibel dan menyeronokkan—sebaik sahaja anda memahaminya!

Atas ialah kandungan terperinci Memahami Iterator dalam Go: A Fun Dive!. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn