Help us understand the problem. What is going on with this article?

golang 3ways to iterate

More than 1 year has passed since last update.

goの3つのiterator

参考元
https://blog.kowalczyk.info/article/1Bkr/3-ways-to-iterate-in-go.html
github
https://github.com/kjk/go-cookbook/tree/master/3-ways-to-iterate

こちらのgolangのiteratorに関する記事が参考になったので、まとめました。

3つの common iteration patternがgoにはあるそうです。

  • callbacks
  • an iterator object with Next() method
  • channels

callback

func iterateOddNums(max int, f func(n int) error) error {
 if max < 0 {
     return errors.New("max must be greater than 0")
 }
 for i := 1; i <= max; i += 2 {
     err := f(i)
     if err != nil {
         return err
     }
 }
 return nil
}

func printOddNumbers(max int) error {
 err := iterateOddNums(max, func(n int) error {
     fmt.Printf("%d\n", n)
     return nil
 })
 return err
}

func main() {
 if err := printOddNumbers(20); err != nil {
     log.Fatal(err)
 }
}

関数を引数でもらうpattern。
callback関数はiterateを中断するべきかどうかの情報を返す
filepath.Walk で使われているpatternとのこと

next

type OddNumIterator struct {
 max     int
 current int
 err     error
}

func NewOddNumIterator(max int) *OddNumIterator {
 var err error
 if max < 0 {
     err = errors.New("max must be greater than 0")
 }
 return &OddNumIterator{
     max:     max,
     current: 1,
     err:     err,
 }
}

func (i *OddNumIterator) Next() bool {
 if i.err != nil {
     return false
 }
 i.current += 2
 return i.current <= i.max
}

func (i *OddNumIterator) Value() int {
 if i.err != nil || i.current > i.max {
     panic("something wrong")
 }
 return i.current
}

func (i *OddNumIterator) Err() error {
 return i.err
}

func printOddNums(max int) error {
 iter := NewOddNumIterator(max)
 for iter.Next() {
     fmt.Printf("%d\n", iter.Value())
 }
 return iter.Err()
}

func main() {
 if err := printOddNums(20); err != nil {
     log.Fatal(err)
 }
}
  • database/sqlRows.Next
  • bufioScanner.Scan

等で利用されていると思います

goroutine safeにするには, sync.Mutex を埋め込むべきか。
Value() がintのみを返すために、中でpanicを呼ぶのも直したい

channel

type IntWithErr struct {
 Int int
 Err error
}

func generateOddNums(ctx context.Context, max int) <-chan IntWithErr {
 ch := make(chan IntWithErr)
 go func() {
     defer close(ch)
     if max < 0 {
         ch <- IntWithErr{
             Err: errors.New("max must be greater than 0"),
         }
         return
     }

     for i := 1; i <= max; i += 2 {
         if ctx != nil {
             select {
             case <-ctx.Done():
                 return
             default:
             }
         }
         ch <- IntWithErr{
             Int: i,
         }
     }
 }()
 return ch
}

func printOddNums(max int, stopAt int) error {
 ctx, cancel := context.WithCancel(context.Background())
 defer cancel()
 ch := generateOddNums(ctx, 20)

 for val := range ch {
     if val.Err != nil {
         return val.Err
     }
     if val.Int > stopAt {
         cancel()
         break
     }
     fmt.Printf("%d\n", val.Int)
 }

 // drain
 for _ = range ch {
 }
 return nil
}

func main() {
 if err := printOddNums(100, 20); err != nil {
     log.Fatal(err)
 }
}

channelを利用するとpythonのgeneratorのように利用できるのだろうか。
cancel()を読んでも、呼んだ先が処理を停止する保証がないので、channelからdrainしないとgoroutine leak(?) するとのこと。

go func() {
   for _ = range ch {
   }
}()

のようにdrain処理も別のgoroutineに切り出したほうが安全かなと思った。

どれがBestか

The one that best fits your scenario.

やっぱりそうですよね.

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away