【A tour of go】练习题
练习:循环与函数
(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 接近吗?
(2)代码
循环10次:
package main import ( "fmt" "math" ) func Sqrt(x float64) float64 { z := 1.0 for i:= 0;i < 10;i++{ z -= (z*z-x)/(2*z) fmt.Printf("i = %d, z = %f\n", i, z) } return z } func main() { fmt.Println(Sqrt(2)) fmt.Println(math.Sqrt(2)) }
无限逼近:
package main import ( "fmt" "math" ) func Sqrt(x float64) float64 { z := 1.0 i := 0 for math.Abs( z*z-x) > 1e-10 { z -= (z*z-x)/(2*z) fmt.Printf("i = %d, z = %f\n", i, z) i += 1 } return z } func main() { fmt.Println(Sqrt(2)) fmt.Println(math.Sqrt(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)代码
package main import "golang.org/x/tour/pic" func Pic(dx, dy int) [][]uint8 { result := make([][]uint8,dy) for i := 0; i < dy; i++{ result[i] = make([]uint8,dx) for j := 0; j < dx; j++{ //更改此句,可得到不同的结果 result[i][j] = uint8(i*j) } } return result } func main() { pic.Show(Pic) }
(3)结果
练习:映射
(1)题目
实现 WordCount
。它应当返回一个映射,其中包含字符串 s
中每个“单词”的个数。函数 wc.Test
会对此函数执行一系列测试用例,并输出成功还是失败。
你会发现 strings.Fields 很有帮助。
(2)代码
package main import ( "golang.org/x/tour/wc" "strings" ) func WordCount(s string) map[string]int { result := strings.Fields(s) m := make(map[string]int) for _, word := range result{ m[word]++ } return m } func main() { wc.Test(WordCount) }
(3)结果
PASS f("I am learning Go!") = map[string]int{"Go!":1, "I":1, "am":1, "learning":1} PASS f("The quick brown fox jumped over the lazy dog.") = map[string]int{"The":1, "brown":1, "dog.":1, "fox":1, "jumped":1, "lazy":1, "over":1, "quick":1, "the":1} PASS f("I ate a donut. Then I ate another donut.") = map[string]int{"I":2, "Then":1, "a":1, "another":1, "ate":2, "donut.":2} PASS f("A man a plan a canal panama.") = map[string]int{"A":1, "a":2, "canal":1, "man":1, "panama.":1, "plan":1}
练习:斐波那契闭包
(1)题目
让我们用函数做些好玩的事情。
实现一个 fibonacci
函数,它返回一个函数(闭包),该闭包返回一个斐波纳契数列 `(0, 1, 1, 2, 3, 5, ...)`。
(2)代码
package main import "fmt" // 返回一个“返回int的函数” func fibonacci() func() int { sum := 0 res1 := 0 res2 := 1 i := 0 return func() int{ if i == 0{ sum = res1 }else if i == 1{ sum = res2 }else{ sum = res1 + res2 res1 = res2 res2 = sum } i += 1 return sum } } func main() { f := fibonacci() for i := 0; i < 10; i++ { fmt.Println(f()) } }
练习:Stringer
(1)题目
通过让 IPAddr
类型实现 fmt.Stringer
来打印点号分隔的地址。
例如,IPAddr{1, 2, 3, 4}
应当打印为 "1.2.3.4"
。
(2)代码
package main import "fmt" type IPAddr [4]byte // TODO: 给 IPAddr 添加一个 "String() string" 方法 func (addr IPAddr) String() string{ return fmt.Sprintf("%v.%v.%v.%v\n",addr[0],addr[1],addr[2],addr[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) } }
练习:错误
(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" ) func Sqrt(x float64) (float64, error) { if(x < 0){ return x, ErrNegativeSqrt(x) } return math.Sqrt(x), nil } func main() { fmt.Println(Sqrt(2)) fmt.Println(Sqrt(-2)) } type ErrNegativeSqrt float64 func (num ErrNegativeSqrt) Error() string{ return fmt.Sprintf("cannot Sqrt negative number : %f\n", num) }
练习:Reader
(1)题目
实现一个 Reader
类型,它产生一个 ASCII 字符 'A'
的无限流。
(2)代码
package main import "golang.org/x/tour/reader" type MyReader struct{} // TODO: 给 MyReader 添加一个 Read([]byte) (int, error) 方法 func (read MyReader) Read(b []byte) (int, error) { b[0] = 'A' return 1, nil } func main() { reader.Validate(MyReader{}) }
(3)结果
OK! Program exited.
练习: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{ if (b >= 'A' && b <= 'M') || (b >= 'a' && b <= 'm'){ b += 13 }else if (b >= 'N' && b <= 'Z') || (b >= 'n' && b <= 'z'){ b -= 13 } return b } func (reader rot13Reader) Read(b []byte)(int, error){ n, e := reader.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.
练习:图像
(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 ( "golang.org/x/tour/pic" "image" "image/color" ) type Image struct{ width int length int } func (i Image) ColorModel() color.Model{ return color.RGBAModel } func (i Image) Bounds() image.Rectangle{ return image.Rect(0,0,i.width,i.length) } func (i Image) At(x, y int) color.Color{ return color.RGBA{uint8(x), uint8(y), 255, 255} } func main() { m := Image{150,150} pic.ShowImage(m) }
(3)结果
练习:等价二叉查找树
(1)题目
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
的文档可在这里找到。
(2)代码
package main import( "golang.org/x/tour/tree" "fmt" ) // 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) } 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 if x != y{ return false } } return true } func main(){ fmt.Println(Same(tree.New(1), tree.New(3))) }
练习:Web爬虫
(1)题目
在这个练习中,我们将会使用 Go 的并发特性来并行化一个 Web 爬虫。
修改 Crawl
函数来并行地抓取 URL,并且保证不重复。
提示:你可以用一个 map 来缓存已经获取的 URL,但是要注意 map 本身并不是并发安全的!
(2)代码
package main import ( "fmt" "sync" ) type Cache struct{ cache map[string]bool mutex sync.Mutex } var cache Cache = Cache{cache:make(map[string]bool),} func (cache Cache) add(url string){ cache.mutex.Lock() defer cache.mutex.Unlock() cache.cache[url] = true } func (cache Cache) isExist(url string) bool{ cache.mutex.Lock() defer cache.mutex.Unlock() _, ok := cache.cache[url] if !ok { cache.cache[url] = true } return ok } 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, end chan bool) { if depth <= 0 { end <- true return } if cache.isExist(url){ //fmt.Println("Already Exist") end <- true return } cache.add(url) body, urls, err := fetcher.Fetch(url) if err != nil { fmt.Println(err) end <- true return } fmt.Printf("found: %s %q\n", url, body) subEnd := make(chan bool) for _, u := range urls { go Crawl(u, depth-1, fetcher, subEnd) } for i := 0; i < len(urls); i++{ <- subEnd } end <- true } func main() { end := make(chan bool) go Crawl("https://golang.org/", 4, fetcher, end) for{ if <- end{ return } } 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{ "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/", }, }, }
(3)结果
found: https://golang.org/ "The Go Programming Language" not found: https://golang.org/cmd/ found: https://golang.org/pkg/ "Packages" found: https://golang.org/pkg/os/ "Package os" found: https://golang.org/pkg/fmt/ "Package fmt"