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搭配使用,否则尽量不用

转载于:https://www.cnblogs.com/linyihai/p/10842020.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值