这两天学完了 A Tour of Go 官方的语法教学,里面有很多的 Excercise(训练题)。希望对大家有用,如果有其他人也写过,并觉得我写的不对的,求教!❤️
Exercise: Loops and Functions
题目
- 给一个 number
x
,我们通过 loop 和 function 来找到其平方根z
,即z² = x
- tour.golang.org/flowcontrol…
解答
package main
import (
"fmt"
"math"
)
func Sqrt(x float64) float64 {
z := x/2
for i:= 0; math.Abs(z*z - x) > 0.0000000001; i++ {
z -= (z*z - x) / (2*z)
fmt.Println(i, "z:", z, "z^2 -x:", z*z - x)
}
return z
}
func main() {
fmt.Println(Sqrt(1000))
}
复制代码
z := x/2
这个是猜测的初始值(也可以像是题目里的 hint 写的设置成 1)math.Abs(z*z - x) > 0.0000000001
用最优解的逻辑就是给了一个 tolerance0.0000000001
,即我们用计算公式算出来的z²
与x
的差值已经足够小,我们认定预估的z
算是一个近似准确值。
Exercise: Slices
题目
- 写一个
Pic
函数来生成一个[][]uint8
的 2D 图片(即可说是 Array of Array)。它的大小由参数(dx, dy int)
决定,这个有dy
个数组,每个数组里又有一个长度为dx
的数组。而相关的位置上pic[y][x]
是这个图片的 bluescale(只有蓝色)数值,格式为uint8
。 - tour.golang.org/moretypes/1…
解答
package main
import "golang.org/x/tour/pic"
func Pic(dx, dy int) [][]uint8 {
pic := make([][]uint8, dy)
for i := range pic {
pic[i] = make([]uint8, dx)
for j := range pic[i] {
pic[i][j] = uint8(i*j + j*j)
}
}
return pic
}
func main() {
pic.Show(Pic)
}
复制代码
pic := make([][]uint8, dy)
先建一个数组,长度是dy
,数组里每个元素的内容是一个数组[]uint8
pic[i] = make([]uint8, dx)
在数组里的第i
个元素里,我们再创造一个[]uint8
数组,长度为dx
pic[i][j] = uint8(i*j + j*j)
表示我们设计的 bluesacle 计算公式里,pic[i][j]
位置的数值是uint8(i*j + j*j)
(这里你可以随意改几个,能看到很多不同的效果哦!)
Exercise: Maps
题目
- 实现一个函数
WordCount
,它可以回复一个map
里面包含输入字符串中出现的单词 word 及相应出现的次数。 - 例如:"I love you you you you",返回
map[string]int{"I":1, "love":1, "you":3}
- tour.golang.org/moretypes/2…
解答
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] = m[word] + 1
}
return m
}
func main() {
wc.Test(WordCount)
}
复制代码
strings.Files(s)
这个函数会自动切分一个字符串到一个数组,每个数组里是一个 word- 建立
map
然后在数组里每当某一个word
出现,就相应的增加 1
Exercise: Fibonacci closure
题目
- 实现一个
fibonacci
函数,使其返回一个函数 - 这个函数会连续的输出斐波那契数列
- tour.golang.org/moretypes/2…
解答
package main
import "fmt"
// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
a, b := 0, 1
return func() int {
c := a
a, b = b, a+b
return c
}
}
func main() {
f := fibonacci()
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}
复制代码
- closure 也叫闭包,意思是一个函数中需要使用的某一个变量是在此函数外定义的
- 在上面的
func fibonacci() func() int
中,返回的是一个函数func() int
,而这个函数每次运行返回的是一个int
- 在
fibonacci()
中,变量a
和b
定义在函数fibonacci()
里,并被此函数返回的return func() int { ... }
函数引用到,也就是说在返回的函数里a
和b
两个变量一直存储在内存中,且数值会一直变化 f := fibonacci()
中f
是fibonacci()
返回的函数,在初始情况中,此时的a, b := 0, 1
- 以第一次
f()
调用为例:c := a
:c
赋值为a
即 0a, b = b, a+b
:a
赋值为b
即 1,b
赋值为a+b
即 1return c
,返回 0,也就是斐波那契数列的第一个值
- 第二次
f()
调用,注意此时a
是 1,b
是 1:c := a
:c
赋值为a
即 1a, b = b, a+b
:a
赋值为b
即 1,b
赋值为a+b
即 2return c
,返回 1,也就是斐波那契数列的第二个值
- 以此类推,循环中
f
函数被调用了 10 次,输出了斐波那契数列前 10 个值
Exercise: Stringers
题目
- 为
type IPAddr [4]byte
增加 Stringer Interface 函数来输出字符串,即IPAddr{1, 2, 3, 4}
print 为1.2.3.4
- tour.golang.org/methods/18
解答
package main
import (
"fmt"
"strings"
"strconv"
)
type IPAddr [4]byte
// TODO: Add a "String() string" method to IPAddr.
func (ip IPAddr) String() string {
s := make([]string, len(ip))
for i, val := range ip {
s[i] = strconv.Itoa(int(val))
}
return fmt.Sprintf(strings.Join(s, "."))
}
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)
}
}
复制代码
- 引入
strconv.Itoa
,int to string IPAddr
是一个大小为 4 的[]byte
,我们生成一个[4]string
数组s
,每一个元素是 IP 地址中的一位,并且我们用strconv.Itoa(int(val))
把它转换为字符串strings.Join(s, ".")
将字符串数组用"."
连起来- 这样在使用
fmt.Printf("%v: %v\n", name, ip)
时,会对type IPAddr
默认调用其Stringer interface
下定义的String()
函数来输出
Exercise: Errors
题目
- 优化 Exercise: Loops and Functions 里写的
sqrt
函数,当参数是一个负数时,添加type ErrNegativeSqrt float64
,并通过定义func (e ErrNegativeSqrt) Error() string
从而使其为error
- tour.golang.org/methods/20
解答
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) {
z:= x/2
for i:= 0; math.Abs(z*z - x) > 0.0000000001; i++ {
z -= (z*z - x) / (2*z)
fmt.Println(i, "z:", z, "z^2 -x:", z*z - x)
}
return z, nil
} else {
return 0, ErrNegativeSqrt(x)
}
}
复制代码
error
类型是一个 built-in interface,需要定义Error() string
函数,测试一个error
type 是否是nil
是定义函数返回是否出错的方法。例如:i, err := strconv.Atoi("42")
返回值中i
代表函数返回数值,而err
如果不是nil
的话,则表示有错误发生func (e ErrNegativeSqrt) Error() string
定义了ErrNegativeSqrt
属于error
的Error()
函数,也就简洁说明ErrNegativeSqrt
是一个error
func Sqrt(x float64) (float64, error)
函数返回两个数值,前者为参数的平方根,后者为error
,当后者不是nil
的时候,在Println
中会自动调用Error()
输出相应的错误信息字符串。
Exercise: Readers
题目
- 实现一个
Reader
type,以输出一个无限个'A'
的字符流 - tour.golang.org/methods/22
解答
package main
import (
"fmt"
"golang.org/x/tour/reader"
)
type MyReader struct{}
type ErrEmptyBuffer []byte
func (b ErrEmptyBuffer) Error() string {
return fmt.Sprintf("cannot read an empty buffer: %v", b)
}
// TODO: Add a Read([]byte) (int, error) method to MyReader.
func (reader MyReader) Read(b []byte) (int, error) {
bLength := len(b)
if (bLength == 0) {
return 0, ErrEmptyBuffer(b)
}
for i := range b {
b[i] = 'A'
}
return bLength, nil
}
func main() {
reader.Validate(MyReader{})
}
复制代码
- 因为
MyReader
会输出无限个'A'
,因此只要输入参数b []byte
不是一个空的 Buffer,就会写满 - 当
bLength == 0
也就是 Bufferb
是空时,返回ErrEmptyBuffer(b)
错误 - 只要不是空,就都填满
'A'
并返回bLength, nil
Exercise: rot13Reader
题目
- 实现一个
rot13Reader
type 使其包含一个io.Reader
使得其在执行Read
函数时,会自动根据rot13
来转化相应的字母字符。 - tour.golang.org/methods/23
解答
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) (n int, err 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)
}
复制代码
- 先根据
rot13
规则实现函数func rot13(c byte) byte
,也就是A-M
与N-Z
的兑换,以及a-m
与n-z
的兑换 - 定义
rot13Reader
的Read
函数,首先使用期包含的r
Reader 来读取数据,然后每一个数据都通过rot13
转换,最终返回相应的数字结果
Exercise: Images
题目
- 优化 Exercise: Slices 里实现的图片函数,这次实现一个
image.Image
图像而不仅仅是一个二维数组数据 - 定义一个
Image
type,并定义相应的image.Image
interface,其中ColorModel
使用color.RGBAModel
Bounds
使用image.Rectangle
type,并用image.Rect(0, 0, w, h)
定义At
会返回具体图片像素点上的颜色,最终用color.RGBA{v, v, 255, 255}
定义
- tour.golang.org/methods/25
解答
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)
}
复制代码
- 因为要用到
image.Rect
,color.RGBAModel
,color.RGBA
因此,我们引入"image"
和"image/color"
packages - 定义相关函数,即可得到结果,这里
At
函数我沿用了之前的图片颜色计算公式v := uint8(x*y + y*y)
Exercise: Equivalent Binary Trees
题目
- 同一组二叉树数列可能会存储在不同样子的二叉树中,如上图两个二叉树都存储者数列:1, 1, 2, 3, 5, 8, 13。定义
Tree
类型,并实现如下函数来测试两个Tree
是否存储同样的数列。- 实现
Tree
的Walk
method,使其会按照树内存储数列顺序逐一走完相关数字,方式是逐一传入ch chan int
中,也就是func Walk(t *tree.Tree, ch chan int)
,而这个过程放入 goroutine 中完成go Walk(tree.New(1), ch)
。注意:tree.New(k)
会随机生成一个结构不一的树,但都会存储相同的数列k
,2k
, ...,10k
。 - 实现
Same
函数并调用Walk
方法,使其可以比较两个Tree
是否存储相同的数列。例如:Same(tree.New(1), tree.New(1))
应该返回true
,因为里面都存着1
,2
, ...10
。而Same(tree.New(1), tree.New(2))
应该返回false
- 实现
解答
package main
import (
"fmt"
"golang.org/x/tour/tree"
)
// type Tree struct {
// Left *Tree
// Value int
// Right *Tree
// }
// Walk walks the tree t sending all values
// from the tree to the channel ch.
func Walk(t *tree.Tree, ch chan int) {
if t.Left != nil {
Walk(t.Left, ch)
}
ch <- t.Value
if t.Right != nil {
Walk(t.Right, ch)
}
}
// Same determines whether the trees
// t1 and t2 contain the same values.
func Same(t1, t2 *tree.Tree) bool {
var v1, v2 int
c1 := make(chan int)
c2 := make(chan int)
go Walk(t1, c1)
go Walk(t2, c2)
for i := 0; i < 10; i++ {
v1 = <-c1
v2 = <-c2
if v1 != v2 {
return false
}
}
return true
}
func main() {
ch := make(chan int)
go Walk(tree.New(10), ch)
for i := 0; i < 10; i++ {
fmt.Println(<-ch)
}
fmt.Println(Same(tree.New(1), tree.New(1)))
}
复制代码
Walk
方法中,我们按照t.Left
,ch <- t.Value
,t.Right
顺序逐一将数据传入ch
Same
方法中,因为我们知道tree.New(k)
生成的树包含 10 个节点,因此我们生成两个 Channels 分别存储两个Tree
在Walk
时的数据,然后一个循环 10 次的 Loop 每次从里面分别拿出一个数值,比较相应数列下的数值是否相同,如果出现不同,则表述两个树不相同。
Exercise: Web Crawler
竟然能看到这里,那有必要加个微信了 ymkalasoo,我们在找优秀的 Go 开发者
题目
- 实现一个可以并行的 Web Crawler(网页爬虫),实现
Crawl
函数使其可以并行抓去URL
但不重复抓取 fakeFetcher
是一个假的数据集,表示可以用来被测试的爬虫抓取数据- tour.golang.org/concurrency…
解答
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)
}
type UrlChecker struct {
urls map[string]bool
mux sync.Mutex
}
func (c *UrlChecker) Crawled(url string) bool {
c.mux.Lock()
if c.urls[url] {
defer c.mux.Unlock()
return true
}
c.urls[url] = true
defer c.mux.Unlock()
return false
}
var uc = UrlChecker{urls: make(map[string]bool)}
// Crawl uses fetcher to recursively crawl
// pages starting with url, to a maximum of depth.
func Crawl(url string, depth int, fetcher Fetcher, ret chan string) {
// TODO: Fetch URLs in parallel.
// TODO: Don't fetch the same URL twice.
// This implementation doesn't do either:
defer close(ret)
if depth <= 0 {
return
}
if uc.Crawled(url) {
return
}
body, urls, err := fetcher.Fetch(url)
if err != nil {
fmt.Println(err)
return
}
ret <- fmt.Sprintf("found: %s %q\n", url, body)
results := make([]chan string, len(urls))
for i, u := range urls {
results[i] = make(chan string)
go Crawl(u, depth-1, fetcher, results[i])
}
for _, result := range results {
for s := range result {
ret <- s
}
}
return
}
func main() {
result := make(chan string)
go Crawl("https://golang.org/", 4, fetcher, result)
for s := range result {
fmt.Println(s)
}
}
// 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/",
},
},
}
复制代码
UrlChecker
是用来测试一个url
是否已经被fetch
过,其中包含一个urls map[string]bool
存储 URL 抓取情况,另mux sync.Mutex
来防止数据被重复修改,进而不会出现,fetcher 在 并行的时候,因为此时某一个 URL 都没有在UrlChecker
被标注 fetch 过,进而同时 fetch。func (c *UrlChecker) Crawled(url string) bool
来存储一个 URL 已经被抓取的状态。在测试过程中c.mux.Lock()
使得,此段数据被阻断其他 goroutine 修改。defer c.mux.Unlock()
意思是在return
后再执行解开 Mutual Exclusion 锁。- 声明
var uc = UrlChecker{urls: make(map[string]bool)}
,当uc.Crawled(url)
是true
的时候,不再抓取相应 url。 func Crawl(url string, depth int, fetcher Fetcher, ret chan string)
最后一个参数ret chan string
传入一个 channel 来存储抓取结果results := make([]chan string, len(urls))
,每一个 url 下的需要更深一层抓取的 urls 生成相应的多个 channels,并逐一抓取go Crawl(u, depth-1, fetcher, results[i])
循环抓取 urls-
复制代码
for _, result := range results {
for s := range result {
ret <- s
}
}
```
每一个抓取到的数据从相应的 channel s
传入最上层的 channel ret
,完成所有的抓取
最终的输出结果:
not found: https://golang.org/cmd/
found: https://golang.org/ "The Go Programming Language"
found: https://golang.org/pkg/ "Packages"
found: https://golang.org/pkg/fmt/ "Package fmt"
found: https://golang.org/pkg/os/ "Package os"
复制代码
辛苦啦,我也在学 Go 哦!
有疑问加站长微信联系(非本文作者)