CH4-复合数据类型
CH4-复合数据类型
CH4.1.数组
练习4.1.计算两个sha256中不同bit的数目
练习 4.1: 编写一个函数,计算两个SHA256哈希码中不同bit的数目。(参考2.6.2节的PopCount函数。)
// 编写一个函数,计算两个SHA256哈希码中不同bit的数目
package main
import "crypto/sha256"
// 比较两个 SHA256 哈希码中不同 bit 的数量
func diffBitCount(hash1, hash2 [32]byte) int {
count := 0
for i := 0; i < 32; i++ {
// 异或操作,相同为 0,不同为 1
diff := hash1[i] ^ hash2[i]
diff_int := popCountUint8(diff)
// 计算不同 bit 的数量
count += diff_int
}
return count
}
// 计算一个 unit8 中 1 的数量
func popCountUint8(x uint8) int {
count := 0
for x != 0 {
x = x & (x - 1)
count++
}
return count
}
func main() {
c1 := sha256.Sum256([]byte("x"))
c2 := sha256.Sum256([]byte("X"))
println(diffBitCount(c1, c2))
}
练习4.2.计算输入字符串的哈希
练习 4.2: 编写一个程序,默认情况下打印标准输入的SHA256编码,并支持通过命令行flag定制,输出SHA384或SHA512哈希算法。
// 编写一个程序,默认情况下打印标准输入的SHA256编码,并支持通过命令行flag定制,输出SHA384或SHA512哈希算法。
package main
import (
"bufio"
"crypto/sha256"
"crypto/sha512"
"flag"
"fmt"
"os"
)
// 计算数据的 SHA256 哈希码
func sha256Hash(data []byte) string {
sha256 := sha256.Sum256(data)
return fmt.Sprintf("%x", sha256)
}
// 计算数据的 SHA384 哈希码
func sha384Hash(data []byte) string {
sha384 := sha512.Sum384(data)
return fmt.Sprintf("%x", sha384)
}
// 计算数据的 SHA512 哈希码
func sha512Hash(data []byte) string {
sha512 := sha512.Sum512(data)
return fmt.Sprintf("%x", sha512)
}
var hashType = flag.String("ht", "sha256", "hashType-支持 sha256, sha384, sha512")
func main() {
flag.Parse()
hashType := *hashType
// 如果输入的哈希类型不支持,则退出
if hashType != "sha256" && hashType != "sha384" && hashType != "sha512" {
fmt.Printf("不支持的哈希类型 %s\n", hashType)
return
}
fmt.Printf("请输入需要计算 %s 哈希码的数据,输入 exit 退出\n", hashType)
// 读取一行输入
input := bufio.NewScanner(os.Stdin)
for input.Scan() {
// 遇到 exit 时退出
if input.Text() == "exit" {
break
}
data := input.Bytes()
switch hashType {
case "sha256":
hash := sha256Hash(data)
fmt.Println(hash[:])
case "sha384":
hash := sha384Hash(data)
fmt.Println(hash[:])
case "sha512":
hash := sha512Hash(data)
fmt.Println(hash[:])
default:
fmt.Printf("不支持的哈希类型 %s\n", hashType)
}
}
}
CH4.2.Slice
Slice(切片)代表变长的序列,序列中每个元素都有相同的类型。一个slice类型一般写作[]T,其中T代表slice中元素的类型;slice的语法和数组很像,只是没有固定长度而已。
练习4.3.重写reverse函数
练习 4.3: 重写reverse函数,使用数组指针代替slice。
// 重写reverse函数,使用数组指针代替slice。
package main
import "fmt"
// 重写reverse函数,使用数组指针代替slice。
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]
}
}
func main() {
var a = []int{0, 1, 2, 3, 4, 5}
reverse(&a)
fmt.Println(a)
}
练习4.4.编写旋转函数
练习 4.4: 编写一个rotate函数,通过一次循环完成旋转。
利用 apppend 把开头的 n 个元素追加到 slice 的尾部然后从 n 位置截取到末尾返回即可
package main
import "fmt"
// 编写一个rotate函数,通过一次循环完成旋转 slice 中的所有元素。
func rotate(s []int, n int) []int {
for i := 0; i < n; i++ {
s = append(s, s[i])
}
return s[n:]
}
func main() {
var a = []int{0, 1, 2, 3, 4, 5}
fmt.Printf("旋转前:%v\n", a)
a = rotate(a, 2)
fmt.Printf("旋转 2 位后:%v\n", a)
}
练习4.5.消除相邻重复字符串
练习 4.5: 写一个函数在原地完成消除[]string中相邻重复的字符串的操作。
相邻比较, copy 移位然后重新切片即可
package main
import "fmt"
// 写一个函数在原地完成消除[]string中相邻重复的字符串的操作。 写一个函数在原地完成消除[]string中相邻重复的字符串的操作。
func removeDuplicate(s []string) {
// 直接在原 slice 上操作, 无需返回值
for i := 0; i < len(s)-1; i++ {
if s[i] == s[i+1] {
// 删除重复的元素
copy(s[i:], s[i+1:])
// 重新切片
s = s[:len(s)-1]
i--
}
}
fmt.Printf("去重后:%v\n", s)
}
func main() {
var s = []string{"a", "b", "b", "c", "c", "c", "d", "e", "e", "f"}
fmt.Printf("原始 slice:%v\n", s)
removeDuplicate(s)
}
练习4.6.去除相邻空格
练习 4.6: 编写一个函数,原地将一个UTF-8编码的[]byte类型的slice中相邻的空格(参考unicode.IsSpace)替换成一个空格返回
和上一题差不多的思路, 把重复字符改成空格了而已
package main
import "fmt"
// 编写一个函数,原地将一个UTF-8编码的[]byte类型的slice中相邻的空格(参考unicode.IsSpace)替换成一个空格返回
func replaceSpace(s []byte) []byte {
for i := 0; i < len(s)-1; i++ {
if s[i] == ' ' && s[i+1] == ' ' {
copy(s[i:], s[i+1:])
s = s[:len(s)-1]
i--
}
}
return s
}
func main() {
var s = []byte("a b c d e")
fmt.Printf("原始 slice:%v\n", string(s))
s = replaceSpace(s)
fmt.Printf("去除重复空格后:%v\n", string(s))
}
练习4.7.翻转Slice
练习 4.7: 修改reverse函数用于原地反转UTF-8编码的[]byte。是否可以不用分配额外的内存?
感觉描述的不是很清楚, reverse 函数本身就是在原地操作的
// reverse reverses a slice of ints in place.
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]
}
}
CH4.3.Map
练习4.8.重写charcount
练习 4.8: 修改charcount程序,使用unicode.IsLetter等相关的函数,统计字母、数字等Unicode中不同的字符类别。
// 修改charcount程序,使用unicode.IsLetter等相关的函数,统计字母、数字等Unicode中不同的字符类别。
package main
import (
"bufio"
"fmt"
"io"
"os"
"unicode"
"unicode/utf8"
)
func main() {
counts := map[string]int{
"letter": 0,
"digit": 0,
"space": 0,
"punct": 0,
"control": 0,
"other": 0,
}
var utflen [utf8.UTFMax + 1]int // count of lengths of UTF-8 encodings
invalid := 0 // count of invalid UTF-8 characters
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 == utf8.RuneError && n == 1 {
invalid++
continue
}
switch {
case unicode.IsLetter(r):
counts["letter"]++
case unicode.IsDigit(r):
counts["digit"]++
case unicode.IsSpace(r):
counts["space"]++
case unicode.IsPunct(r):
counts["punct"]++
case unicode.IsControl(r):
counts["control"]++
default:
counts["other"]++
}
utflen[n]++
}
fmt.Printf("category\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)
}
}
if invalid > 0 {
fmt.Printf("\n%d invalid UTF-8 characters\n", invalid)
}
}
练习4.9.词频统计
练习 4.9: 编写一个程序wordfreq程序,报告输入文本中每个单词出现的频率。在第一次调用Scan前先调用input.Split(bufio.ScanWords)函数,这样可以按单词而不是按行输入。
package main
import (
"bufio"
"os"
)
func main() {
var count map[string]int = make(map[string]int)
scanner := bufio.NewScanner(os.Stdin)
scanner.Split(bufio.ScanWords)
for scanner.Scan() {
count[scanner.Text()]++
}
for k, v := range count {
println(k, v)
}
}
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Sed ac tellus eu odio commodo ornare.
Praesent bibendum ex vel massa consectetur, et vulputate lacus sodales.
Donec eu ante mauris. Cras vitae risus sed magna elementum congue.
Curabitur condimentum augue non leo bibendum, at ultrices tortor dictum.
Vivamus et aliquet eros. Fusce fringilla, justo id varius posuere, eros nisl faucibus lacus,
sit amet pellentesque magna eros quis urna.
ibendum ex vel massa consectetur, et vulputate lacus sodales.
Donec eu ante mauris. Cras vitae risus sed magna elementum congue.
Curabitur condimentum augue non leo bibendum, at ultrices tortor dictum.
Vivamus et aliquet eros. Fusce fringilla, justo id var
CH4.4.结构体
结构体(Struct)是一种聚合数据类型,它可以将不同类型的数据组合在一起。结
构体用于表示具有多个属性的复杂对象,每个属性称为字段; 通过结构体,可以将一组相关的数据打包在一起,形成一个更高级别的抽象, 例如
// 定义一个名为 Person 的结构体
type Person struct {
Name string
Age int
Address string
}
CH4.5.Json
Go 语言内置了对 JSON (JavaScript Object Notation)数据的编码和解码支持,主要通过标准库 encoding/json
实现
练习4.10.修改issues程序按照时间分类
练习 4.10: 修改issues程序,根据问题的时间进行分类,比如不到一个月的、不到一年的、超过一年。
// 修改issues程序,根据问题的时间进行分类,比如不到一个月的、不到一年的、超过一年。
package main
import (
"fmt"
"log"
"time"
github "GoLearning/Chapter/ch4/ch4_5_json/github"
)
func main() {
// 获取当前时间(年月日)
currentDate := time.Now()
// 一个月前的时间
oneMonthAgo := currentDate.AddDate(0, -1, 0)
// 一年前的时间
oneYearAgo := currentDate.AddDate(-1, 0, 0)
classifyIssues := map[string][]*github.Issue{
"一个月内": []*github.Issue{},
"一年内": []*github.Issue{},
"一年前": []*github.Issue{},
}
// result, err := github.SearchIssues(os.Args[1:])
var repo = []string{"PKUFlyingPig/cs-self-learning"}
result, err := github.SearchIssues(repo)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%d issues:\n", result.TotalCount)
for _, item := range result.Items {
if item.CreatedAt.After(oneMonthAgo) {
classifyIssues["一个月内"] = append(classifyIssues["一个月内"], item)
} else if item.CreatedAt.After(oneYearAgo) {
classifyIssues["一年内"] = append(classifyIssues["一年内"], item)
} else {
classifyIssues["一年前"] = append(classifyIssues["一年前"], item)
}
}
for k, v := range classifyIssues {
fmt.Printf("Issues %s:\n", k)
for _, item := range v {
fmt.Printf("#%-5d %9.9s %.55s %v\n", item.Number, item.User.Login, item.Title, item.CreatedAt)
}
}
}
练习4.11
练习 4.11: 编写一个工具,允许用户在命令行创建、读取、更新和关闭GitHub上的issue,当必要的时候自动打开用户默认的编辑器用于输入文本信息。
调接口,暂时没需求,暂时不写了(
练习4.12
练习 4.12: 流行的web漫画服务xkcd也提供了JSON接口。例如,一个 https://xkcd.com/571/info.0.json 请求将返回一个很多人喜爱的571编号的详细描述。下载每个链接(只下载一次)然后创建一个离线索引。编写一个xkcd工具,使用这些离线索引,打印和命令行输入的检索词相匹配的漫画的URL。
单页漫画, 离线索引好做, 搞个 json 列表就行了, 简单做 571-580 十个索引
练习 4.13: 使用开放电影数据库的JSON服务接口,允许你检索和下载 https://omdbapi.com/ 上电影的名字和对应的海报图像。编写一个poster工具,通过命令行输入的电影名字,下载对应的海报。
CH4.6.文本和HTML模板
一个模板是一个字符串或一个文件,里面包含了一个或多个由双花括号包含的{{action}}
对象。大部分的字符串只是按字面值打印,但是对于actions部分将触发其它的行为
整体看下来这个概念和 Python 中的 f-string 挺像的, 不过比 f-string 更复杂,支持更多高级特性
下面是一个简单的模板字符串:
const templ = `{{.TotalCount}} issues:
{{range .Items}}----------------------------------------
Number: {{.Number}}
User: {{.User.Login}}
Title: {{.Title | printf "%.64s"}}
Age: {{.CreatedAt | daysAgo}} days
{{end}}`
具体使用例如:
package main
import (
"fmt"
"html/template"
"os"
"time"
)
type Issue struct {
Number int
User struct {
Login string
}
Title string
CreatedAt time.Time
}
type IssueList struct {
TotalCount int
Items []Issue
}
// daysAgo calculates the number of days since t.
func daysAgo(t time.Time) int {
return int(time.Since(t).Hours() / 24)
}
func main() {
const templ = `{{.TotalCount}} issues:
{{range .Items}}----------------------------------------
Number: {{.Number}}
User: {{.User.Login}}
Title: {{.Title | printf "%.64s"}}
Age: {{.CreatedAt | daysAgo}} days
{{end}}`
// Sample data
data := IssueList{
TotalCount: 2,
Items: []Issue{
{
Number: 1,
User: struct{ Login string }{Login: "user1"},
Title: "Issue number one with a very long title that should be truncated",
CreatedAt: time.Now().AddDate(0, 0, -10), // 10 days ago
},
{
Number: 2,
User: struct{ Login string }{Login: "user2"},
Title: "Issue number two",
CreatedAt: time.Now().AddDate(0, 0, -5), // 5 days ago
},
},
}
// Create a new template and register the custom function
tmpl := template.Must(template.New("issueList").Funcs(template.FuncMap{
"daysAgo": daysAgo,
}).Parse(templ))
// Execute the template
if err := tmpl.Execute(os.Stdout, data); err != nil {
fmt.Println("Error executing template:", err)
}
}
template.New("issueList")
创建一个新的模板对象,模板的名字为
"issueList"
这个名字在调试时有助于识别模板,但它不必与模板内容相关联
.Funcs(template.FuncMap{ "daysAgo": daysAgo, })
- 使用
.Funcs
方法为模板注册自定义函数。template.FuncMap
是一个映射(map),它将函数名(字符串)映射到实际的函数。 - 在这个例子中,
daysAgo
函数被注册到模板中,函数名为"daysAgo"
,这样就可以在模板中通过{{.CreatedAt | daysAgo}}
来调用这个函数。
template.FuncMap{ "daysAgo": daysAgo, }
- 使用
.Parse(templ)
- 解析模板字符串
templ
,将其转换为可以执行的模板。 templ
是之前定义的模板字符串,它包含了模板的内容和占位符。
- 解析模板字符串
template.Must(...)
template.Must
是一个帮助函数,用于简化模板的错误处理。- 如果在创建或解析模板的过程中发生错误,
template.Must
会导致程序在初始化时立即崩溃(panic),而不是返回一个错误。这样可以确保模板在运行之前是有效的。
tmpl := template.Must(...)