go中如何更好的迭代

三种迭代方式

3 ways to iterate in Go

有如下三种迭代的写法:

  • 回调函数方式迭代
  • 通过Next()方法迭代。参照python 迭代器的概念,自定义Next()方法来迭代
  • 通过channel实现迭代。

假设实现迭代从[2, max],打印出偶数。

func printEvenNumbers(max int) {
    if max < 0 {
        log.Fatalf("'max' is %d, should be >= 0", max)
    }
    for i := 2; i <= max; i += 2 {
        fmt.Printf("%d\n", i)
    }
}

回调函数的做法

// 将迭代的数值传递到回调函数
func printEvenNumbers(max int) {
    err := iterateEvenNumbers(max, func(n int) error {
        fmt.Printf("%d\n", n)
        return nil
    })
    if err != nil {
        log.Fatalf("error: %s\n", err)
    }
}
// 实际的迭代的结果,接受一个回调函数,由回调函数处理
func iterateEvenNumbers(max int, cb func(n int) error) error {
    if max < 0 {
        return fmt.Errorf("'max' is %d, must be >= 0", max)
    }
    for i := 2; i <= max; i += 2 {
        err := cb(i)
        if err != nil {
            return err
        }
    }
    return nil
}

Next()方法的迭代

// Next()方法放在for循环体之后,通过返回布尔值来控制是否迭代完毕
func (i *EvenNumberIterator) Next() bool 
// Value()方法返回当次迭代的值
func (i *EvenNumberIterator) Value() int

例子

package main

import (
	"fmt"
	"log"
)

// To run:
// go run next.go

// EvenNumberIterator generates even number
type EvenNumberIterator struct {
	max       int
	currValue int
	err       error
}

// NewEvenNumberIterator creates new number iterator
func NewEvenNumberIterator(max int) *EvenNumberIterator {
	var err error
	if max < 0 {
		err = fmt.Errorf("'max' is %d, should be >= 0", max)
	}
	return &EvenNumberIterator{
		max:       max,
		currValue: 0,
		err:       err,
	}
}

// Next advances to next even number. Returns false on end of iteration.
func (i *EvenNumberIterator) Next() bool {
	if i.err != nil {
		return false
	}
	i.currValue += 2
	return i.currValue <= i.max
}

// Value returns current even number
func (i *EvenNumberIterator) Value() int {
	if i.err != nil || i.currValue > i.max {
		panic("Value is not valid after iterator finished")
	}
	return i.currValue
}

// Err returns iteration error.
func (i *EvenNumberIterator) Err() error {
	return i.err
}

func printEvenNumbers(max int) {
	iter := NewEvenNumberIterator(max)
	for iter.Next() {
		fmt.Printf("n: %d\n", iter.Value())
	}
	if iter.Err() != nil {
		log.Fatalf("error: %s\n", iter.Err())
	}
}

func main() {
	fmt.Printf("Even numbers up to 8:\n")
	printEvenNumbers(8)
	fmt.Printf("Even numbers up to 9:\n")
	printEvenNumbers(9)
	fmt.Printf("Error: even numbers up to -1:\n")
	printEvenNumbers(-1)
}

chan方式迭代

// 定义一个返回channel的函数
func generateEvenNumbers(max int) chan IntWithError
// IntWithError struct
type IntWithError struct {
    Int int
    Err error
}
// 调用方法,range方法可以接chan遍历的特性
func printEvenNumbers(max int) {
    for val := range generateEvenNumbers(max) {
        if val.Err != nil {
            log.Fatalf("Error: %s\n", val.Err)
        }
        fmt.Printf("%d\n", val.Int)
    }
}
// 完整generateEvenNumbers
func generateEvenNumbers(max int) chan IntWithError {
    ch := make(chan IntWithError)
    go func() {
        defer close(ch)
        if max < 0 {
            ch <- IntWithError{
                Err: fmt.Errorf("'max' is %d and should be >= 0", max),
            }
            return
        }

        for i := 2; i <= max; i += 2 {
            ch <- IntWithError{
                Int: i,
            }
        }
    }()
    return ch
}

例子:

package main

import (
	"fmt"
	"log"
)

// To run:
// go run channel.go

// IntWithError combines an integer value and an error
type IntWithError struct {
	Int int
	Err error
}

func generateEvenNumbers(max int) chan IntWithError {
	ch := make(chan IntWithError)
	go func() {
		defer close(ch)
		if max < 0 {
			ch <- IntWithError{
				Err: fmt.Errorf("'max' is %d and should be >= 0", max),
			}
			return
		}

		for i := 2; i <= max; i += 2 {
			ch <- IntWithError{
				Int: i,
			}
		}
	}()
	return ch
}

func printEvenNumbers(max int) {
	for val := range generateEvenNumbers(max) {
		if val.Err != nil {
			log.Fatalf("Error: %s\n", val.Err)
		}
		fmt.Printf("%d\n", val.Int)
	}
}

func main() {
	fmt.Printf("Even numbers up to 8:\n")
	printEvenNumbers(8)
	fmt.Printf("Even numbers up to 9:\n")
	printEvenNumbers(9)
	fmt.Printf("Error: even numbers up to -1:\n")
	printEvenNumbers(-1)
}

通过context实现cancel停止迭代功能

package main

import (
	"context"
	"fmt"
	"log"
)

// To run:
// go run channel-cancellable.go

// IntWithError combines an integer value and an error
type IntWithError struct {
	Int int
	Err error
}

func generateEvenNumbers(ctx context.Context, max int) chan IntWithError {
	ch := make(chan IntWithError)
	go func() {
		defer close(ch)
		if max < 0 {
			ch <- IntWithError{
				Err: fmt.Errorf("'max' is %d and should be >= 0", max),
			}
			return
		}

		for i := 2; i <= max; i += 2 {
			if ctx != nil {
				// if context was cancelled, we stop early
				select {
				case <-ctx.Done():
					return
				default:
				}
			}
			ch <- IntWithError{
				Int: i,
			}
		}
	}()
	return ch
}

func printEvenNumbersCancellable(max int, stopAt int) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	ch := generateEvenNumbers(ctx, max)
	for val := range ch {
		if val.Err != nil {
			log.Fatalf("Error: %s\n", val.Err)
		}
		if val.Int > stopAt {
			cancel()
			// notice we keep going in order to drain the channel
			continue
		}
		// process the value
		fmt.Printf("%d\n", val.Int)
	}
}

func main() {
	fmt.Printf("Even numbers up to 20, cancel at 8:\n")
	printEvenNumbersCancellable(20, 8)
}

总结:

  1. 回调方式实现起来最简单但是语法很别扭
  2. Next()方法实现最困难,但是对调用方很友好,标准库里运用了这种复杂写法
  3. channel的实现很好,对系统资源的消耗最昂贵,channel应该与goroutine搭配使用,否则尽量不用
posted @ 2019-05-10 00:23  yihailin  阅读(319)  评论(0编辑  收藏  举报