<>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)/2 、x*y 、 x^y 、 x*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)运行结果
<>3、练习:映射
<>(1)题目
实现 WordCount 。它应当返回一个映射,其中包含每个字符串 s 中“单词”的个数。函数 wc.Test
会对此函数执行一系列测试用例,并输出成功还是失败。
你会发现 strings.Fields <https://go-zh.org/pkg/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 函数,它返回一个函数(闭包), 该闭包返回一个斐波纳契数列
<https://zh.wikipedia.org/wiki/%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97>
(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)题目
从之前的练习 <https://tour.go-zh.org/flowcontrol/8>中复制 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 <https://go-zh.org/pkg/io/#Reader> 包装另一个 io.Reader
,然后通过某种方式修改其数据流。
例如,gzip.NewReader <https://go-zh.org/pkg/compress/gzip/#NewReader> 函数接受一个
io.Reader (已压缩的数据流)并返回一个同样实现了 io.Reader 的 *gzip.Reader (解压后的数据流)。
编写一个实现了 io.Reader 并从另一个 io.Reader 中读取数据的 rot13Reader , 通过应用 rot13
<http://en.wikipedia.org/wiki/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 类型,实现必要的方法 <https://go-zh.org/pkg/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
Imagestruct { 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)运行结果
<>10、练习:等价二叉查找树
<>(1)题目
不同二叉树的叶节点上可以保存相同的值序列。例如,以下两个二叉树都保存了序列 1,1,2,3,5,8,13 。
在大多数语言中,检查两个二叉树是否保存了相同序列的函数都相当复杂。 我们将使用 Go 的并发和信道来编写一个简单的解法。
本例使用了 tree 包,它定义了类型:
type Tree struct { Left *Tree Value int Right *Tree }
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 函数来检测 t1 和 t2 是否存储了相同的值。
4. 测试 Same 函数。
Same(tree.New(1), tree.New(1)) 应当返回 true ,而 Same(tree.New(1), tree.New(2))
应当返回false 。
Tree 的文档可在这里 <https://godoc.org/golang.org/x/tour/tree#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 string)
(body string, urls []string, err error) } func Crawl(url string, depth int,
fetcher Fetcher, wg *sync.WaitGroup) { defer wg.Done() if depth <= 0 { return }
if cache.has(url) { // fmt.Println("already exist.") 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 { wg.Add(1) go Crawl(u,
depth-1, fetcher, wg) } return } type Cache struct { cache map[string]bool
mutex sync.Mutex } func (cache *Cache) add(url string) { cache.mutex.Lock()
cache.cache[url] = true cache.mutex.Unlock() } func (cache *Cache) has(url
string) bool { cache.mutex.Lock() defer cache.mutex.Unlock() _, ok := cache.
cache[url] if !ok { cache.cache[url] = true } return ok } var cache Cache =
Cache{ cache: make(map[string]bool), } func main() { var wg sync.WaitGroup wg.
Add(1) go Crawl("https://golang.org/", 4, fetcher, &wg) wg.Wait() } type
fakeFetchermap[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) } 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/os1/", }, }, "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/", }, }, }
<>(3)运行结果
found: https://golang.org/ "The Go Programming Language" not found: https://
golang.org/cmd/ found: https://golang.org/pkg/ "Packages" not found: https://
golang.org/pkg/os1/ found: https://golang.org/pkg/fmt/ "Package fmt" found:
https://golang.org/pkg/os/ "Package os" Program exited.
—— 2018-08-05 ——
热门工具 换一换