【Go】A tour of go 练习题答案

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/TMT123421/article/details/79580599

A tour of go 练习题答案

1、 练习:循环与函数

(1)题目

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

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

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

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

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

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

z := 1.0
z := float64(1)

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

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

(2)答案

​ 循环10次:

package main
import (
     "fmt"
)

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

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

​ 无限接近:

package main

import (
    "fmt"
    "math"
)

func Sqrt(x float64) float64 {
    z := float64(1)
    for {
        res := z - (z*z-x)/(2*z)
        if math.Abs(res-z) < 1e-10 {
            return res
        }
        z = res
    }
    return z
}

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

(3)运行结果

循环10次:

1.4142135623730951

无限接近:

1.4142135623730951

2、 练习:切片

(1) 题目

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

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

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

(2)答案

// 以 x%(y+1) 为例
package main

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

func Pic(dx, dy int) [][]uint8 {
    ret := make([][]uint8, dy)
    for i := 0; i < dy; i++ {
        ret[i] = make([]uint8, dx)
        for j := 0; j < dx; j++ {
            ret[i][j] = uint8(i % (j + 1))
        }
    }
    return ret
}

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

(3)运行结果

res_slice

3、练习:映射

(1)题目

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

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

(2)答案

package main

import (
     "tour/wc"
     "strings"
)

func WordCount(s string) map[string]int {
     ret := make(map[string]int)

     arr := strings.Fields(s)
     for _, val := range arr {
          ret[val]++
     }
     return ret
}

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

(3)运行结果

PASS
 f("I ate a donut. Then I ate another donut.") = 
  map[string]int{"donut.":2, "Then":1, "another":1, "I":2, "ate":2, "a":1}
PASS
 f("A man a plan a canal panama.") = 
  map[string]int{"a":2, "plan":1, "canal":1, "panama.":1, "A":1, "man":1}

Program exited.

4、练习:斐波纳契闭包

(1)题目

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

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

(2)答案

package main

import "fmt"

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

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

(3)运行结果

0
1
1
2
3
5
8
13
21
34

Program exited.

5、练习:Stringer

(1)题目

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

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

(2)答案

package main

import "fmt"

type IPAddr [4]byte

// TODO: Add a "String() string" method to IPAddr.

func (ip IPAddr) String() string {
    return fmt.Sprintf("%v.%v.%v.%v", 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)
    }
}

(3)运行结果

loopback: 127.0.0.1
googleDNS: 8.8.8.8

Program exited.

6、练习:错误

(1)题目

​ 从之前的练习中复制 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 值。

(2)答案

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)
    }
    return math.Sqrt(x), nil
}

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

(3)运行结果

1.4142135623730951 <nil>
0 cannot Sqrt negative number: -2

Program exited.

7、练习:Reader

(1)题目

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

(2)答案

package main

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

type MyReader struct{}

// TODO: Add a Read([]byte) (int, error) method to MyReader.
func (r MyReader) Read(b []byte) (int, error) {
    // 赋值并返回
    b[0] = 'A'
    return 1, nil
}

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

(3)运行结果

OK!

Program exited.

8、练习:rot13Reader

(1)题目

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

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

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

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

(2)答案

package main

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

type rot13Reader struct {
    r io.Reader
}

func rot13(b byte) byte {
    switch {
    case 'A' <= b && b <= 'M':
        b = b + 13
    case 'M' < b && b <= 'Z':
        b = b - 13
    case 'a' <= b && b <= 'm':
        b = b + 13
    case 'm' < b && b <= 'z':
        b = b - 13
    }
    return b
}

func (mr rot13Reader) Read(b []byte) (int, error) {
    n, e := mr.r.Read(b)
    for i := 0; i < n; i++ {
        b[i] = rot13(b[i])
    }
    return n, e
}

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

(3)运行结果

You cracked the code!

Program exited.

9、练习:图像

(1)题目

​ 还记得之前编写的图片生成器吗?我们再来编写另外一个,不过这次它将会返回一个 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}

(2)答案

package main

import (
    "image"
    "image/color"

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

type Image struct {
    W int
    H int
}

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

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

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

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

(3)运行结果

image_exercise

10、练习:等价二叉查找树

(1)题目

​ 不同二叉树的叶节点上可以保存相同的值序列。例如,以下两个二叉树都保存了序列 1,1,2,3,5,8,13

2_tree

​ 在大多数语言中,检查两个二叉树是否保存了相同序列的函数都相当复杂。 我们将使用 Go 的并发和信道来编写一个简单的解法。

​ 本例使用了 tree 包,它定义了类型:

type Tree struct {
    Left  *Tree
    Value int
    Right *Tree
}

1. 实现 Walk 函数。

2. 测试 Walk 函数。

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

​ 创建一个新的信道 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 的文档可在这里找到。

(2)答案

package main

import (
    "fmt"

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

// Walk 步进 tree t 将所有的值从 tree 发送到 channel 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 检测树 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 := 0; i < 10; i++ {
        x, y := <-ch1, <-ch2
        fmt.Println(x, y)
        if x != y {
            return false
        }
    }
    return true
}

func main() {
    // fmt.Println(Same(tree.New(1), tree.New(2)))
    fmt.Println(Same(tree.New(1), tree.New(1)))
}

(3)运行结果

1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
10 10
true

Program exited.

11、练习:Web 爬虫

(1)题目

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

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

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

(2)答案

package main

import (
    "fmt"
    "sync"
)

type Fetcher interface {
    // Fetch 返回 URL 的 body 内容,并且将在这个页面上找到的 URL 放到一个 slice 中。
    Fetch(url string) (body string, urls []string, err error)
}

// Crawl 使用 fetcher 从某个 URL 开始递归的爬取页面,直到达到最大深度。
func Crawl(url string, depth int, fetcher Fetcher, crawled Crawled, out chan string, end chan bool) {
    if depth <= 0 {
        end <- true
        return
    }

    crawled.mux.Lock()
    if _, ok := crawled.crawled[url]; ok {
        crawled.mux.Unlock()
        end <- true
        return
    }

    crawled.crawled[url] = 1
    crawled.mux.Unlock()

    _, urls, err := fetcher.Fetch(url)
    if err != nil {
        fmt.Println(err)
        end <- true
        return
    }

    out <- url
    //fmt.Println("found: ", url, body)
    for _, u := range urls {
        go Crawl(u, depth-1, fetcher, crawled, out, end)
    }

    for i := 0; i < len(urls); i++ {
        <-end
    }

    end <- true
    return
}

type Crawled struct {
    crawled map[string]int
    mux     sync.Mutex
}

func main() {
    crawled := Crawled{make(map[string]int), sync.Mutex{}}
    out := make(chan string)
    end := make(chan bool)
    go Crawl("http://golang.org/", 4, fetcher, crawled, out, end)

    for {
        select {
        case url := <-out:
            fmt.Println("found: ", url)
        case <-end:
            return
        }
    }
}

// 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{
    "http://golang.org/": &fakeResult{
        "The Go Programming Language",
        []string{
            "http://golang.org/pkg/",
            "http://golang.org/cmd/",
        },
    },
    "http://golang.org/pkg/": &fakeResult{
        "Packages",
        []string{
            "http://golang.org/",
            "http://golang.org/cmd/",
            "http://golang.org/pkg/fmt/",
            "http://golang.org/pkg/os/",
        },
    },
    "http://golang.org/pkg/fmt/": &fakeResult{
        "Package fmt",
        []string{
            "http://golang.org/",
            "http://golang.org/pkg/",
        },
    },
    "http://golang.org/pkg/os/": &fakeResult{
        "Package os",
        []string{
            "http://golang.org/",
            "http://golang.org/pkg/",
        },
    },
}

(3)运行结果

not found: http://golang.org/cmd/
found:  http://golang.org/
found:  http://golang.org/pkg/
found:  http://golang.org/pkg/os/

Program exited.

—— 2018-08-05 ——

猜你喜欢

转载自blog.csdn.net/TMT123421/article/details/79580599
go