练习:等价二叉查找树

Go 语言之旅 (go-zh.org)

题目

1. 实现 Walk 函数。

2. 测试 Walk 函数。

函数 tree.New(k) 用于构造一个随机结构的已排序二叉查找树,它保存了值 k, 2k, 3k, …, 10k

创建一个新的信道 ch 并且对其进行步进:

1
go Walk(tree.New(1), ch)

然后从信道中读取并打印 10 个值。应当是数字 1, 2, 3, ..., 10

3.Walk 实现 Same 函数来检测 t1t2 是否存储了相同的值。

4. 测试 Same 函数。

Same(tree.New(1), tree.New(1)) 应当返回 true,而 Same(tree.New(1), tree.New(2)) 应当返回 false

Tree 的文档可在这里找到。

答案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package main

import (
"fmt"
"golang.org/x/tour/tree"
)

// Walk 步进 tree t 将所有的值从 tree 发送到 channel ch。
func Walk(t *tree.Tree, ch chan int) {
walkRecur(t, ch)
close(ch)
}

func walkRecur(t *tree.Tree, ch chan int) {
if t == nil {
return
}
walkRecur(t.Left, ch)
ch <- t.Value
walkRecur(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)

for {
v1, ok1 := <-ch1
v2, ok2 := <-ch2
if !ok1 || !ok2 {
return ok1 == ok2
}
if v1 != v2 {
return false
}
}
}

func main() {
fmt.Println(Same(tree.New(1), tree.New(1))) // true
fmt.Println(Same(tree.New(1), tree.New(2))) // fal

}

练习:Web 爬虫

题目

在这个练习中,我们将会使用 Go 的并发特性来并行化一个 Web 爬虫。

修改 Crawl 函数来并行地抓取 URL,并且保证不重复。

提示:你可以用一个 map 来缓存已经获取的 URL,但是要注意 map 本身并不是并发安全的!

答案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package main

import (
"fmt"
"sync"
"time"
)

type Fetcher interface {
// Fetch 返回 URL 的 body 内容,并且将在这个页面上找到的 URL 放到一个 slice 中。
Fetch(url string) (body string, urls []string, err error)
}

// Crawl 使用 fetcher 从某个 URL 开始递归的爬取页面,直到达到最大深度。

type SafeCrawl struct {
myMap map[string]string
mux sync.Mutex
}

func (safeCrawl *SafeCrawl) addOne(url, body string, err error) {

if err != nil {
if _, ok := safeCrawl.myMap[url]; !ok {
safeCrawl.mux.Lock()
safeCrawl.myMap[url] = ""
fmt.Println(err)
safeCrawl.mux.Unlock()
}
return
}

if _, ok := safeCrawl.myMap[url]; !ok {
safeCrawl.mux.Lock()
safeCrawl.myMap[url] = body
fmt.Printf("found: %s %q\n", url, safeCrawl.myMap[url])
safeCrawl.mux.Unlock()
}

}

func Crawl(url string, depth int, fetcher Fetcher, safeCrawl SafeCrawl) {
// 并行的抓取 URL。
// 不重复抓取页面。
if depth <= 0 {
return
}

body, urls, err := fetcher.Fetch(url)

safeCrawl.addOne(url, body, err)

for _, u := range urls {
Crawl(u, depth-1, fetcher, safeCrawl)
}
return
}

func main() {
safeCrawl := SafeCrawl{
myMap: make(map[string]string),
}
go Crawl("https://golang.org/", 4, fetcher, safeCrawl)
go Crawl("https://golang.org/", 4, fetcher, safeCrawl)
go Crawl("https://golang.org/", 4, fetcher, safeCrawl)
go Crawl("https://golang.org/", 4, fetcher, safeCrawl)
go Crawl("https://golang.org/", 4, fetcher, safeCrawl)
time.Sleep(time.Second)

}

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