zoukankan      html  css  js  c++  java
  • 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
    ", i)
        }
    }
    

    回调函数的做法

    // 将迭代的数值传递到回调函数
    func printEvenNumbers(max int) {
        err := iterateEvenNumbers(max, func(n int) error {
            fmt.Printf("%d
    ", n)
            return nil
        })
        if err != nil {
            log.Fatalf("error: %s
    ", 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
    ", iter.Value())
    	}
    	if iter.Err() != nil {
    		log.Fatalf("error: %s
    ", iter.Err())
    	}
    }
    
    func main() {
    	fmt.Printf("Even numbers up to 8:
    ")
    	printEvenNumbers(8)
    	fmt.Printf("Even numbers up to 9:
    ")
    	printEvenNumbers(9)
    	fmt.Printf("Error: even numbers up to -1:
    ")
    	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
    ", val.Err)
            }
            fmt.Printf("%d
    ", 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
    ", val.Err)
    		}
    		fmt.Printf("%d
    ", val.Int)
    	}
    }
    
    func main() {
    	fmt.Printf("Even numbers up to 8:
    ")
    	printEvenNumbers(8)
    	fmt.Printf("Even numbers up to 9:
    ")
    	printEvenNumbers(9)
    	fmt.Printf("Error: even numbers up to -1:
    ")
    	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
    ", val.Err)
    		}
    		if val.Int > stopAt {
    			cancel()
    			// notice we keep going in order to drain the channel
    			continue
    		}
    		// process the value
    		fmt.Printf("%d
    ", val.Int)
    	}
    }
    
    func main() {
    	fmt.Printf("Even numbers up to 20, cancel at 8:
    ")
    	printEvenNumbersCancellable(20, 8)
    }
    

    总结:

    1. 回调方式实现起来最简单但是语法很别扭
    2. Next()方法实现最困难,但是对调用方很友好,标准库里运用了这种复杂写法
    3. channel的实现很好,对系统资源的消耗最昂贵,channel应该与goroutine搭配使用,否则尽量不用
  • 相关阅读:
    JavaScript 之 typeof
    Octotree Chrome安装与使用方法
    支持主流MySQL中间件的数据迁移工具ora2mysql
    Eclipse搭建SpringBoot之HelloWorld
    Spring Boot插件spring tool suite安装及使用
    树的前中后序遍历非递归实现
    判断是否是完全二叉树
    Leetcode 437_path sum III todo
    DFS回溯只在递归基回溯————leetcode112
    Leetcode 94 Binary Tree Inorder
  • 原文地址:https://www.cnblogs.com/linyihai/p/10842020.html
Copyright © 2011-2022 走看看