官方教程 A Tour of Go Excercises 個人答案總結及講解

這兩天學完了 A Tour of Go 官方的語法教學,裏面有不少的 Excercise(訓練題)。但願對你們有用,若是有其餘人也寫過,並以爲我寫的不對的,求教!❤️golang

Exercise: Loops and Functions

題目

解答

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))
}
複製代碼
  1. z := x/2 這個是猜想的初始值(也能夠像是題目裏的 hint 寫的設置成 1)
  2. math.Abs(z*z - x) > 0.0000000001 用最優解的邏輯就是給了一個 tolerance 0.0000000001,即咱們用計算公式算出來的 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)
}
複製代碼

Exercise: Slices 生成圖片

  1. pic := make([][]uint8, dy) 先建一個數組,長度是 dy,數組裏每一個元素的內容是一個數組 []uint8
  2. pic[i] = make([]uint8, dx) 在數組裏的第 i 個元素裏,咱們再創造一個 []uint8 數組,長度爲 dx
  3. 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)
}
複製代碼
  1. strings.Files(s) 這個函數會自動切分一個字符串到一個數組,每一個數組裏是一個 word
  2. 創建 map 而後在數組裏每當某一個 word 出現,就相應的增長 1

Exercise: Fibonacci closure

題目

解答

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())
	}
}
複製代碼
  1. closure 也叫閉包,意思是一個函數中須要使用的某一個變量是在此函數外定義的
  2. 在上面的 func fibonacci() func() int 中,返回的是一個函數 func() int,而這個函數每次運行返回的是一個 int
  3. fibonacci() 中,變量 ab 定義在函數 fibonacci() 裏,並被此函數返回的 return func() int { ... } 函數引用到,也就是說在返回的函數裏 ab 兩個變量一直存儲在內存中,且數值會一直變化
  4. f := fibonacci()ffibonacci() 返回的函數,在初始狀況中,此時的 a, b := 0, 1
  5. 以第一次 f() 調用爲例:
    • c := ac 賦值爲 a 即 0
    • a, b = b, a+ba 賦值爲 b 即 1,b 賦值爲 a+b 即 1
    • return c,返回 0,也就是斐波那契數列的第一個值
  6. 第二次 f() 調用,注意此時 a是 1,b 是 1:
    • c := ac 賦值爲 a 即 1
    • a, b = b, a+ba 賦值爲 b 即 1,b 賦值爲 a+b 即 2
    • return c,返回 1,也就是斐波那契數列的第二個值
  7. 以此類推,循環中 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)
	}
}
複製代碼
  1. 引入 strconv.Itoa,int to string
  2. IPAddr 是一個大小爲 4 的 []byte,咱們生成一個 [4]string 數組 s,每個元素是 IP 地址中的一位,而且咱們用 strconv.Itoa(int(val)) 把它轉換爲字符串
  3. strings.Join(s, ".") 將字符串數組用 "." 連起來
  4. 這樣在使用 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)
	}
}
複製代碼
  1. error 類型是一個 built-in interface,須要定義 Error() string 函數,測試一個 error type 是不是 nil 是定義函數返回是否出錯的方法。例如: i, err := strconv.Atoi("42") 返回值中 i 表明函數返回數值,而 err 若是不是 nil 的話,則表示有錯誤發生
  2. func (e ErrNegativeSqrt) Error() string 定義了 ErrNegativeSqrt 屬於 errorError() 函數,也就簡潔說明 ErrNegativeSqrt 是一個 error
  3. func Sqrt(x float64) (float64, error) 函數返回兩個數值,前者爲參數的平方根,後者爲 error,當後者不是 nil 的時候,在 Println 中會自動調用 Error() 輸出相應的錯誤信息字符串。

Exercise: Readers

題目

解答

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{})
}
複製代碼
  1. 由於 MyReader 會輸出無限個 'A',所以只要輸入參數 b []byte 不是一個空的 Buffer,就會寫滿
  2. bLength == 0 也就是 Buffer b 是空時,返回 ErrEmptyBuffer(b) 錯誤
  3. 只要不是空,就都填滿 '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)
}
複製代碼
  1. 先根據 rot13 規則實現函數 func rot13(c byte) byte,也就是 A-MN-Z 的兌換,以及 a-mn-z 的兌換
  2. 定義 rot13ReaderRead 函數,首先使用期包含的 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)
}
複製代碼

Exercise: Images

  1. 由於要用到 image.Rect, color.RGBAModel, color.RGBA 所以,咱們引入 "image""image/color" packages
  2. 定義相關函數,便可獲得結果,這裏 At 函數我沿用了以前的圖片顏色計算公式 v := uint8(x*y + y*y)

Exercise: Equivalent Binary Trees

題目

二叉樹

  • 同一組二叉樹數列可能會存儲在不一樣樣子的二叉樹中,如上圖兩個二叉樹都存儲者數列:1, 1, 2, 3, 5, 8, 13。定義 Tree 類型,並實現以下函數來測試兩個 Tree 是否存儲一樣的數列。
    1. 實現 TreeWalk 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
    2. 實現 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)))
}
複製代碼
  1. Walk 方法中,咱們按照 t.Left, ch <- t.Valuet.Right 順序逐一將數據傳入 ch
  2. Same 方法中,由於咱們知道 tree.New(k) 生成的樹包含 10 個節點,所以咱們生成兩個 Channels 分別存儲兩個 TreeWalk 時的數據,而後一個循環 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/",
		},
	},
}
複製代碼
  1. UrlChecker 是用來測試一個 url 是否已經被 fetch 過,其中包含一個 urls map[string]bool 存儲 URL 抓取狀況,另 mux sync.Mutex 來防止數據被重複修改,進而不會出現,fetcher 在 並行的時候,由於此時某一個 URL 都沒有在 UrlChecker 被標註 fetch 過,進而同時 fetch。
  2. func (c *UrlChecker) Crawled(url string) bool 來存儲一個 URL 已經被抓取的狀態。在測試過程當中 c.mux.Lock() 使得,此段數據被阻斷其餘 goroutine 修改。 defer c.mux.Unlock() 意思是在 return 後再執行解開 Mutual Exclusion 鎖。
  3. 聲明 var uc = UrlChecker{urls: make(map[string]bool)},當 uc.Crawled(url)true 的時候,再也不抓取相應 url。
  4. func Crawl(url string, depth int, fetcher Fetcher, ret chan string) 最後一個參數 ret chan string 傳入一個 channel 來存儲抓取結果
  5. results := make([]chan string, len(urls)),每個 url 下的須要更深一層抓取的 urls 生成相應的多個 channels,並逐一抓取
  6. go Crawl(u, depth-1, fetcher, results[i]) 循環抓取 urls
  7. 複製代碼

for _, result := range results { for s := range result { ret <- s } } ``` 每個抓取到的數據從相應的 channel s 傳入最上層的 channel ret,完成全部的抓取數組

最終的輸出結果:bash

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 哦!微信

相關文章
相關標籤/搜索