go中如何更好的迭代

三種迭代方式

3 ways to iterate in Gohtml

有以下三種迭代的寫法:python

  • 回調函數方式迭代
  • 經過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

例子code

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
}

例子:htm

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中止迭代功能blog

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搭配使用,不然儘可能不用
相關文章
相關標籤/搜索