天天看點

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