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.
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?
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.
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.
Jadi, mengapa menggunakan iterator dalam Go?
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!