Go程序设计语言练习7.1-7.10

源代码主要参考自 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
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值