LoginSignup
9
7

More than 5 years have passed since last update.

golang 3ways to iterate

Last updated at Posted at 2017-11-21

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.

やっぱりそうですよね.

9
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
7