源代碼主要參考自 https://www.cnblogs.com/ling-diary/p/10294916.html
結合參考 https://xingdl2007.gitbooks.io/gopl-soljutions/chapter-7-interfaces.html
僅供學習,如有侵權,請聯繫刪除。
練習7.1:使用類似ByteCounter的想法,實現單詞和行的計數器,實現時考慮使用bufio.ScanWords。
API
1.func ScanBytes(data []byte, atEOF bool) (advance int, token []byte, err error)
2.func ScanLines(data []byte, atEOF bool) (advance int, token []byte, err error)
思路
每次讀取一行或一個單詞,統計後,修改開始的位置,重複直至讀完。
代碼如下:
package main
import (
"bufio"
"fmt"
)
type WordsCounter int
func (c *WordsCounter) Write(content []byte) (int, error) {
for start := 0; start < len(content); {
//跳過開頭的space,返回遇到第一個word後下次scan的開始index
//Hello Worlds 調用bufio.ScanWords返回
//6 [Hello的字節slice] nil
advance, _, err := bufio.ScanWords(content[start:], true)
if err != nil {
return 0, err
}
start += advance
(*c)++
}
return int(*c), nil
}
type LinesCounter int
func (c *LinesCounter) Write(content []byte) (int, error) {
for start := 0; start < len(content); {
advance, _, err := bufio.ScanLines(content[start:], true)
if err != nil {
return 0, err
}
start += advance
(*c)++
}
return int(*c), nil
}
func main() {
var wc WordsCounter
wc.Write([]byte("Hello Worlds Test Me"))
fmt.Println(wc) // 4
wc.Write([]byte("append something to the end"))
fmt.Println(wc) // 9
var lc LinesCounter
fmt.Fprintf(&lc, "%s\n%s\n%s\n", "Hello World", "Second Line", "Third Line")
fmt.Println(lc) // 3
fmt.Fprintf(&lc, "%s\n%s\n%s", "第4行", "第5行", "")
fmt.Println(lc) // 5
}
練習 7.2: 寫一個帶有如下函數簽名的函數CountingWriter,傳入一個io.Writer接口類型,返回一個新的Writer類型把原來的Writer封裝在裏面和一個表示寫入新的Writer字節數的int64類型指針
思路
如題目所示。
代碼如下
package main
import (
"fmt"
"io"
"os"
)
type CountWriter struct {
Writer io.Writer
Count int
}
func (cw *CountWriter) Write(content []byte) (int, error) {
n, err := cw.Writer.Write(content)
if err != nil {
return n, err
}
cw.Count += n
return n, nil
}
func CountingWriter(writer io.Writer) (io.Writer, *int) {
cw := CountWriter{
Writer: writer,
}
return &cw, &(cw.Count)
}
func main() {
cw, counter := CountingWriter(os.Stdout)
fmt.Fprintf(cw, "%s", "Print somethind to the screen...")
fmt.Println(*counter)
cw.Write([]byte("Append soething..."))
fmt.Println(*counter)
}
練習7.3: 爲在gopl.io/ch4/treesort (§4.4)的*tree類型實現一個String方法去展示tree類型的值序列。
代碼
package main
import (
"fmt"
"math/rand"
)
type tree struct {
value int
left, right *tree
}
func (t *tree) String() string {
res := ""
if t == nil {
return res
}
res += t.left.String() // 左樹
res = fmt.Sprintf("%s %d", res, t.value) // 左樹 空格 當前值 空格 右樹
res += t.right.String()
return res
}
func buildTree(data []int) *tree {
var root = new(tree)
for _, v := range data {
root = add(root, v)
}
return root
}
func add(t *tree, e int) *tree {
if t == nil {
t = new(tree)
t.value = e
return t
}
if e < t.value {
t.left = add(t.left, e)
} else {
t.right = add(t.right, e)
}
return t
}
func main() {
data := make([]int, 50)
for i := range data {
data[i] = rand.Int() % 50
}
root := buildTree(data)
fmt.Println(root)
//空指針
fmt.Println(new(tree))
//只有根節點
root = new(tree)
root.value = 100
fmt.Println(root)
//沒有右子樹
data = []int{5, 4, 3, 2, 1}
root = buildTree(data)
fmt.Println(root)
//沒有左子樹
data = []int{1, 3, 2, 4, 5}
root = buildTree(data)
fmt.Println(root)
}
練習 7.4: strings.NewReader函數通過讀取一個string參數返回一個滿足io.Reader接口類型的值(和其它值)。實現一個簡單版本的NewReader,並用它來構造一個接收字符串輸入的HTML解析器(§5.2)
接口
func NewReader(s string) *Reader
type Reader interface { Read(p []byte) (n int, err error) }
func Copy(dst Writer, src Reader) (written int64, err error)
代碼如下
HTML解析器沒做。
此處代碼結合了 https://xingdl2007.gitbooks.io/gopl-soljutions/chapter-7-interfaces.html
主要判斷b長度爲0,且考慮讀完返回EOF,修改測試讀取邏輯。
package main
import (
"fmt"
"io"
)
type StringReader struct {
data string
current int
}
func (sr *StringReader) Read(b []byte) (n int, err error) {
if len(b) == 0 { // 不需要讀入
return 0, nil
}
// copy() guarantee copy min(len(b),len(sr.data[sr.current:])) bytes
n = copy(b, sr.data[sr.current:])
if sr.current += n; sr.current >= len(sr.data) { // 已讀完
err = io.EOF
}
return
}
func NewReader(in string) *StringReader {
sr := new(StringReader)
sr.data = in
return sr
}
func main() {
str := "Hello World"
sr := NewReader(str)
data := make([]byte, 10) // 每次最多讀10個byte
n, err := sr.Read(data[:0]) // 初始化
for err == nil{
n, err = sr.Read(data)
fmt.Println(n, string(data[0:n])) // 重新取切片,因爲最後一次結果data[n:]含有上一輪的結果
}
//output:
// 10 Hello Worl
// 1 d
}
7.5 io包裏面的LimitReader函數接收一個io.Reader接口類型的r和字節數n,並且返回另一個從r中讀取字節但是當讀完n個字節後就表示讀到文件結束的Reader。實現這個 LimitReader函數:
代碼如下:
稍微改動,維護一個當前剩餘的長度。
package main
import (
"fmt"
"io"
"os"
)
type LimitedReader struct {
Reader io.Reader
Limit int
}
func (r *LimitedReader) Read(b []byte) (n int, err error) {
if r.Limit <= 0 {
return 0, io.EOF
}
if len(b) > r.Limit {
b = b[:r.Limit]
}
n, err = r.Reader.Read(b)
r.Limit -= n
return
}
func LimitReader(r io.Reader, limit int) io.Reader {
return &LimitedReader{
Reader: r,
Limit: limit,
}
}
func main() {
file, err := os.Open("limit.txt") // 1234567890
if err != nil {
panic(err)
}
defer file.Close()
lr := LimitReader(file, 5)
buf := make([]byte, 10)
n, err := lr.Read(buf)
if err != nil {
panic(err)
}
fmt.Println(n, buf, string(buf)) // 5 [49 50 51 52 53 0 0 0 0 0] 12345
}
7.6 對tempFlag加入支持開爾文溫度。
func KToC(k Kelvin) Celsius { return Celsius(k-273.15) }
仿着寫就ok了。
7.7 解釋爲什麼幫助信息在它的默認值是20.0沒有包含°C的情況下輸出了°C。
func (c Celsius) String() string { return fmt.Sprintf("%g°C", c) }
Celsius實現了String方法,輸出時會在數字後自動加上°C
注:
關於flag的講解可以參考 https://blog.51cto.com/steed/2363801
調用 Var 方法是會把 *celsiusFlag 實參賦給 flag.Value 形參,編譯器會在此時檢查 *celsiusFlag 類型是否有 flag.Value 所必需的方法
func CelsiusFlag(name string, value Celsius, usage string) *Celsius {
f := celsiusFlag{value}
flag.CommandLine.Var(&f, name, usage)
return &f.Celsius
}
練習7.8 多重排序表格插件
以下代碼來自鏈接2,補充了輸入輸出樣例。鏈接2模擬了點選操作,不過鏈接1將排序方法獨立擴展性要好一點。建議都參看一下。
package main
import (
"fmt"
"os"
"sort"
"text/tabwriter"
"time"
)
type multier struct {
t []*Track
primary string
secondary string
third string
}
func (x *multier) Len() int { return len(x.t) }
func (x *multier) Swap(i, j int) { x.t[i], x.t[j] = x.t[j], x.t[i] }
func (x *multier) Less(i, j int) bool {
key := x.primary
for k := 0; k < 3; k++ {
switch key {
case "Title":
if x.t[i].Title != x.t[j].Title {
return x.t[i].Title < x.t[j].Title
}
case "Year":
if x.t[i].Year != x.t[j].Year {
return x.t[i].Year < x.t[j].Year
}
case "Length":
if x.t[i].Length != x.t[j].Length {
return x.t[i].Length < x.t[j].Length
}
}
if k == 0 {
key = x.secondary
} else if k == 1 {
key = x.third
}
}
return false
}
// 更新排序鍵優先值
func setPrimary(x *multier, p string) {
x.primary, x.secondary, x.third = p, x.primary, x.secondary
}
// if x is *multiple type, then update ordering keys
func SetPrimary(x sort.Interface, p string) {
if x, ok := x.(*multier); ok {
setPrimary(x, p)
}
}
// return a new multier
func NewMultier(t []*Track, p, s, th string) sort.Interface {
return &multier{
t: t,
primary: p,
secondary: s,
third: th,
}
}
// 下面是輸出測試
//!+main
type Track struct {
Title string
Artist string
Album string
Year int
Length time.Duration
}
var tracks = []*Track{
{"Go", "Delilah", "From the Roots Up", 2012, length("3m38s")},
{"Go", "Moby", "Moby", 1992, length("3m37s")},
{"Go Ahead", "Alicia Keys", "As I Am", 2007, length("4m36s")},
{"Ready 2 Go", "Martin Solveig", "Smash", 2011, length("4m24s")},
}
func length(s string) time.Duration {
d, err := time.ParseDuration(s)
if err != nil {
panic(s)
}
return d
}
func printTracks(tracks []*Track) {
const format = "%v\t%v\t%v\t%v\t%v\t\n"
tw := new(tabwriter.Writer).Init(os.Stdout, 0, 8, 2, ' ', 0)
fmt.Fprintf(tw, format, "Title", "Artist", "Album", "Year", "Length")
fmt.Fprintf(tw, format, "-----", "------", "-----", "----", "------")
for _, t := range tracks {
fmt.Fprintf(tw, format, t.Title, t.Artist, t.Album, t.Year, t.Length)
}
tw.Flush() // calculate column widths and print table
}
func main() {
printTracks(tracks)
multier := NewMultier(tracks, "Title", "Year", "Length")
fmt.Println("\nCustom by Title, Year, Length:")
sort.Sort(multier)
printTracks(tracks)
fmt.Println("\nCustom by Year, Title, Length:")
multier = NewMultier(tracks, "Year", "Title", "Length")
sort.Sort(multier)
printTracks(tracks)
fmt.Println("\nCustom select Length")
SetPrimary(multier, "Length")
sort.Sort(multier)
printTracks(tracks)
}
7.9 使用html/template包 (§4.6) 替代printTracks將tracks展示成一個HTML表格。將這 個解決方案用在前一個練習中,讓每次點擊一個列的頭部產生一個HTTP請求來排序這個表 格
代碼來自鏈接2
// ref: https://stackoverflow.com/questions/25824095/order-by-clicking-table-header
var trackTable = template.Must(template.New("Track").Parse(`
<h1> Tracks </h1>
<table>
<tr style='text-align: left'>
<th οnclick="submitform('Title')">Title
<form action="" name="Title" method="post">
<input type="hidden" name="orderby" value="Title"/>
</form>
</th>
<th>Artist
<form action="" name="Artist" method="post">
<input type="hidden" name="orderby" value="Artist"/>
</form>
</th>
<th>Album
<form action="" name="Album" method="post">
<input type="hidden" name="orderby" value="Album"/>
</form>
</th>
<th οnclick="submitform('Year')">Year
<form action="" name="Year" method="post">
<input type="hidden" name="orderby" value="Year"/>
</form>
</th>
<th οnclick="submitform('Length')">Length
<form action="" name="Length" method="post">
<input type="hidden" name="orderby" value="Length"/>
</form>
</th>
</tr>
{{range .T}}
<tr>
<td>{{.Title}}</td>
<td>{{.Artist}}</td>
<td>{{.Album}}</td>
<td>{{.Year}}</td>
<td>{{.Length}}</td>
</tr>
{{end}}
</table>
<script>
function submitform(formname) {
document[formname].submit();
}
</script>
`))
type multier struct {
T []*Track // exported
primary string
secondary string
third string
}
//!+printTracks
func printTracks(w io.Writer, x sort.Interface) {
if x, ok := x.(*multier); ok {
trackTable.Execute(w, x)
}
}
func main() {
// default sort by "Title"
multi := NewMultier(tracks, "Title", "", "")
sort.Sort(multi)
// start a simple server
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
fmt.Printf("ParseForm: %v\n", err)
}
for k, v := range r.Form {
if k == "orderby" {
SetPrimary(multi, v[0])
}
}
sort.Sort(multi)
printTracks(w, multi)
})
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
練習7.10 判斷迴文
鏈接2代碼
func IsPalindrome(s sort.Interface) bool {
i, j := 0, s.Len()-1
for j > i {
// Less() only
if !s.Less(i, j) && !s.Less(j, i) {
i++
j--
} else {
return false
}
}
return true
}