go练习题
文章目录
- go练习题
- 一、 复合类型习题
- 1. 重写reverse函数,使用数组指针代替slice。
- 2. 编写一个rotate函数,通过一次循环完成旋转。
- 1. 编写一个非递归版本的comma函数,使用bytes.Buffer代替字符串链接操作
- 2. 编写一个函数,判断两个字符串是否是相互打乱的,也就是说它们有着相同的字符,但是对应不同的顺序。
- 3. 写一个函数在原地完成消除[]string中相邻重复的字符串的操作
- 4. 修改charcount程序,使用unicode.IsLetter等相关的函数,统计字母、数字等Unicode中不同的字符类别
- 5. 编写一个程序wordfreq程序,报告输入文本中每个单词出现的频率。在第一次调用Scan前先调用input.Split(bufio.ScanWords)函数,这样可以按单词而不是按行输入。
- 6.练习 7.8: 很多图形界面提供了一个有状态的多重排序表格插件:主要的排序键是最近一次点击过列头的列,第二个排序键是第二最近点击过列头的列,等等。定义一个sort.Interface的实现用在这样的表格中。比较这个实现方式和重复使用sort.Stable来排序的方式。
- 7. 增加额外的handler让客户端可以创建,读取,更新和删除数据库记录。例如,一个形如 /update?item=socks&price=6 的请求会更新库存清单里一个货品的价格并且当这个货品不存在或价格无效时返回一个错误值。(注意:这个修改会引入变量同时更新的问题)
- 8. 练习 8.1: 修改clock2来支持传入参数作为端口号,然后写一个clockwall的程序,这个程序可以同时与多个clock服务器通信,从多个服务器中读取时间,并且在一个表格中一次显示所有服务器传回的结果,类似于你在某些办公室里看到的时钟墙。如果你有地理学上分布式的服务器可以用的话,让这些服务器跑在不同的机器上面;或者在同一台机器上跑多个不同的实例,这些实例监听不同的端口,假装自己在不同的时区。像下面这样:
- 9. 写一个du工具,每隔一段时间将root目录下的目录大小计算并显示出来。
一、 复合类型习题
1. 重写reverse函数,使用数组指针代替slice。
func main(){
ss := [6]int{0,1,2,3,4,5}
reverse2(&ss)
}
func reverse2(ss *[6]int) {
for i, j := 0, len(ss)-1; i < j; i, j = i+1, j-1 {
ss[i], ss[j] = ss[j], ss[i]
}
}
也可以使用以下方法实现数组内元素反转
func main(){
ss := [6]int{0,1,2,3,4,5}
var s = make([]int,0)
copy(s,ss[:])
}
2. 编写一个rotate函数,通过一次循环完成旋转。
func rotate(s []int, k int, direction string) []int {
res := make([]int, len(s))
if direction == "left" {
for i := 0; i < len(s); i++ {
index := i + k
if index >= len(s) {
index = index - len(s)
}
res[i] = s[index]
}
}
return res
}
1. 编写一个非递归版本的comma函数,使用bytes.Buffer代替字符串链接操作
设计思路
使用bytes.Buffer实现切相同数量的子串为一组。
代码实现
- 方法一
func comma3(s string, seq int) string {
var (
buf bytes.Buffer
tem []byte
str = []byte(s)
)
// 区分小数点
comm := bytes.IndexByte(str, '.')
if comm != -1 {
tem = str[comm:]
str = str[:comm]
}
if len(str) <= seq {
return s
}
if strings.HasPrefix(s, "-") || strings.HasPrefix(s, "+") {
buf.WriteByte(str[0])
str = str[1:]
}
for n := len(str); n > 0; n -= seq {
m := n % seq
if m > 0 {
buf.Write(str[:m])
str = str[m:n]
n -= m
}
buf.WriteByte(',')
fmt.Fprintf(&buf, "%s", str[:seq])
str = str[seq:]
}
buf.Write(tem)
return buf.String()
}
- 方法二
func comma(s string) string {
var buf1, buf2 bytes.Buffer
lenS := len(s)
if lenS <= 3 {
return s
}
count := 0
for i := lenS - 1; i >= 0; i-- {
count++
buf1.WriteByte(s[i])
if count % 3 == 0 {
buf1.WriteString(",")
}
}
n := buf1.String()
for j := len(n) - 1; j >= 0; j-- {
buf2.WriteByte(n[j])
}
return buf2.String()
}
2. 编写一个函数,判断两个字符串是否是相互打乱的,也就是说它们有着相同的字符,但是对应不同的顺序。
设计思路
首先定义一个flag,使用内置函数bytes.IndexByte
先取出该字节所另一字串的下标,再判断字串1的下标和字串2的下标是否相等。如果不相等则flag+1,直到flag和s1或s2的长度相等为止。
代码实现
func isDisorder(str1, str2 string) bool {
if len(str1) != len(str2) {
return false
}
var (
s1 = []byte(str1)
s2 = []byte(str2)
flag int
)
for i := 0; i < len(s1); i++ {
index := bytes.IndexByte(s2, s1[i])
if index != i && bytes.Contains(s2, []byte{s1[i]}) {
flag++
}
}
if flag == len(str1) {
return true
}
return false
}
3. 写一个函数在原地完成消除[]string中相邻重复的字符串的操作
代码思路
定义一个容量和原切片相等的变量newSlice,遍历原切片,如果当前元素和后一个元素的值不相等,那么就把当前元素append到newSlice中。完成后,将原切片的最后一个元素再加入到newSlice中。
代码实现
func distincStr(s []string) []string {
var (
i int
index int
)
newSlice := s[:0]
for _, v := range s {
index = i + 1
if index >= len(s) {
break
}
if v != s[index] {
newSlice = append(newSlice, v)
}
i++
}
newSlice = append(newSlice, s[len(s)-1])
return newSlice
}
4. 修改charcount程序,使用unicode.IsLetter等相关的函数,统计字母、数字等Unicode中不同的字符类别
代码实现
func charCount2(data *bytes.Reader) {
letters := make(map[rune]int)
nums := make(map[rune]int)
in := bufio.NewReader(data)
for {
r, _, err := in.ReadRune()
if err == io.EOF {
break
}
if err != nil {
fmt.Fprintf(os.Stderr, "charcount:%v\n", err)
os.Exit(1)
}
if unicode.IsLetter(r) {
letters[r]++
}
if unicode.IsNumber(r) {
nums[r]++
}
}
fmt.Print("\nletters\tcount\n")
for letter, num := range letters {
fmt.Printf("%q\t%d", letter, num)
}
fmt.Print("\nnumbers\tcount\n")
for number, num := range nums {
fmt.Printf("%q\t%d\n", number, num)
}
}
func main(){
b, err := ioutil.ReadFile("./map.txt")
if err != nil {
fmt.Println(err.Error())
}
rd := bytes.NewReader(b)
charCount2(rd)
}
5. 编写一个程序wordfreq程序,报告输入文本中每个单词出现的频率。在第一次调用Scan前先调用input.Split(bufio.ScanWords)函数,这样可以按单词而不是按行输入。
代码实现
func main(){
f, err := os.ReadFile("./map.txt")
if err != nil {
fmt.Println(err.Error())
}
rd := bytes.NewReader(f)
wordC := wordfreq(rd)
for text, count := range wordC {
fmt.Printf("%q\t%d\n", text, count)
}
}
func wordfreq(data *bytes.Reader) map[string]int {
wordCount := make(map[string]int)
input := bufio.NewScanner(data)
input.Split(bufio.ScanWords)
for input.Scan() {
//fmt.Println(input.Text())
wordCount[input.Text()]++
}
return wordCount
}
第4题和第5题通用文件map.txt
可以写任意内容,包括:字母,数字,符号,中文。
6.练习 7.8: 很多图形界面提供了一个有状态的多重排序表格插件:主要的排序键是最近一次点击过列头的列,第二个排序键是第二最近点击过列头的列,等等。定义一个sort.Interface的实现用在这样的表格中。比较这个实现方式和重复使用sort.Stable来排序的方式。
package main
import (
"errors"
"fmt"
"math/rand"
"os"
"sort"
"strings"
"text/tabwriter"
"time"
)
// 练习 7.8: 很多图形界面提供了一个有状态的多重排序表格插件:
//主要的排序键是最近一次点击过列头的列,第二个排序键是第二最近点击过列头的列,等等。
//定义一个sort.Interface的实现用在这样的表格中。比较这个实现方式和重复使用sort.Stable来排序的方式。
// LogTable 日志表格
type LogTable struct {
Sequence int
Mode string
Level string
Comment string
Time time.Time
}
// PrintTable 打印表格
func PrintTable(logtable []*LogTable) {
const format = "%v\t%v\t%v\t%v\t%v\t\n"
lt := new(tabwriter.Writer)
lt = lt.Init(os.Stdout, 0, 8, 2, ' ', 0)
fmt.Fprintf(lt, format, "序号", "日志模式", "日志等级", "日志内容", "记录时间")
fmt.Fprintf(lt, format, "-----", "------", "-----", "----", "------")
for _, v := range logtable {
fmt.Fprintf(lt, format, v.Sequence, v.Mode, v.Level, v.Comment, v.Time)
}
lt.Flush()
}
// UniversalSort 按任意键排序
type UniversalSort struct {
log []*LogTable
means map[string]int
less func(i, j *LogTable) bool
}
func (u UniversalSort) Len() int { return len(u.log) }
func (u UniversalSort) Less(i, j int) bool { return u.less(u.log[i], u.log[j]) }
func (u UniversalSort) Swap(i, j int) { u.log[i], u.log[j] = u.log[j], u.log[i] }
var tables = []*LogTable{
{4, "参数模块日志", "ERROR", "[ERROR]:ssh from to 11.11.1.1", randate()},
{1, "系统模块日志", "INFO", "[INFO]:ssh from to 11.11.1.1", randate()},
{5, "参数模块日志", "WARN", "[WARN]:mysql_host", randate()},
{6, "系统模块日志", "INFO", "[INFO]:ssh from to 11.11.1.1", randate()},
{8, "参数模块日志", "DEBUG", "[DEBUG]:ssh from to 11.11.1.1", randate()},
{7, "系统模块日志", "INFO", "[INFO]:ssh from to 11.11.1.1", randate()},
{2, "参数模块日志", "WARN", "[WARN]: no Invalid param - mysql-port", randate()},
{3, "系统模块日志", "INFO", "[INFO]:ssh from to 11.11.1.1", randate()},
{9, "参数模块日志", "INFO", "[INFO]:ssh from to 11.11.1.1", randate()},
}
func bySeqence() *UniversalSort {
return &UniversalSort{
log: tables,
less: func(i, j *LogTable) bool {
return i.Sequence < j.Sequence
},
}
}
func byMode() *UniversalSort {
return &UniversalSort{
log: tables, less: func(i, j *LogTable) bool {
return i.Mode < j.Mode
},
}
}
func byLevel() *UniversalSort {
return &UniversalSort{
log: tables, less: func(i, j *LogTable) bool {
return i.Level < j.Level
},
}
}
func byComment() *UniversalSort {
return &UniversalSort{
log: tables, less: func(i, j *LogTable) bool {
return i.Comment < j.Comment
},
}
}
func byTime() *UniversalSort {
return &UniversalSort{
log: tables, less: func(i, j *LogTable) bool {
return i.Time.Before(j.Time)
},
}
}
// choiceSort 实现了是基于哪种规则做的正序或倒序
func (u *UniversalSort) bySort(means map[string]int) (*UniversalSort, error) {
var nu *UniversalSort
for m, c := range means {
m = strings.ToLower(m)
if c < 1 {
return nu, errors.New("no invaild count")
}
switch m {
case "mode":
nu = byMode()
case "level":
nu = byLevel()
case "comment":
nu = byComment()
case "time":
nu = byTime()
default:
nu = bySeqence()
}
nu.means = u.means
if c%2 != 0 {
sort.Sort(nu)
}
sort.Sort(sort.Reverse(nu))
}
return nu, nil
}
func randate() time.Time {
min := time.Date(1970, 1, 0, 0, 0, 0, 0, time.UTC).Unix()
max := time.Date(2070, 1, 0, 0, 0, 0, 0, time.UTC).Unix()
delta := max - min
sec := rand.Int63n(delta) + min
return time.Unix(sec, 0)
}
func main() {
PrintTable(tables)
fmt.Println("=======1.seq sort========")
u := new(UniversalSort)
var m = make(map[string]int, 0)
m["time"] = 1
if _, err := u.bySort(m); err != nil {
fmt.Println(err.Error())
}
PrintTable(tables)
}
7. 增加额外的handler让客户端可以创建,读取,更新和删除数据库记录。例如,一个形如 /update?item=socks&price=6 的请求会更新库存清单里一个货品的价格并且当这个货品不存在或价格无效时返回一个错误值。(注意:这个修改会引入变量同时更新的问题)
package main
import (
"fmt"
"html/template"
"net/http"
"strconv"
"sync"
)
type dollars float32
var mu sync.Mutex
func (d dollars) String() string { return fmt.Sprintf("$%.2f", d) }
type database map[string]dollars
func (db database) listHttp() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
// 解析模板
t, err := template.ParseFiles("./table_temp.html")
if err != nil {
fmt.Errorf("parse template error:%v\n", err.Error())
}
if err := t.Execute(w, db); err != nil {
fmt.Fprintf(w, err.Error())
}
var rows = `<p>{{ .A }} | {{ .B }}</p>`
rowsTemp := template.Must(template.New("rows").Parse(rows))
type row struct {
A string
B dollars
}
for custom, price := range db {
d := row{A: custom, B: price}
if err := rowsTemp.Execute(w, d); err != nil {
fmt.Errorf("prase rows template error :%v\n", err.Error())
}
}
}
}
func (db database) priceHttp() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
item := r.URL.Query().Get("item")
price, ok := db[item]
if !ok {
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "no such item:%q\n", item)
return
}
fmt.Fprintf(w, "%s\n", price)
}
}
//createHttp 增加一件商品
func (db database) createHttp() func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
item := r.URL.Query().Get("item")
price := r.URL.Query().Get("price")
p, err := strconv.ParseFloat(price, 64)
if err != nil {
fmt.Fprintf(w, "%v\n", err.Error())
return
}
db[item] = dollars(p)
if _, ok := db[item]; ok {
fmt.Fprintf(w, "%s:$%.2f 创建成功\n", item, db[item])
}
}
}
//updateHttp 更新一件商品
func (db database) updateHttp() func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
item := r.URL.Query().Get("item")
p, err := strconv.ParseFloat(r.URL.Query().Get("price"), 64)
if err != nil {
fmt.Fprintf(w, "%v\n", err.Error())
}
if v, ok := db[item]; ok {
if dollars(p) == v {
fmt.Fprintf(w, "%s:$%.2f\nf无需更改", item, dollars(p))
return
}
mu.Lock()
db[item] = dollars(p)
mu.Unlock()
} else {
fmt.Fprintf(w, "%s不存在\n", item)
return
}
fmt.Fprintf(w, "%s:$%.2f 修改成功\n", item, db[item])
}
}
// deleteHttp 删除一件商品
func (db database) deleteHttp() func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
item := r.URL.Query().Get("item")
if _, ok := db[item]; !ok {
fmt.Fprintf(w, "%s 不存在\n", item)
return
}
mu.Lock()
delete(db, item)
mu.Unlock()
fmt.Fprintf(w, "%s 商品已删除\n", item)
}
}
func main() {
db := database{"coat": 20.34, "clothes": 350.89}
http.HandleFunc("/list", db.listHttp())
http.HandleFunc("/price", db.priceHttp())
http.HandleFunc("/create", db.createHttp())
http.HandleFunc("/update", db.updateHttp())
http.HandleFunc("/delete", db.deleteHttp())
if err := http.ListenAndServe(":8090", nil); err != nil {
fmt.Println(err.Error())
}
}
8. 练习 8.1: 修改clock2来支持传入参数作为端口号,然后写一个clockwall的程序,这个程序可以同时与多个clock服务器通信,从多个服务器中读取时间,并且在一个表格中一次显示所有服务器传回的结果,类似于你在某些办公室里看到的时钟墙。如果你有地理学上分布式的服务器可以用的话,让这些服务器跑在不同的机器上面;或者在同一台机器上跑多个不同的实例,这些实例监听不同的端口,假装自己在不同的时区。像下面这样:
$ TZ=US/Eastern ./clock2 -port 8010 &
$ TZ=Asia/Tokyo ./clock2 -port 8020 &
$ TZ=Europe/London ./clock2 -port 8030 &
$ clockwall NewYork=localhost:8010 Tokyo=localhost:8020 London=localhost:8030
设计思路
首先要搞清楚服务端和客户端,由题目可知,服务端是多个clock,客户端是clockwall,clockwall会监听所有clock的服务端口,将每个clock在网络上输出的内容获取到本地并输出。
代码实现
代码目录结构
服务端代码clock/clock.go
package main
import (
"flag"
"fmt"
"io"
"log"
"net"
"time"
)
// ./clock -host localhost -port 8000 &
func main() {
ip := flag.String("host", "localhost", "listen host's ip")
p := flag.Int("port", 8000, "listen host's port")
flag.Parse()
go Listen(*ip, *p)
for {
time.Sleep(1 * time.Second)
}
}
func Listen(ip string, port int) {
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", ip, port))
if err != nil {
log.Fatal(err)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Fatal(err)
}
go HandleConn(ip, port, conn)
}
}
func HandleConn(ip string, port int, n net.Conn) {
defer n.Close()
var (
zone string
t = time.Now()
)
switch port {
case 8000:
zone = "America/New_York"
case 8001:
zone = "Asia/Shanghai"
case 8002:
zone = "Asia/Harbin"
default:
log.Fatal("Not time zone")
}
local, _ := time.LoadLocation(zone)
writeInfo := fmt.Sprintf("%v\t%v\t%v\t%v\t\n", zone, ip, port, t.In(local).Format("2006-01-02 15:04:05"))
if _, err := io.WriteString(n, writeInfo); err != nil {
log.Fatal(err)
}package main
import (
"flag"
"fmt"
"io"
"log"
"net"
"os"
"strconv"
"strings"
"time"
)
func main() {
var (
zoneArr []string
addressArr []string
i int = 0
)
for _, param := range os.Args[1:] {
if strings.HasPrefix(param, "-") {
zoneArr = append(zoneArr, strings.Split(param, "-")[1])
} else {
addressArr = append(addressArr, param)
}
}
for _, zone := range zoneArr {
flag.StringVar(&zone, zone, fmt.Sprintf("localhost:800%d", i), "please input ip:port")
i++
}
flag.Usage = func() {
fmt.Println("Usage: practise [-h] [NewYork=localhost:8000] [Tokyo=localhost:8001] [London=localhost:8002]")
flag.PrintDefaults()
}
flag.Parse()
fmt.Fprintf(os.Stdout, fmt.Sprintf("%v\t%10v\t%10v\t%10v\t\n", "ZONE", "IP", "PORT", "time"))
fmt.Fprintf(os.Stdout, fmt.Sprintf("%v\t%10v\t%10v\t%10v\t\n", "-----", "------", "-----", "-----"))
for {
for _, param := range addressArr {
ip := strings.Split(param, ":")[0]
port := strings.Split(param, ":")[1]
p, _ := strconv.Atoi(port)
go dial(ip, p)
}
time.Sleep(3 * time.Second)
}
}
func dial(host string, port int) {
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", host, port))
if err != nil {
log.Fatal(err)
}
defer conn.Close()
copy(os.Stdout, conn)
}
func copy(dst io.Writer, src io.Reader) {
if _, err := io.Copy(dst, src); err != nil {
log.Fatal(err)
}
}
}
客户端代码 clock/clockwall.go
package main
import (
"flag"
"fmt"
"io"
"log"
"net"
"os"
"strconv"
"strings"
"time"
)
func main() {
var (
zoneArr []string
addressArr []string
i int = 0
)
for _, param := range os.Args[1:] {
if strings.HasPrefix(param, "-") {
zoneArr = append(zoneArr, strings.Split(param, "-")[1])
} else {
addressArr = append(addressArr, param)
}
}
for _, zone := range zoneArr {
flag.StringVar(&zone, zone, fmt.Sprintf("localhost:800%d", i), "please input ip:port")
i++
}
flag.Usage = func() {
fmt.Println("Usage: practise [-h] [NewYork=localhost:8000] [Tokyo=localhost:8001] [London=localhost:8002]")
flag.PrintDefaults()
}
flag.Parse()
fmt.Fprintf(os.Stdout, fmt.Sprintf("%v\t%10v\t%10v\t%10v\t\n", "ZONE", "IP", "PORT", "time"))
fmt.Fprintf(os.Stdout, fmt.Sprintf("%v\t%10v\t%10v\t%10v\t\n", "-----", "------", "-----", "-----"))
for {
for _, param := range addressArr {
ip := strings.Split(param, ":")[0]
port := strings.Split(param, ":")[1]
p, _ := strconv.Atoi(port)
go dial(ip, p)
}
time.Sleep(3 * time.Second)
}
}
func dial(host string, port int) {
conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", host, port))
if err != nil {
log.Fatal(err)
}
defer conn.Close()
copy(os.Stdout, conn)
}
func copy(dst io.Writer, src io.Reader) {
if _, err := io.Copy(dst, src); err != nil {
log.Fatal(err)
}
}
使用方式
- 服务端
$ cd clock
$ go build
$ ./clock -host localhost -port 8001 &
- 客户端
$ cd clockwall
$ go build
$ ./clockwall -NewYork localhost:8000 -ShangHai localhost:8001
9. 写一个du工具,每隔一段时间将root目录下的目录大小计算并显示出来。
package main
import (
"flag"
"sync"
"time"
)
// 写一个du工具,每隔一段时间将root目录下的目录大小计算并显示出来。
var realTime = flag.Bool("r", false, "real-time output message")
func dirents(dir string) []os.FileInfo {
entries,err := ioutil.ReadDir(dir)
if err != nil {
fmt.Fprintf(os.Stderr, "du1:%v\n",err )
return nil
}
return entries
}
func walkDir(dir string,size chan<-int64,wg *sync.WaitGroup){
defer wg.Done()
for _,d := range dirents(dir){
if d.IsDir() {
wg.Add(1)
subdir := filepath.Join(dir,d.Name())
go walkDir(subdir,size,wg)
} else {
size <- d.Size()
}
}
}
func printDiskUsage(nfiles, nbytes int64) {
fmt.Printf
func printDiskUsage(nfiles, nbytes int64) {
fmt.Printf("%d files %.1f GB\n", nfiles, float64(nbytes)/1e9)
}("%d files %.1f GB\n", nfiles, float64(nbytes)/1e9)
}
func main() {
flag.Parse()
root := flag.Args()
if len(root) == 0 {
root = []string{"/root"}
}
var (
ticker *time.Ticker
wg sync.WaitGroup
fileSize = make(chan int64)
nfiles, nbytes int64
)
if *realTime {
ticker = time.NewTicker(10 * time.Second)
}
for _,r:= range root {
wg.Add(1)
go walkDir(r, fileSize,&wg)
}
go func () {
wg.Wait()
close(fileSize)
}()
for {
Loop:
select {
case <- ticker.C:
printDiskUsage(nfiles,nbytes)
break Loop
case e,ok := <-fileSize:
if !ok{
break Loop
}
nfiles++
nbytes += e
}
}
}
go build
./clock -r /root