Go プログラマーであれば、おそらく Go 1.22、特に Go 1.23 でイテレーターについて何度も聞いたことがあるでしょう。 。しかし、なぜそれらが役立つのか、いつ使用する必要があるのか、まだ頭を悩ませているかもしれません。そうですね、あなたは正しい場所にいます!まずは、Go でイテレータがどのように機能するのか、そしてなぜイテレータが非常に役立つのかを見てみましょう。
数値のリストがあり、それぞれの数値を 2 倍にしたいと想像してください。以下のような簡単な関数を使用してこれを行うことができます:
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) } }
このコードを実行すると、次のことが起こります:
0 2 1 4 2 6 3 8 4 10
とてもシンプルですよね?これは基本的な汎用 Go 関数で、任意の型 T1 のリストを受け取り、各要素に変換関数を適用して、任意の型 T2 の変換されたリストを含む新しいリストを返します。 Goジェネリックを知っていれば簡単に理解できます!
しかし、これを処理する別の方法、つまりイテレータを使用する方法があると言ったらどうなるでしょうか?
ここで、同じ変換にイテレータを使用する方法を見てみましょう:
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) } }
実行する前に、Go のバージョンが 1.23 であることを確認する必要があります。出力はまったく同じです:
0 2 1 4 2 6 3 8 4 10
しかし、待ってください、なぜここでイテレータが必要なのでしょうか?それはもっと複雑ではないでしょうか?違いを詳しく見てみましょう。
一見すると、イテレーターは、リストの変換のような単純なことに対して少し過剰設計されているように見えます。しかし、ベンチマークを実行すると、ベンチマークを検討する価値がある理由が分かり始めます!
両方のメソッドをベンチマークして、そのパフォーマンスを確認してみましょう:
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) } }
最初のベンチマーク結果は次のとおりです:
BenchmarkNormalTransform-8 41292933 29.49 ns/op BenchmarkIteratorTransform-8 1000000000 0.3135 ns/op
おお!それは大きな違いです!しかし、ちょっと待ってください。ここには少し不公平があります。 NormalTransform 関数は完全に変換されたリストを返しますが、IteratorTransform 関数はリストをまだ変換せずに反復子を設定するだけです。
イテレータを完全にループして公平にしましょう:
func BenchmarkIteratorTransform(b *testing.B) { for i := 0; i < b.N; i++ { for range IteratorTransform(list, transform) { } } }
結果はより合理的になりました:
BenchmarkNormalTransform-8 40758822 29.16 ns/op BenchmarkIteratorTransform-8 53967146 22.39 ns/op
わかりました、イテレーターは少し高速です。なぜ? NormalTransform は、変換されたリスト全体をメモリ (ヒープ上) に作成してから返しますが、反復子はループ処理中に変換を実行するため、時間とメモリが節約されます。
スタックとヒープ について詳しくは、こちらをご覧ください
イテレータの本当の魔法は、リスト全体を処理する必要がないときに起こります。リストを変換した後に数字の 4 だけを見つけたいというシナリオのベンチマークを行ってみましょう:
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 } } } }
結果がすべてを物語っています:
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) } }
この場合、反復子ははるかに高速です。なぜ?イテレーターはリスト全体を変換するわけではないため、探している結果が見つかるとすぐに停止します。一方、NormalTransform は、たとえ 1 つの項目だけを対象としても、リスト全体を変換します。
それでは、なぜ Go でイテレータを使用するのでしょうか?
イテレーター: コツを掴めば、高速かつ柔軟で楽しいです!
以上がGo のイテレータを理解する: 楽しいダイビング!の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。