小文字 吃饭,睡觉,遛狗头

Go指南/习题解答

img

背景

Go Tour练习题,解答。

Go指南/解答

练习:Stringers
让 IPAddr 类型实现 fmt.Stringer 以便用点分格式输出地址。

例如,IPAddr{1, 2, 3, 4} 应当输出 "1.2.3.4"。

  • 解答
package main

import "fmt"
import "strconv"

type IPAddr [4]byte

// TODO: Add a "String() string" method to IPAddr.
func (addr IPAddr) String() string {
	readableip := ""
	last := len(addr) - 1
	for i, c := range addr {
		readableip += strconv.Itoa(int(c))
		if i != last {
			readableip += "."
		}
	}
	return readableip
}
func main() {
	addrs := map[string]IPAddr{
		"loopback":  {127, 0, 0, 1},
		"googleDNS": {8, 8, 8, 8},
	}
	for n, a := range addrs {
		fmt.Printf("%v: %v\n", n, a.String())
	}
}
练习:slice
实现 Pic 。它返回一个长度为 dy 的 slice,其中每个元素是一个长度为 dx 且元素类型为8位无符号整数的 slice。当你运行这个程序时, 它会将每个整数作为对应像素的灰度值(好吧,其实是蓝度)并显示这个 slice 所对应的图像。

计算每个像素的灰度值的方法由你决定;几个有意思的选择包括 (x+y)/2、x*y 和 x^y 。

(需要使用循环来分配 [][]uint8 中的每个 []uint8 。)

(使用 uint8(intValue) 来在类型之间进行转换。)
  • 解答
package main

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

func Pic(dx, dy int) [][]uint8 {
	var	p = make([][]uint8,dy,dy)
	for i,_:=range p {
		p[i]=make([]uint8,dx,dx)
	}
	return p
}

func main() {
	pic.Show(Pic)
}
练习:map
实现 WordCount。它应当返回一个含有 s 中每个 “词” 个数的 map。函数 wc.Test 针对这个函数执行一个测试用例,并输出成功还是失败。

你会发现 strings.Fields 很有帮助。
  • 解答
package main

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

func WordCount(s string) map[string]int {
	q:=strings.Fields(s)
	m:=make(map[string]int)
	for _,c:=range q {
		m[c]+=1
	}
	fmt.Println("%v", m)
	return m
}

func main() {
	wc.Test(WordCount)
}
练习:斐波纳契闭包
现在来通过函数做些有趣的事情。

实现一个 fibonacci 函数,返回一个函数(一个闭包)可以返回连续的斐波纳契数。

< 23/24 > exercise-fibonacci-closure.go 语法高亮
  • 解答
package main

import "fmt"

// fibonacci 函数会返回一个返回 int 的函数。
func fibonacci() func() int {
	first:=0
	second:=1
	index:=0
	return func() int {
		if index == 0 {
			index++
			return 0
		}
		if index == 1 {
			index++
			return 1
		}
		cur:=first+second
		first=second
		second=cur
		return cur
	}
}

func main() {
	f := fibonacci()
	for i := 0; i < 10; i++ {
		fmt.Println(f())
	}
}
练习:错误
从先前的练习中复制 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"
)
type ErrNegativeSqrt float64
func (e ErrNegativeSqrt) Error() string {
	return "cannot Sqrt negtive number: "+fmt.Sprint(float64(e))
}
 
func Sqrt(x float64) (float64, error) {
	if x<0 {
		return x, ErrNegativeSqrt(x)
	}
	z:=1.0
	for i:=0;i<10;i++{
		z=z-(z*z-x)/2/z
	}
	return z, nil
}

func main() {
	fmt.Println(Sqrt(2))
	fmt.Println(Sqrt(-2))
}
练习:Reader
实现一个 Reader 类型,它不断生成 ASCII 字符 'A' 的流。
  • 解答
package main

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

type MyReader struct{}

// TODO: Add a Read(b []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{})
}
练习:rot13Reader
一个常见模式是 io.Reader 包裹另一个 io.Reader,然后通过某种形式修改数据流。

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

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

已经帮你构造了 rot13Reader 类型。 通过实现 Read 方法使其匹配 io.Reader。
  • 解答
package main

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

type rot13Reader struct {
	r io.Reader
}

func (r rot13Reader) Read(b []byte) (int, error) {
	n, e := r.r.Read(b)
	for i := 0; i < n; i++ {
		item := b[i]
		if (item <= 'Z' && item > 'M') || (item <= 'z' && item > 'm') {
			b[i] = item - 13
		} else if (item >= 'A' && item <= 'M') || (item >= 'a' && item <= 'm') {
			b[i] = item + 13
		} else {
			b[i] = item
		}
	}
	return n, e
}
func main() {
	s := strings.NewReader("Lbh penpxrq gur pbqr!")
	r := rot13Reader{s}
	io.Copy(os.Stdout, &r)
}

*练习:等价二叉树

练习:等价二叉树
1. 实现 Walk 函数。

2. 测试 Walk 函数。

函数 tree.New(k) 构造了一个随机结构的二叉树,保存了值 k,2k,3k,...,10k。 创建一个新的 channel ch 并且对其进行步进:

go Walk(tree.New(1), ch)
然后从 channel 中读取并且打印 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 "golang.org/x/tour/tree"
import "fmt"

// Walk 步进 tree t 将所有的值从 tree 发送到 channel ch。
func Walk(t *tree.Tree, ch chan int) {
	ch <- t.Value
	if t.Left != nil {
		Walk(t.Left, ch)
	}
	if t.Right != nil {
		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)
	result := make(map[int]int)
	for i := 0; i < 10; i++ {
		value1 := <-ch1
		value2 := <-ch2
		result[value1]++
		result[value2]--
	}
	for _, e := range result {
		if e == 0 {
			continue
		}
		return false
	}
	return true
}

func main() {
	fmt.Println("tree.New(1) =tree.New(1):", Same(tree.New(1), tree.New(1)))
	fmt.Println("tree.New(1) =tree.New(2):", Same(tree.New(1), tree.New(2)))
}

小结