《go语言圣经》练习答案--第四章
练习 4.1: 编写一个函数,计算两个SHA256哈希码中不同bit的数目。
思路:首先进行按位异或,异或之后二进制序列相同的为 0,不同的为 1,这样就又转换为求二进制中 1 的个数。
package main
import (
"crypto/sha256"
"fmt"
)
var pc [256]byte
func init() {
for i := range pc {
pc[i] = pc[i/2] + byte(i&1)
}
}
func main() {
c1 := sha256.Sum256([]byte("x"))
c2 := sha256.Sum256([]byte("X"))
fmt.Println(popCount(c1, c2))
}
func popCount(s1, s2 [32]byte) int {
count := 0
for i := 0; i < 32; i++ {
temp := s1[i] ^ s2[i]
count += int(pc[temp])
}
return count
}
练习 4.2: 编写一个程序,默认情况下打印标准输入的SHA256编码,并支持通过命令行flag定制,输出SHA384或SHA512哈希算法。
package main
import (
"crypto/sha256"
"crypto/sha512"
"flag"
"fmt"
)
var hashMethod = flag.Int("s", 256, "选择哈希版本:256、384、512。")
func main() {
flag.Parse()
printHash()
}
func printHash() {
var s string
fmt.Println("输入要解析的字符串:")
fmt.Scanf("%s\n", s)
switch *hashMethod {
case 256:
fmt.Printf("%x\n", sha256.Sum256([]byte(s)))
case 384:
fmt.Printf("%x\n", sha512.Sum384([]byte(s)))
case 512:
fmt.Printf("%x\n", sha512.Sum512([]byte(s)))
}
}
练习 4.3: 重写reverse函数,使用数组指针代替slice。
package main
import "fmt"
func main() {
s := [...]int{0, 1, 2, 3, 4, 5}
fmt.Println(s)
reverse(&s)
fmt.Println(s)
}
func reverse(s *[6]int) {
fmt.Printf("%T\n", s)
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
}
输出
[0 1 2 3 4 5]
*[6]int
[5 4 3 2 1 0]
练习 4.4: 编写一个rotate函数,通过一次循环完成旋转。
func main() {
s := []int{0, 1, 2, 3, 4, 5}
s = rotate(s, 2)
fmt.Println(s)
}
func rotate(s []int, n int) []int {
result := make([]int, len(s))
index := len(s) - n
for i := 0; i < len(s); i++ {
if index >= len(s) {
index = 0
}
result[i] = s[index]
index++
}
return result
}
练习 4.5: 写一个函数在原地完成消除[]string中相邻重复的字符串的操作。
使用 map 去重
func nonEqual(strings []string) []string {
m1 := make(map[string]int)
for i := 0; i < len(strings); i++ {
if _, ok := m1[strings[i]]; ok {
i++
continue
} else {
m1[strings[i]] = i
}
}
i := 0
for k, _ := range m1 {
strings[i] = k
i++
}
return strings[:i]
}
双重循环去重
func nonEqual1(strings []string) []string {
for i := 0; i < len(strings)-1; i++ {
for j := i + 1; j < len(strings); j++ {
if strings[i] == strings[j] {
copy(strings[j:], strings[j+1:])
strings = strings[:len(strings)-1]
j--
}
}
}
return strings
}
练习 4.6: 编写一个函数,原地将一个UTF-8编码的[]byte类型的slice中相邻的空格(参考unicode.IsSpace)替换成一个空格返回
package main
import (
"fmt"
"unicode"
)
func main() {
s := a([]byte{'1', '2', ' ', ' ', '3', ' ', ' ', ' '})
fmt.Printf("%c %d", s, len(s))
}
func a(s []byte) []byte {
for i := 0; i < len(s)-1; i++ {
if unicode.IsSpace(rune(s[i])) {
if unicode.IsSpace(rune(s[i+1])) {
copy(s[i+1:], s[i+2:])
s = s[:len(s)-1]
i--
}
}
}
return s
}
输出
[1 2 3 ] 5
练习 4.7: 修改reverse函数用于原地反转UTF-8编码的[]byte。是否可以不用分配额外的内存?
不理解,反转 []byte 和是否 UTF-8 编码有关系?
练习 4.8: 修改charcount程序,使用unicode.IsLetter等相关的函数,统计字母、数字等Unicode中不同的字符类别。
package main
import (
"bufio"
"fmt"
"io"
"os"
"unicode"
"unicode/utf8"
)
func main() {
counts := make(map[rune]int) // counts of Unicode characters 不同字符的计数,类型为 map
var utflen [utf8.UTFMax + 1]int // count of lengths of UTF-8 encodings 字符编码长度的计数,类型为 [5]int
invalid := 0 // count of invalid UTF-8 characters 无效字符的计数
var utftype [3]int //1代表字母 2代表数字 0代表其他类别
in := bufio.NewReader(os.Stdin)
for {
r, n, err := in.ReadRune() // returns rune, nbytes, error
if err == io.EOF {
break
}
if err != nil {
fmt.Fprintf(os.Stderr, "charcount: %v\n", err)
os.Exit(1)
}
if r == unicode.ReplacementChar && n == 1 {
invalid++
continue
}
switch {
case unicode.IsLetter(r):
utftype[1]++
case unicode.IsNumber(r):
utftype[2]++
default:
utftype[0]++
}
counts[r]++
utflen[n]++
}
fmt.Printf("rune\tcount\n")
for c, n := range counts {
fmt.Printf("%q\t%d\n", c, n)
}
fmt.Print("\nlen\tcount\n")
for i, n := range utflen {
if i > 0 {
fmt.Printf("%d\t%d\n", i, n)
}
}
fmt.Print("\ntype\tcount\n")
for i, n := range utftype {
var s string
switch i {
case 0:
s = "Other"
case 1:
s = "Letter"
case 2:
s = "Number"
}
fmt.Printf("%s\t%d\n", s, n)
}
if invalid > 0 {
fmt.Printf("\n%d invalid UTF-8 characters\n", invalid)
}
}
练习 4.9: 编写一个程序wordfreq程序,报告输入文本中每个单词出现的频率。在第一次调用Scan前先调用input.Split(bufio.ScanWords)函数,这样可以按单词而不是按行输入。
func wordfreq() {
counts := make(map[string]int)
in := bufio.NewScanner(os.Stdin)
in.Split(bufio.ScanWords)
for in.Scan() {
counts[in.Text()]++
}
for k, v := range counts {
fmt.Println("%s %d\n", k, v)
}
}
练习 4.10: 修改issues程序,根据问题的时间进行分类,比如不到一个月的、不到一年的、超过一年。
package main
import (
"fmt"
github "hello/Github"
"log"
"os"
"time"
)
func main() {
//now 为现在的时间,yearAgo 为距现在一年的时间,monthAgo 为距现在一月的时间。
now := time.Now()
yearAgo := now.AddDate(-1, 0, 0)
monthAgo := now.AddDate(0, -1, 0)
//三个切片,用来存储 不足一个月的问题,不足一年的问题,超过一年的问题。
yearAgos := make([]*github.Issue, 0)
monthAgos := make([]*github.Issue, 0)
lessMonths := make([]*github.Issue, 0)
result, err := github.SearchIssues(os.Args[1:])
if err != nil {
log.Fatal(err)
}
fmt.Printf("%d issues:\n", result.TotalCount)
for _, item := range result.Items {
//如果 yearAgo 比 创建时间晚,说明超过一年
if yearAgo.After(item.CreatedAt) {
yearAgos = append(yearAgos, item)
//如果 monthAgo 比 创建时间晚,说明超过一月 不足一年
} else if monthAgo.After(item.CreatedAt) {
monthAgos = append(monthAgos, item)
//如果 monthAgo 比 创建时间早,说明不足一月。
} else if monthAgo.Before(item.CreatedAt) {
lessMonths = append(lessMonths, item)
}
}
fmt.Printf("\n一年前\n")
for _, item := range yearAgos {
fmt.Printf("#%-5d %9.9s %.55s %v\n",
item.Number, item.User.Login, item.Title, item.CreatedAt)
}
fmt.Printf("\n一月前\n")
for _, item := range monthAgos {
fmt.Printf("#%-5d %9.9s %.55s %v\n",
item.Number, item.User.Login, item.Title, item.CreatedAt)
}
fmt.Printf("\n不足一月\n")
for _, item := range lessMonths {
fmt.Printf("#%-5d %9.9s %.55s %-40v\n",
item.Number, item.User.Login, item.Title, item.CreatedAt)
}
}
练习 4.14: 创建一个web服务器,查询一次GitHub,然后生成BUG报告、里程碑和对应的用户信息。
package main
import (
github "hello/Github"
"html/template"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", handle)
http.ListenAndServe("0.0.0.0:8080", nil)
}
func handle(w http.ResponseWriter, r *http.Request) {
keywords := []string{"goland", "java"}
result, err := github.SearchIssues(keywords)
if err != nil {
log.Fatal(err)
}
var issueList = template.Must(template.New("issuelist").Parse(`
<h1>{{.TotalCount}} issues</h1>
<table>
<tr style='text-align: left'>
<th>#</th>
<th>State</th>
<th>User</th>
<th>Title</th>
</tr>
{{range .Items}}
<tr>
<td><a href='{{.HTMLURL}}'>{{.Number}}</a></td>
<td>{{.State}}</td>
<td><a href='{{.User.HTMLURL}}'>{{.User.Login}}</a></td>
<td><a href='{{.HTMLURL}}'>{{.Title}}</a></td>
</tr>
{{end}}
</table>
`))
issueList.Execute(w, result)
}