正确解答A Tour of Go的练习问题

首先

我认为《Go之旅》是一本很好的入门教材,但是在练习问题中,有些问题初次看到时感到很难。有些问题即使查了也很难找到能直接运行的答案,所以我想留下答案供日后参考。

    • そのままコピーして正しく動く

 

    • 必要ないパッケージを使わない

 

    なるべくシンプルに書く

我们将采取以下解答策略。

以下是其他找到的答案链接:
– https://qiita.com/kroton/items/8622d5e9e38ff822070c
– https://gist.github.com/zyxar/2317744
– https://github.com/golang/tour/tree/master/solutions
– http://takatoshiono.hatenablog.com/entry/2016/09/08/001823
– http://blog.yuuk.io/entry/2014/02/16/183206

练习:循环和函数

作为使用函数和循环的简单练习,让我们尝试使用牛顿法来计算平方根。…问题链接:https://go-tour-jp.appspot.com/flowcontrol/8

只重复10次

请最初将该计算公式循环进行10次,检查对于各个不同的x值(如1、2、3等)结果与正确值的接近程度。

package main

import (
    "fmt"
)

func Sqrt(x float64) float64 {
    z := 1.0
    for i := 0; i < 10; i++ {
        z -= (z*z - x) / (2 * z)
    }
    return z
}

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

如果与前一刻的差距变小就停下来。

请尝试修改循环,以便在循环之前计算出的变量z的值不再改变(或者变化非常小)时停止循环。

package main

import (
    "fmt"
)

func Sqrt(x float64) float64 {
    z := 1.0
    for d := 1.0; d*d > 1e-10; z -= d {
        d = (z*z - x) / (2 * z)
    }
    return z
}

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

Exercise: 切片 (Qiē

我们来实现Pic函数。当我们执行这个程序时,应该会在下方显示生成的图像。这个函数需要实现将一个长度为dy的切片分配给一个长度为dx的切片,其中每个元素是8位无符号整数类型。图像将被解释为灰度图像(实际上是蓝黑图像)…问题详情请参考https://go-tour-jp.appspot.com/moretypes/18。

package main

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

func Pic(dx, dy int) [][]uint8 {
    pic := make([][]uint8, dy)
    for y := range pic {
        pic[y] = make([]uint8, dx)
        for x := range pic[y] {
            pic[y][x] = uint8((x + y) / 2)
        }
    }
    return pic
}

func main() {
    pic.Show(Pic)
}

练习:地图

让我们来实现一个 WordCount 函数吧。该函数需要接收一个字符串 s 作为参数,并返回一个记录每个单词出现次数的映射。wc.Test 函数会执行测试套件,检查传入的函数是否成功,并将结果显示为成功或失败。…
问题链接:https://go-tour-jp.appspot.com/moretypes/23

package main

import (
    "golang.org/x/tour/wc"
    "strings"
)

func WordCount(s string) map[string]int {
    m := make(map[string]int)
    for _, w := range strings.Fields(s) {
        m[w]++
    }
    return m
}

func main() {
    wc.Test(WordCount)
}

练习:斐波那契闭包

让我们实现斐波那契函数。该函数返回一个返回连续斐波那契数(0、1、1、2、3、5、…)的函数(闭包)。
问题链接:https://go-tour-jp.appspot.com/moretypes/26

package main

import "fmt"

// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
    a, b := 1, 0
    return func() int {
        a, b = b, a+b
        return a
    }
}

func main() {
    f := fibonacci()
    for i := 0; i < 10; i++ {
        fmt.Println(f())
    }
}

练习:紧绳

让我们实现一个IPAddr类型,以点分四段的形式输出IP地址。请实现fmt.Stringer接口…
问题:https://go-tour-jp.appspot.com/methods/18

package main

import "fmt"

type IPAddr [4]byte

func (ip IPAddr) String() string {
    return fmt.Sprintf("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[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)
    }
}

Exercise: Mistakes
练习:错误

请从以前的练习中复制Sqrt函数,并修复它以返回错误值。…
问题链接https://go-tour-jp.appspot.com/methods/20

package main

import (
    "fmt"
)

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
    for i := 0; i < 10; i++ {
        z -= (z*z - x) / (2 * z)
    }
    return z, nil
}

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

锻炼:读者们

请实现一个 Reader 类型,用于输出无限流的 ASCII 字符 ‘A’。问题链接:https://go-tour-jp.appspot.com/methods/22

package main

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

type MyReader struct{}

func (r MyReader) Read(b []byte) (int, error) {
    for i := range b {
        b[i] = 'A'
    }
    return len(b), nil
}

func main() {
    reader.Validate(MyReader{})
}

练习:rot13阅读器

请实现一个 rot13Reader,它实现了 io.Reader 接口,并将 ROT13 換字式暗号(置换密码)应用于所有字母进行解密。你可以在此链接找到该问题的详细说明:https://go-tour-jp.appspot.com/methods/23

package main

import (
    "io"
    "os"
    "strings"
)

type rot13Reader struct {
    r io.Reader
}

func (rot13 rot13Reader) Read(b []byte) (int, error) {
    n, err := rot13.r.Read(b)
    for i, v := range b {
        switch {
        case v >= 'A' && v < 'N', v >= 'a' && v < 'n':
            b[i] += 13
        case v >= 'N' && v <= 'Z', v >= 'n' && v <= 'z':
            b[i] -= 13
        }
    }
    return n, err
}

func main() {
    s := strings.NewReader("Lbh penpxrq gur pbqr!")
    r := rot13Reader{s}
    io.Copy(os.Stdout, &r)
}

健身:图像

你还记得之前解决的图像生成器吗?这次,我们尝试返回实现了 image.Image 接口而不是数据切片的图像。…问题链接:https://go-tour-jp.appspot.com/methods/25

package main

import (
    "golang.org/x/tour/pic"
    "image"
    "image/color"
)

type Image struct{}

func (i Image) ColorModel() color.Model {
    return color.RGBAModel
}

func (i Image) Bounds() image.Rectangle {
    return image.Rect(0, 0, 256, 256)
}

func (i Image) At(x, y int) color.Color {
    return color.RGBA{uint8(x), uint8(y), 255, 255}
}

func main() {
    m := Image{}
    pic.ShowImage(m)
}

练习:等价的二叉树

在许多语言中,检查两个二叉树是否保持相同的顺序是相当复杂的。为了描述一种简单的解决方法,我们将利用Go的并发性和通道。…
问题链接:https://go-tour-jp.appspot.com/concurrency/7
继续问题链接:https://go-tour-jp.appspot.com/concurrency/8

package main

import (
    "fmt"
    "golang.org/x/tour/tree"
)

// Walk walks the tree t sending all values
// from the tree to the 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 {
        return
    }
    walk(t.Left, ch)
    ch <- t.Value
    walk(t.Right, ch)
}

// Same determines whether the trees
// t1 and t2 contain the same values.
func Same(t1, t2 *tree.Tree) bool {
    c1, c2 := make(chan int), make(chan int)
    go Walk(t1, c1)
    go Walk(t2, c2)
    for {
        v1, ok1 := <-c1
        v2, ok2 := <-c2
        switch {
        case !ok1, !ok2:
            return ok1 == ok2
        case v1 != v2:
            return false
        }
    }
}

func main() {
    ch := make(chan int)
    go Walk(tree.New(1), ch)
    for i := range ch {
        fmt.Println(i)
    }
    fmt.Println(Same(tree.New(1), tree.New(1)))
    fmt.Println(Same(tree.New(1), tree.New(2)))
}

运动:网络爬虫

在这个练习中,我们将使用Go语言的并发特性来并行化网络爬虫(web crawler)。
问题链接:https://go-tour-jp.appspot.com/concurrency/10

尽管本文中未提及sync.WaitGroup,但基于以下原因,我们使用它。

Mutexを使うのでどうせsyncパッケージはインポートすることになる

channelを使って頑張るよりもシンプルで分かりやすく書ける

WaitGroupのドキュメントのExampleがこの問題と同じような設定

此外,由于这个过程会在一瞬间结束,因此很难确认是否可以同时执行。为了更加清楚明了,我们可以导入time包,并在Fetch方法的第一行加入time.Sleep(1 * time.Second),以便等待1秒钟。如果一切正确,应该在3秒钟内结束。

package main

import (
    "fmt"
    "sync"
)

type Fetcher interface {
    // Fetch returns the body of URL and
    // a slice of URLs found on that page.
    Fetch(url string) (body string, urls []string, err error)
}

// Crawl uses fetcher to recursively crawl
// pages starting with url, to a maximum of depth.
func Crawl(url string, depth int, fetcher Fetcher) {
    cache := struct {
        visited map[string]bool
        sync.Mutex
    }{
        visited: make(map[string]bool),
    }
    var wg sync.WaitGroup
    var crawl func(string, int)
    crawl = func(url string, depth int) {
        if depth <= 0 {
            return
        }
        cache.Lock()
        if cache.visited[url] {
            cache.Unlock()
            return
        }
        cache.visited[url] = true
        cache.Unlock()
        body, urls, err := fetcher.Fetch(url)
        if err != nil {
            fmt.Println(err)
            return
        }
        fmt.Printf("found: %s %q\n", url, body)
        wg.Add(len(urls))
        for _, u := range urls {
            go func(u string) {
                crawl(u, depth-1)
                wg.Done()
            }(u)
        }
    }
    crawl(url, depth)
    wg.Wait()
}

func main() {
    Crawl("https://golang.org/", 4, fetcher)
}

// fakeFetcher is Fetcher that returns canned results.
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 is a populated 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/",
        },
    },
}