第四章

  • 数组
  • slice
  • map
  • 结构体
  • json
  • 文本和HTML模板

练习4.1

编写一个函数,计算两个SHA256哈希中不同的bit的数目

  • 得到两个字符串的sha256结果,为[32]byte类型,然而需要对比bit
  • 暂时没有得到很好的操作bit的方法,因此采用了格式化字符串的’%08b’这种奇葩的方案
  • 使用了bufio和bytes.Buffer,最后需要强制Flush
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
package main

import (
"bytes"
"bufio"
"fmt"
"crypto/sha256"
)

func main() {
var r1 bytes.Buffer
writer := bufio.NewWriter(&r1)
fmt.Fprintf(writer, "%08b", sha256.Sum256([]byte("1")))
writer.Flush()
r1Str := r1.String()

var r2 bytes.Buffer
writer2 := bufio.NewWriter(&r2)
fmt.Fprintf(writer2, "%08b", sha256.Sum256([]byte("2")))
writer2.Flush()
r2Str := r2.String()

count := 0
for i, _ := range r1Str {
if r1Str[i] != r2Str[i] {
count += 1
}
}
fmt.Println(count)
}

练习4.2

编写一个程序,默认情况下打印标准输入的SHA256编码,并支持通过命令行flag定制,输出SHA384或SHA512哈希算法

  • 这个问题看起来是一个命令行参数解析的问题,flag模块
  • 针对多个不同的case使用switch语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"flag"
"fmt"
"crypto/sha512"
"crypto/sha256"
)

func main() {
method := flag.String("method", "sha256", "select hash method(sha256,sha384,sha512)")
text := flag.String("text", "", "input the string your want to hash")
flag.Parse()
switch *method {
case "sha256":
fmt.Printf("%x\n", sha256.Sum256([]byte(*text)))
case "sha384":
fmt.Printf("%x\n", sha512.Sum384([]byte(*text)))
case "sha512":
fmt.Printf("%x\n", sha512.Sum512([]byte(*text)))
default:
panic("not support hash method")
}
}

练习4.3

重写函数reverse,使用数组指针作为参数而不是slice

  • 使用指针避免值拷贝
  • 使用的时候注意运算优先级
1
2
3
4
5
6
7
package main

func Reverse(s *[]int) {
for i,j := 0,len(*s)-1;i<j;i,j =i+1,j-1{
(*s)[i],(*s)[j] = (*s)[j],(*s)[i]
}
}

练习4.4

编写一个函数rorate,实现一次遍历就可以完成元素旋转

  • 找到位置当做起始位置,对起始位置前面的数据进行倒序append
1
2
3
4
5
6
7
8
9
package main

func Rotate(s []int, position int) []int {
r := s[position:]
for i := position-1 ; i >= 0; i-- {
r = append(r, s[i])
}
return r
}

练习4.5

编写一个就地处理函数,用于去除[]string slice中相邻的重复字符串元素

  • 设置标记为和当前位置两个参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

func DeleteRepet(s []string) []string {
if len(s) < 1 {
return s
}
current := s[0]
position := 0
for i := 1; i < len(s); i++ {
if s[i] != current {
position += 1
s[position] = s[i]
current = s[i]
}
}
return s[:position+1]
}

练习4.6

编写一个就地处理函数,用于将一个UTF-8编码的字节slice中所有相邻的Unicode空白字符(查看unicode.IsSpace)缩减为一个ASCII空白字符

  • 将多个连续的空格转换成单个空格,和上面差不多,设置标志位和当前位置,另外判断的条件会稍微多一些
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "unicode"

func DeleteEmpty(s []rune) []rune {
position := 0
tag := unicode.IsSpace(s[0])
for i := 1; i < len(s); i++ {
if unicode.IsSpace(s[i]) {
if tag != true {
position += 1
s[position] = s[i]
tag = true
}
} else {
tag = false
position += 1
s[position] = s[i]
}
}
return s[:position+1]
}

练习4.7

修改函数reverse,来翻转一个UTF-8编码的字符串中的字符元素,传入参数是该字符串对应的字节slice类型([]byte).你可以做到不需要重新分配内存就实现该功能吗

  • 这个感觉有点难,类型转换会导致内存重新分配
  • 我的思路是对每个byte读取前面有几个1,有几个1则表示有多少个byte是连续的。至于翻转,感觉不好翻…..但是能够转换类型就好办了,直接转换成rune
1
2
3
4
5
6
7
8
func Reverse() {
a := []byte("我要搞Golang")
b := []rune(string(a))
for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 {
b[i], b[j] = b[j], b[i]
}
_ = []byte(string(b))
}

练习4.8

修改charcount的代码来统计字母,数字和其他Unicode分类中的字符数量,可以使用函数unicode.IsLetter等

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
package main

import (
"bufio"
"os"
"io"
"unicode"
"fmt"
)

func main() {
in := bufio.NewReader(os.Stdin)
var control, digit, graphic, letter, lower, mark, number, print, punct, space, symbol, title, upper int
for {
r, _, err := in.ReadRune()
if err == io.EOF {
break
}
if unicode.IsControl(r) {
control ++
}
if unicode.IsDigit(r) {
digit++
}
if unicode.IsGraphic(r) {
graphic++
}
if unicode.IsLetter(r) {
letter++
}
if unicode.IsLower(r) {
lower++
}
if unicode.IsMark(r) {
mark++
}
if unicode.IsNumber(r) {
number++
}
if unicode.IsPrint(r) {
print++
}
if unicode.IsPunct(r) {
punct ++
}
if unicode.IsSpace(r) {
space ++
}
if unicode.IsSpace(r) {
symbol ++
}
if unicode.IsTitle(r) {
title ++
}
if unicode.IsUpper(r) {
upper ++
}
}
fmt.Printf("control: %d\n", control)
fmt.Printf("digit: %d\n", digit)
fmt.Printf("graphic: %d\n", graphic)
fmt.Printf("letter: %d\n", letter)
fmt.Printf("lower: %d\n", lower)
fmt.Printf("mark: %d\n", mark)
fmt.Printf("number: %d\n", number)
fmt.Printf("print: %d\n", print)
fmt.Printf("punct: %d\n", punct)
fmt.Printf("space: %d\n", space)
fmt.Printf("symbol: %d\n", symbol)
fmt.Printf("title: %d\n", title)
fmt.Printf("upper: %d\n", upper)
}

练习4.9

编写一个程序wordfreq来汇总输入文本文件中每个单词出现的次数.在第一次调用scan之前,需要使用input.Split(bufio.ScanWords)来将文本行按照单词分割而不是行分割

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"bufio"
"os"
"fmt"
)

func main() {
scanner := bufio.NewScanner(bufio.NewReader(os.Stdin))
scanner.Split(bufio.ScanWords)
record := map[string]int{}
for scanner.Scan() {
record[scanner.Text()]++
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "Reading input:", err)
}

for k, v := range record {
fmt.Printf("%s\t%d\n", k, v)
}
}

练习4.10

修改issues实例,按照时间来输出结果,比如一个月以内,一年以内或者超过一年

  • 对获得的结果使用sort排序
  • 将所有结果依照一个月、一年、超过一年的放入三个slice当中
  • 最后输出
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
package main

import (
"time"
"net/url"
"strings"
"net/http"
"fmt"
"encoding/json"
"os"
"log"
"sort"
)

const IssuesURL = "https://api.github.com/search/issues"

type IssuesSearchResult struct {
TotalCount int `json:"total_count"`
Items []*Issue
}

type Issue struct {
Number int
HTMLURL string `json:"html_url"`
Title string
State string
User *User
CreatedAt time.Time `json:"created_at"`
Body string
}

type User struct {
Login string
HTMLURL string `json:"html_url"`
}

func SearchIssues(terms []string) (*IssuesSearchResult, error) {
q := url.QueryEscape(strings.Join(terms, " "))
resp, err := http.Get(IssuesURL + "?q=" + q)
if err != nil {
return nil, err
}

if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return nil, fmt.Errorf("search query failed: %s", resp.Status)
}

var result IssuesSearchResult
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
resp.Body.Close()
return nil, err
}
resp.Body.Close()
return &result, nil
}

func SortResult(result *IssuesSearchResult) {
sort.Slice(result.Items, func(i, j int) bool {
return result.Items[i].CreatedAt.Before(result.Items[j].CreatedAt)
})
}

func main() {
result, err := SearchIssues(os.Args[1:])
if err != nil {
log.Fatal(err)
}

SortResult(result)
var month, year, distant []*Issue

for v, i := range result.Items {
if i.CreatedAt.After(time.Now().Add(time.Hour * 24 * 30 * -1)) {
month = append(month, result.Items[v])
} else if i.CreatedAt.After(time.Now().Add(time.Hour * 24 * 365 * -1)) {
year = append(year, result.Items[v])
} else {
distant = append(distant, result.Items[v])
}
}

fmt.Printf("%d issues:\n", result.TotalCount)
fmt.Printf("with in a month\n")
for _, item := range month {
fmt.Printf("#%-5d %9.9s %.55s %s\n", item.Number, item.User.Login, item.Title, item.CreatedAt)
}
fmt.Printf("with in a year\n")
for _, item := range year {
fmt.Printf("#%-5d %9.9s %.55s %s\n", item.Number, item.User.Login, item.Title, item.CreatedAt)
}

fmt.Printf("long long ago\n")
for _, item := range distant {
fmt.Printf("#%-5d %9.9s %.55s %s\n", item.Number, item.User.Login, item.Title, item.CreatedAt)
}
}

练习4.11

开发一个工具让用户可以通过命令行创建、读取、更新或者关闭Github的issues,当需要额外输入的时候,调用他们喜欢的文本编辑器

  • 啊,感觉这个题目很无聊~~~

练习4.12

流行web漫画xkcd有一个JSON接口.例如,调用https://xkcd.com/571/info.0.json输出漫画571的详细描述,这个是很多人最喜欢的之一.下载每一个URL并且构建一个离线索引.编写xkcd来使用这个索引,可以通过命令行指定的搜索条件来查找并输出符合条件的每个漫画的URL和剧本

  • 感觉这条超纲了啊,构建离线索引…….是写到sqlite里面吗?查找的时候正则匹配?

练习4.13

基于JSON开发的web服务,开放电影数据库让你可以在https://omdbapi.com/上通过名字来搜索电影并下载海报图片.开发一个poster工具以通过命令行指定的电影名称来下载海报

  • 没什么兴趣,就一个JSON解析

练习4.14:

创建一个web服务器,可以通过查询github并缓存信息,然后可以浏览bug列表、里程碑信息以及用户参与的信息

  • 这题应该意图就是编写模板了
  • 至于缓存大概用Redis吧
  • 我很懒,就随便搞了个模板测试了下用法
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

package main

import (
"html/template"
"net/http"
)

func main() {
const templ = `<p>A: {.A}</p><p>B: {.B}</p>` (换{},因为hexo渲染问题)
t := template.Must(template.New("escape").Parse(templ))

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
var data struct {
A string
B template.HTML
}
data.A = "<b>Hello!</b>"
data.B = "<b>Hello!</b>"

t.Execute(w, data)
})
http.ListenAndServe(":8888", nil)

}