《go语言圣经》练习答案--第五章
练习 5.2: 编写函数,记录在HTML树中出现的同名元素的次数。
package main
import (
"fmt"
"golang.org/x/net/html"
"os"
)
func main() {
doc, err := html.Parse(os.Stdin)
if err != nil {
fmt.Fprintf(os.Stderr, "findlinks1: %v\n", err)
os.Exit(1)
}
elements := make(map[string]int)
visit(elements, doc)
for e, c := range elements {
fmt.Printf("%s\t%d\n", e, c)
}
}
func visit(elements map[string]int, n *html.Node) {
if n.Type == html.ElementNode {
elements[n.Data]++
}
for i := n.FirstChild; i != nil; i = i.NextSibling {
visit(elements, i)
}
}
输出
script 11
path 4
code 1
h2 4
link 3
a 134
aside 1
练习 5.3: 编写函数输出所有text结点的内容。注意不要访问<script>
和<style>
元素,因为这些元素对浏览者是不可见的。
package main
import (
"fmt"
"golang.org/x/net/html"
"os"
)
func main() {
doc, err := html.Parse(os.Stdin)
if err != nil {
fmt.Fprintf(os.Stderr, "findlinks1: %v\n", err)
os.Exit(1)
}
visit(doc)
}
func visit(n *html.Node) {
if n != nil && n.Type == html.ElementNode {
if n.Data == "script" || n.Data == "style" {
return
}
}
if n.Type == html.TextNode {
fmt.Println(n.Data)
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
visit(c)
}
}
练习 5.4: 扩展visit函数,使其能够处理其他类型的结点,如images、scripts和style sheets。
var filter = map[string]string{
"a": "href",
"img": "src",
"script": "src",
}
func visit(links []string, n *html.Node) []string {
for k, v := range filter {
if n.Type == html.ElementNode && n.Data == k {
for _, a := range n.Attr {
if a.Key == v {
links = append(links, a.Val)
}
}
}
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
links = visit(links, c)
}
return links
}
练习 5.5: 实现countWordsAndImages。
func countWordsAndImages(n *html.Node) (words, images int) {
if n.Type == html.TextNode {
scanner := bufio.NewScanner(strings.NewReader(n.Data))
scanner.Split(bufio.ScanWords)
for scanner.Scan() {
words++
}
}
if n.Type == html.ElementNode && n.Data == "img" {
images++
}
return words, images
}
练习 5.6: 修改gopl.io/ch3/surface (§3.2) 中的corner函数,将返回值命名,并使用bare return。
func corner(i, j int) (sx, sy float64) {
// Find point (x,y) at corner of cell (i,j).
x := xyrange * (float64(i)/cells - 0.5)
y := xyrange * (float64(j)/cells - 0.5)
// Compute surface height z.
z := f(x, y)
// Project (x,y,z) isometrically onto 2-D SVG canvas (sx,sy).
sx = width/2 + (x-y)*cos30*xyscale
sy = height/2 + (x+y)*sin30*xyscale - z*zscale
return
}
练习 5.9: 编写函数expand,将s中的"foo"替换为f("foo")的返回值。
func expand(s string, f func(string) string) string {
str := strings.Replace(s, "foo", f("foo"), -1)
return str
}
练习5.10: 重写topoSort函数,用map代替切片并移除对key的排序代码。验证结果的正确性(结果不唯一)。
package main
import (
"fmt"
)
var prereqs = map[string][]string{
"算法": {"数据结构"},
"微积分": {"线性代数"},
"编译器": {
"数据结构",
"形式语言",
"计算机组成",
},
"数据结构": {"离散数学"},
"数据库": {"数据结构"},
"离散数学": {"编程入门"},
"形式语言": {"离散数学"},
"计算机网络": {"操作系统"},
"操作系统": {"数据结构", "计算机组成"},
"编程语言": {"数据结构", "计算机组成"},
}
func main() {
for k, v := range topoSort(prereqs) {
fmt.Printf("%d:\t%s\n", k, v)
}
}
func topoSort(m map[string][]string) map[int]string {
order := make(map[int]string)
seen := make(map[string]bool)
index := 1
var visitAll func(items []string)
visitAll = func(items []string) {
for _, item := range items {
if !seen[item] {
seen[item] = true
visitAll(m[item])
//order = append(order, item)
order[index] = item
index++
}
}
}
var keys []string
for key := range m {
keys = append(keys, key)
}
visitAll(keys)
return order
}
练习5.15: 编写类似sum的可变参数函数max和min。考虑不传参时,max和min该如何处理,再编写至少接收1个参数的版本。
func max(vals ...int) (m int) {
if len(vals) == 0 {
return 0
}
if len(vals) == 1 {
return vals[0]
}
m = vals[0]
for i := 1; i < len(vals); i++ {
if m > vals[i] {
m = vals[i]
}
}
return m
}
func min(vals ...int) (m int) {
if len(vals) == 0 {
return 0
}
if len(vals) == 1 {
return vals[0]
}
m = vals[0]
for i := 1; i < len(vals); i++ {
if m < vals[i] {
m = vals[i]
}
}
return m
}
练习5.18:不修改fetch的行为,重写fetch函数,要求使用defer机制关闭文件。
func fetch(url string) (filename string, n int64, err error) {
resp, err := http.Get(url)
if err != nil {
return "", 0, err
}
defer resp.Body.Close()
local := path.Base(resp.Request.URL.Path)
if local == "/" {
local = "index.html"
}
f, err := os.Create(local)
if err != nil {
return "", 0, err
}
defer func() {
if closeErr := f.Close(); err == nil {
err = closeErr
}
}()
// Close file, but prefer error from Copy, if any.
n, err = io.Copy(f, resp.Body)
return local, n, err
}
练习5.19: 使用panic和recover编写一个不包含return语句但能返回一个非零值的函数。
func rzero(a int) (r int) {
defer func() {
if p := recover(); p != nil {
r = a
}
}()
panic(a)
}