【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))
}
View Code

無限逼近:

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

 

練習:切片

(1)切片

實現 Pic。它應當返回一個長度為 dy 的切片,其中每個元素是一個長度為 dx,元素類型為 uint8 的切片。當你運行此程序時,它會將每個整數解釋為灰度值(好吧,其實是藍度值)並顯示它所對應的圖像。

圖像的選擇由你來定。幾個有趣的函數包括 (x+y)/2x*yx^yx*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)
}
View Code

(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)
}
View Code

(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}
View Code

 

練習:斐波那契閉包

(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())
	}
}
View Code

 

練習: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)
	}
}
View Code

 

練習:錯誤

(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)
}
View Code

 

練習: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{})
}
View Code

(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)
}
View Code

(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)
}
View Code

(3)結果

 

練習:等價二叉查找樹

(1)題目

1. 實現 Walk 函數。

2. 測試 Walk 函數。

函數 tree.New(k) 用於構造一個隨機結構的已排序二叉查找樹,它保存了值 k2k3k, ..., 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)))
}
View Code

 

練習: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/",
        },
    },
}
View Code

(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"

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM