三種疊代方式
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)
}
總結:
- 回調方式實作起來最簡單但是文法很别扭
- Next()方法實作最困難,但是對調用方很友好,标準庫裡運用了這種複雜寫法
- channel的實作很好,對系統資源的消耗最昂貴,channel應該與goroutine搭配使用,否則盡量不用