Tour地址:https://tour.go-zh.org/welcome/1

练习1:循环与函数(基础 - 流程控制语句)

用牛顿法实现平方根函数。什么是牛顿法,题目中已给出:

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

x为函数的变量,z的初始值为一个猜测值。z² − x 是 z² 到它所要到达的值(即 x)的距离, 除以的 2z 为 z² 的导数,我们通过 z² 的变化速度来改变 z 的调整量。 这种通用方法叫做牛顿法。 它对很多函数,特别是平方根而言非常有效。

计算机通常使用循环来计算 x 的平方根。从某个猜测的值 z 开始,我们可以根据 z² 与 x 的近似度来调整 z,产生一个更好的猜测:重复调整的过程,猜测的结果会越来越精确,得到的答案也会尽可能接近实际的平方根。

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

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

z := 1.0
z := float64(1)

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

package main

import (
	"fmt"
	"math"
)

func Sqrt(x float64) float64 {
	z := 1.0
	for {
		pz := z
		z -= (z*z - x) / (2 * z)
		if pz == z || math.Abs(pz-z) <= 1e-10 {    // 如果相邻的两次循环,结果绝对值小于1*10的负5次方,终止循环
			return z
		} else {
			fmt.Println(z)
		}
	}
}

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

练习2:切片(基础 - 更多类型)

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

图像的选择由你来定。几个有趣的函数包括 (x+y)/2, xy, x^y, xlog(y) 和 x%(y+1)。

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

package main

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

func Pic(dx int, dy int) [][]uint8 {
	res := make([][]uint8, dy)
	for y := range res {
		res[y] = make([]uint8, dx)
		for x := range res[y] {
			res[y][x] = uint8(x % (y + 1))
		}
	}
	return res
}

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

最终运行结果:

练习3: 映射(基础 - 更多类型)

/**
 * 题目:
 * 实现 WordCount。它应当返回一个映射,其中包含字符串 s 中每个“单词”的个数。
 * 函数 wc.Test 会对此函数执行一系列测试用例,并输出成功还是失败。
 * 你会发现 strings.Fields 很有帮助。
 */

package main

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

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

	for _, word := range strings.Fields(s) {
		res[word]++
	}

	return res
}

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

这一题没啥说的

练习4: 斐波纳契闭包(基础 - 更多类型)

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

package main

import "fmt"

// 返回一个“返回int的函数”
func fibonacci() func() int {
	i, j := 0, 1
	return func() int {
		res := i
		i, j = j, i+j
		return res
	}
}

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

斐波那契数列:后一项是前两项的和。
闭包:Go 函数可以是一个闭包。闭包是一个函数值,它引用了其函数体之外的变量。该函数可以访问并赋予其引用的变量的值,换句话说,该函数被这些变量“绑定”在一起。
针对这一题:f是个函数值,绑定了fibonacci()中的i、j两个变量,被绑定的变量成了类似php中的静态变量,值不会被重置。

练习5: Stringer(方法和接口)

/**
 * 通过让 IPAddr 类型实现 fmt.Stringer 来打印点号分隔的地址。
 * 例如,IPAddr{1, 2, 3, 4} 应当打印为 "1.2.3.4"。
 */

package main

import (
	"fmt"
)

type IPAddr [4]byte

// TODO: 给 IPAddr 添加一个 "String() string" 方法
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)
	}
}

fmt.Printf会自动调用String()方法

练习6: 错误(方法和接口)

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

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) {
	err := ErrNegativeSqrt(x)
	if x < 0 {
		return 0, err
	}

	z := 1.0
	for {
		pz := z
		z -= (z*z - x) / (2 * z)
		if pz == z || math.Abs(pz-z) <= 1e-10 {
			return z, nil
		}
	}
}

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

同样的,当一个类实现了Error方法,也会在需要的地方自动输出Error的返回值

练习7: Reader(方法和接口)

/**
 *自带的Read功能: 用数据填充给定的字节切片并返回填充的字节数和错误值。在遇到数据流的结尾时,它会返回一个 io.EOF 错误。
 * 实现一个 Reader 类型,它产生一个 ASCII 字符 'A' 的无限流。
 */

package main

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

type MyReader struct{}

func (mr MyReader) Read(a []byte) (int, error) {
	a[0] = 'A'
	return 1, nil
}

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

练习8: rot13Reader (方法和接口)

/**
 * 有种常见的模式是一个 io.Reader 包装另一个 io.Reader,然后通过某种方式修改其数据流。
 * 例如,gzip.NewReader 函数接受一个 io.Reader(已压缩的数据流)并返回一个同样实现了 io.Reader 的 *gzip.Reader(解压后的数据流)。
 * 编写一个实现了 io.Reader 并从另一个 io.Reader 中读取数据的 rot13Reader,通过应用 rot13 代换密码对数据流进行修改。
 * rot13Reader 类型已经提供。实现 Read 方法以满足 io.Reader。
 */

package main

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

type rot13Reader struct {
	r io.Reader
}

func (rot rot13Reader) Read(a []byte) (int, error) {
	count := 0
	for {
		n, err := rot.r.Read(a)
		if err == io.EOF {
			return count, err
		}
		rot13(a)
		count += n
	}
}

func rot13(s []byte) {
	for index, value := range s {
		if value >= 65 && value <= 90 {
			value += 13
			if value > 90 {
				value -= 26
			}
			s[index] = value
		} else if value >= 97 && value <= 122 {
			value += 13
			if value > 122 {
				value -= 26
			}
			s[index] = value
		}
	}
}

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

练习9: 图像(方法和接口)

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

package main

import (
	"image"
	"image/color"

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

type Image struct{}

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

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

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)
}

练习10: 等价二叉查找树(并发)

/**
 * 练习:等价二叉查找树
 * 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。
 */

package main

import (
	"fmt"

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

// Walk 步进 tree t 将所有的值从 tree 发送到 channel ch。
func Walk(t *tree.Tree, ch chan int) {
	slice := []*tree.Tree{}
	for len(slice) > 0 || t != nil {
		if t != nil {
			slice = append(slice, t)
			t = t.Left
		} else {
			popNode := slice[len(slice)-1]
			ch <- popNode.Value
			slice = slice[:len(slice)-1]
			t = popNode.Right
		}
	}
	close(ch)
}

// Same 检测树 t1 和 t2 是否含有相同的值。
func Same(t1, t2 *tree.Tree) bool {
	ch1, ch2 := make(chan int), make(chan int)
	go Walk(t1, ch1)
	go Walk(t2, ch2)
	for {
		chv1, ok1 := <-ch1
		chv2, ok2 := <-ch2
		if ok1 && ok2 {
			if chv1 != chv2 {
				return false
			}
			continue
		} else if !ok1 && !ok2 {
			return true
		} else {
			return false
		}
	}

}

func main() {
	fmt.Println(Same(tree.New(5), tree.New(6)))
}

练习11: Web爬虫(并发)

/**
 * 练习:Web 爬虫
 * 在这个练习中,我们将会使用 Go 的并发特性来并行化一个 Web 爬虫。
 * 修改 Crawl 函数来并行地抓取 URL,并且保证不重复。
 * 提示:你可以用一个 map 来缓存已经获取的 URL,但是要注意 map 本身并不是并发安全的!
 */

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, sMap *safeMap) {
	defer sMap.wg.Done()

	if depth <= 0 {
		return
	}

	if sMap.Add(url) {
		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 {
			go Crawl(u, depth-1, fetcher, sMap)
		}
	}
	return
}

func main() {
	url := "https://golang.org/"
	sMap := safeMap{v: make(map[string]int)}
	sMap.Add(url)
	Crawl(url, 4, fetcher, &sMap)
	sMap.wg.Wait()
}

type safeMap struct {
	v   map[string]int
	mux sync.Mutex
	wg  sync.WaitGroup
}

func (m safeMap) Add(s string) bool {
	m.mux.Lock()
	defer m.mux.Unlock()
	if _, ok := m.v[s]; ok {
		return false
	} else {
		m.v[s] = 1
		m.wg.Add(1)
		return true
	}
}

// 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/",
		},
	},
}