官方教程 A Tour of Go Excercises|青训营笔记

296 阅读4分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第1篇笔记

这两天在学 A Tour of Go 官方的语法教学,里面有很多的 Excercise(训练题),依照我做完就忘的性格,还是留下一点记录吧,加深印象。

go tour 学习笔记

练习:循环与函数

为了练习函数与循环,我们来实现一个平方根函数:用牛顿法实现平方根函数。

计算机通常使用循环来计算 x 的平方根。从某个猜测的值 z 开始,我们可以根据 z² 与 x 的近似度来调整 z,产生一个更好的猜测:

z=(zzx)/(2z)z -= (z*z - x) / (2*z)

重复调整的过程,猜测的结果会越来越精确,得到的答案也会尽可能接近实际的平方根。

在提供的 func Sqrt 中实现它。无论输入是什么,对 z 的一个恰当的猜测为 1。 要开始,请重复计算 10 次并随之打印每次的 z 值。观察对于不同的值 x(1、2、3 ...), 你得到的答案是如何逼近结果的,猜测提升的速度有多快。

提示:用类型转换或浮点数语法来声明并初始化一个浮点数值:

z:=1.0z := 1.0

z:=float64(1)z := float64(1)

然后,修改循环条件,使得当值停止改变(或改变非常小)的时候退出循环。观察迭代次数大于还是小于 10。 尝试改变 z 的初始猜测,如 x 或 x/2。你的函数结果与标准库中的 math.Sqrt 接近吗?

注: 如果你对该算法的细节感兴趣,上面的 z² − x 是 z² 到它所要到达的值(即 x)的距离, 除以的 2z 为 z² 的导数,我们通过 z² 的变化速度来改变 z 的调整量。 这种通用方法叫做牛顿法。 它对很多函数,特别是平方根而言非常有效。)

package main

import (

        "fmt"

        "math"

)

func Sqrt(x float64) float64 {

        z := 1.0

        eps := 1e-6

        for i:=0; math.Abs(z*z - x) > eps; i++{

                fmt.Println(i, "z = ", z, "z^2 - x", z * z - x)

                z -= (z*z - x) / (2*z)

        }

        return z

}

func main() {
        fmt.Println(Sqrt(2))
}

练习:切片

实现 Pic。它应当返回一个长度为 dy 的切片,其中每个元素是一个长度为 dx,元素类型为 uint8 的切片。当你运行此程序时,它会将每个整数解释为灰度值(好吧,其实是蓝度值)并显示它所对应的图像。

图像的选择由你来定。几个有趣的函数包括 (x+y)/2, x*y, x^y, x*log(y)x%(y+1)

(提示:需要使用循环来分配 [][]uint8 中的每个 []uint8;请使用 uint8(intValue) 在类型之间转换;你可能会用到 math 包中的函数。)

package main



import "golang.org/x/tour/pic"



func Pic(dx, dy int) [][]uint8 {

        ans := make([][]uint8,dy)

        for i,_ := range ans{

                ans[i] = make([]uint8,dx)

                for j,_ := range ans[i]{

                        ans[i][j] = uint8(i^j)

                }

        }

        return ans

}



func main() {

        pic.Show(Pic)

}

练习:映射

实现 WordCount。它应当返回一个映射,其中包含字符串 s 中每个“单词”的个数。函数 wc.Test 会对此函数执行一系列测试用例,并输出成功还是失败。

你会发现 strings.Fields 很有帮助。

package main



import (

        "golang.org/x/tour/wc"

        "strings"

)



func WordCount(s string) map[string]int {

        m := make(map[string]int)

        words := strings.Fields(s)

        for _, word := range words{

                m[word]++;

        }

        return m

}



func main() {

        wc.Test(WordCount)

}

练习:斐波纳契闭包

让我们用函数做些好玩的事情。

实现一个 fibonacci 函数,它返回一个函数(闭包),该闭包返回一个斐波纳契数列 (0, 1, 1, 2, 3, 5, ...)

package main



import "fmt"



// 返回一个“返回int的函数”

func fibonacci() func() int {

        f0, f1 := 0, 1

        return func () int{

                ans := f0

                f0, f1 = f1, f0 + f1

                return ans

        }

}



func main() {

        f := fibonacci()

        for i := 0; i < 10; i++ {

                fmt.Println(f())

        }

}

练习:Stringer

通过让 IPAddr 类型实现 fmt.Stringer 来打印点号分隔的地址。

例如,IPAddr{1, 2, 3, 4} 应当打印为 "1.2.3.4"

package main



import "fmt"



type IPAddr [4]byte



// TODO: 给 IPAddr 添加一个 "String() string" 方法

func (ipaddr IPAddr) String() string{

        return fmt.Sprintf("%d.%d.%d.%d",ipaddr[0],ipaddr[1],ipaddr[2],ipaddr[3])

}

func main() {

        hosts := map[string]IPAddr{

                "loopback":  {127, 0, 0, 1},

                "googleDNS": {8, 8, 8, 8},

        }

        for name, ip := range hosts {

                fmt.Printf("%v: %v\n", name, ip)

        }

}

练习:错误

之前的练习中复制 Sqrt 函数,修改它使其返回 error 值。

Sqrt 接受到一个负数时,应当返回一个非 nil 的错误值。复数同样也不被支持。

创建一个新的类型

type ErrNegativeSqrt float64

并为其实现

func (e ErrNegativeSqrt) Error() string

方法使其拥有 error 值,通过 ErrNegativeSqrt(-2).Error() 调用该方法应返回 "cannot Sqrt negative number: -2"

注意:Error 方法内调用 fmt.Sprint(e) 会让程序陷入死循环。可以通过先转换 e 来避免这个问题:fmt.Sprint(float64(e))。这是为什么呢?

修改 Sqrt 函数,使其接受一个负数时,返回 ErrNegativeSqrt 值。

package main



import (

        "fmt"

        "math"

)

type ErrNegativeSqrt float64



func (e ErrNegativeSqrt) Error() string{

        return fmt.Sprintf("cannot Sqrt negative number: %v", float64(e))

}

func Sqrt(x float64) (float64, error) {

        if x < 0{

                return 0,ErrNegativeSqrt(x)

        }

        z := 1.0

        eps := 1e-6

        for i:=0; math.Abs(z*z - x) > eps; i++{

                //fmt.Println(i, "z = ", z, "z^2 - x", z * z - x)

                z -= (z*z - x) / (2*z)

        }

        return z, nil

}



func main() {

        fmt.Println(Sqrt(2))

        fmt.Println(Sqrt(-2))

}

练习:Reader

实现一个 Reader 类型,它产生一个 ASCII 字符 'A' 的无限流。

package main



import "golang.org/x/tour/reader"



type MyReader struct{}



// TODO: 给 MyReader 添加一个 Read([]byte) (int, error) 方法

func (myreader MyReader)Read(b []byte) (int, error){

        blength := len(b)

        for i,_ := range b{

                b[i] = 'A'

        }

        return blength,nil

}



func main() {

        reader.Validate(MyReader{})

}

练习:rot13Reader

有种常见的模式是一个 io.Reader 包装另一个 io.Reader,然后通过某种方式修改其数据流。

例如,gzip.NewReader 函数接受一个 io.Reader(已压缩的数据流)并返回一个同样实现了 io.Reader*gzip.Reader(解压后的数据流)。

编写一个实现了 io.Reader 并从另一个 io.Reader 中读取数据的 rot13Reader,通过应用 rot13 代换密码对数据流进行修改。

rot13Reader 类型已经提供。实现 Read 方法以满足 io.Reader

package main



import (

        "io"

        "os"

        "strings"

)



type rot13Reader struct {

        r io.Reader

}



func rot13(c byte) byte{

        switch {

        case (c >= 'A' && c <= 'M') || (c >= 'a' && c <= 'm'):

                c += 13

        case (c >= 'N' && c <= 'Z') || (c >= 'n' && c <= 'z'):

                c -= 13

        }

        return c

}



func (reader *rot13Reader)Read(b []byte) (int, error){

        n, err := reader.r.Read(b)

        for i := range b{

                b[i]=rot13(b[i])

        }

        return n,err

}





func main() {

        s := strings.NewReader("Lbh penpxrq gur pbqr!")

        r := rot13Reader{s}

        io.Copy(os.Stdout, &r)

}

练习:图像

还记得之前编写的图片生成器 吗?我们再来编写另外一个,不过这次它将会返回一个 image.Image 的实现而非一个数据切片。

定义你自己的 Image 类型,实现必要的方法并调用 pic.ShowImage

Bounds 应当返回一个 image.Rectangle ,例如 image.Rect(0, 0, w, h)

ColorModel 应当返回 color.RGBAModel

At 应当返回一个颜色。上一个图片生成器的值 v 对应于此次的 color.RGBA{v, v, 255, 255}

package main



import (

        "golang.org/x/tour/pic"

        "image"

        "image/color"

)



type Image struct{

        W int

        H int

}



func (i Image) ColorModel() color.Model {

        return color.RGBAModel

}



func (i Image) Bounds() image.Rectangle {

        return image.Rect(0, 0, i.W, i.H)

}



func (i Image) At(x, y int)  color.Color {

        v := uint8(x*y + y*y)

        return color.RGBA{v, v, 255, 255}

}



func main() {

        m := Image{200, 200}

        pic.ShowImage(m)

}

练习:等价二叉查找树

1. 实现 Walk 函数。

2. 测试 Walk 函数。

函数 tree.New(k) 用于构造一个随机结构的已排序二叉查找树,它保存了值 k, 2k, 3k, ..., 10k

创建一个新的信道 ch 并且对其进行步进:

go Walk(tree.New(1), ch)

然后从信道中读取并打印 10 个值。应当是数字 1, 2, 3, ..., 10

3.Walk 实现 Same 函数来检测 t1t2 是否存储了相同的值。

4. 测试 Same 函数。

Same(tree.New(1), tree.New(1)) 应当返回 true,而 Same(tree.New(1), tree.New(2)) 应当返回 false

Tree 的文档可在这里找到。

package main



import "golang.org/x/tour/tree"

import "fmt"



// Walk 步进 tree t 将所有的值从 tree 发送到 channel ch。

func Walk(t *tree.Tree, ch chan int){

        _walk(t, ch)

        close(ch)

}



func _walk(t *tree.Tree, ch chan int){

        if t != nil {

                _walk(t.Left, ch)

                ch <- t.Value

                _walk(t.Right, ch)

        }

}



// Same 检测树 t1 和 t2 是否含有相同的值。

func Same(t1, t2 *tree.Tree) bool{

        ch1 := make(chan int)

        ch2 := make(chan int)

        go Walk(t1, ch1)

        go Walk(t2, ch2)

        for i := range ch1{

                if(i != <- ch2){

                        return false

                }

        }

        return true

}



func main() {

        //t1 := tree.New(1)

        //t2 := tree.New(2)

        ch := make(chan int)

        go Walk(tree.New(1), ch)

        for v := range ch{

                fmt.Println(v)

        }

        fmt.Println(Same(tree.New(1), tree.New(1)))

        fmt.Println(Same(tree.New(1), tree.New(2)))

}

练习:Web 爬虫

在这个练习中,我们将会使用 Go 的并发特性来并行化一个 Web 爬虫。

修改 Crawl 函数来并行地抓取 URL,并且保证不重复。

提示:你可以用一个 map 来缓存已经获取的 URL,但是要注意 map 本身并不是并发安全的!

package main



import (

    "fmt"

)



type Fetcher interface {

    // Fetch 返回 URL 的 body 内容,并且将在这个页面上找到的 URL 放到一个 slice 中。

    Fetch(url string) (body string, urls []string, err error)

}



var store map[string]bool



func Krawl(url string, fetcher Fetcher, Urls chan []string) {

    body, urls, err := fetcher.Fetch(url)

    if err != nil {

        fmt.Println(err)

    } else {

        fmt.Printf("found: %s %q\n", url, body)

    }

    Urls <- urls

}



// Crawl 使用 fetcher 从某个 URL 开始递归的爬取页面,直到达到最大深度。

func Crawl(url string, depth int, fetcher Fetcher) {

    // TODO: 并行的抓取 URL。

    Urls := make(chan []string)

    go Krawl(url, fetcher, Urls)

    band := 1

    store[url] = true

    for i := 0; i < depth; i++ {

        for band > 0 {

            band--

            next := <-Urls

            for _, url := range next {

                if _, done := store[url]; !done {

                    store[url] = true

                    band++

                    go Krawl(url, fetcher, Urls)

                }

            }

        }

    }

    return

}



//  // TODO: 不重复抓取页面。

//         // 下面并没有实现上面两种情况:

//  if depth <= 0 {

//      return

//  }

//  body, urls, err := fetcher.Fetch(url)

//  if err != nil {

//      fmt.Println(err)

//      return

//  }

//  fmt.Printf("found: %s %q\n", url, body)

//  for _, u := range urls {

//      Crawl(u, depth-1, fetcher)

//  }

//  return

// }



func main() {

    store = make(map[string]bool)

    Crawl("https://golang.org/", 4, fetcher)

}



// fakeFetcher 是返回若干结果的 Fetcher。

type fakeFetcher map[string]*fakeResult



type fakeResult struct {

    body string

    urls []string

}



func (f fakeFetcher) Fetch(url string) (string, []string, error) {

    if res, ok := f[url]; ok {

        return res.body, res.urls, nil

    }

    return "", nil, fmt.Errorf("not found: %s", url)

}



// fetcher 是填充后的 fakeFetcher。

var fetcher = fakeFetcher{

    "https://golang.org/": &fakeResult{

        "The Go Programming Language",

        []string{

            "https://golang.org/pkg/",

            "https://golang.org/cmd/",

        },

    },

    "https://golang.org/pkg/": &fakeResult{

        "Packages",

        []string{

            "https://golang.org/",

            "https://golang.org/cmd/",

            "https://golang.org/pkg/fmt/",

            "https://golang.org/pkg/os/",

        },

    },

    "https://golang.org/pkg/fmt/": &fakeResult{

        "Package fmt",

        []string{

            "https://golang.org/",

            "https://golang.org/pkg/",

        },

    },

    "https://golang.org/pkg/os/": &fakeResult{

        "Package os",

        []string{

            "https://golang.org/",

            "https://golang.org/pkg/",

        },

    },

}