go语言圣经练习题

前言

本练习所使用的go version1.14.4

$ go version
go version go1.14.4 darwin/amd64

阅读的文档来源于go语言圣经

该习题解题思路只代表我个人的解题思路,如果有更好的解题思路请大家留言。谢谢。

入门

命令行参数

练习 1.1

修改echo程序,使其能够打印os.Args[0],即被执行命令本身的名字。

package main

import (
    "fmt"
    "os"
)

func main() {
    var s, step string
    for _, v := range os.Args {
        s += step + v
        step = " "
    }
    fmt.Println(s)
}

结果:

$ go run p-1-1.go hello world 世界
/var/folders/38/54w98v_57db2rvfyrzvy_0380000gn/T/go-build172726333/b001/exe/p-1-1 hello world 世界

练习1.2

修改echo程序,使其打印每个参数的索引和值,每个一行。

package main

import (
    "fmt"
    "os"
)

func main() {
    for i, v := range os.Args[1:] {
        fmt.Println(i, v)
    }
}

结果:

$  go run main.go hello world test 世界
0 hello
1 world
2 test
3 世界

练习1.3

做实验测量潜在低效的版本和使用了strings.Join的版本的运行时间差异。

package main

import (
    "fmt"
    "os"
    "strings"
    "time"
)

func main() {
    var s, step string
    start := time.Now().UnixNano()
    for i := 1; i < len(os.Args); i++ {
        s += step + os.Args[i]
        step = " "
    }
    fmt.Println(s)
    fmt.Println("耗时:", time.Now().UnixNano()-start, "纳秒")

    start = time.Now().UnixNano()
    fmt.Println(strings.Join(os.Args[1:], " "))
    fmt.Println("耗时:", time.Now().UnixNano()-start, "纳秒")

}


结果

$ go run main.go test hello world 中国 世界 ezreal rao world test
test hello world 中国 世界 ezreal rao world test
耗时: 38000 纳秒
test hello world 中国 世界 ezreal rao world test
耗时: 3000 纳秒

查找重复的行

练习1.4

修改dup2,出现重复的行时打印文件名称。

dup2内容如下

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    counts := make(map[string]int)
    files := os.Args[1:]
    if len(files) == 0 {
        countLines(os.Stdin, counts)
    } else {
        for _, arg := range files {
            file, err := os.Open(arg)
            if err != nil {
                fmt.Fprintf(os.Stderr, "%v", err)
            }
            countLines(file, counts)
            file.Close()
        }
    }
    fmt.Println(counts)
    for line, n := range counts {
        if n > 1 {
            fmt.Printf("%d\t%s\n", n, line)
        }
    }
}

func countLines(f *os.File, counts map[string]int) {
    input := bufio.NewScanner(f)
    for input.Scan() {
        counts[input.Text()]++
    }
}

修改后的内容:

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    counts := make(map[string]map[string]int)
    files := os.Args[1:]
    if len(files) == 0 {
        counts["os.Stdin"] = make(map[string]int)
        countLines(os.Stdin, counts, "os.Stdin")
    } else {
        for _, arg := range files {
            file, err := os.Open(arg)
            if err != nil {
                fmt.Fprintf(os.Stderr, "%v", err)
            }
            counts[arg] = make(map[string]int)
            countLines(file, counts, arg)
            file.Close()
        }
    }

    for line, value := range counts {
        for _, n := range value {
            if n > 1 {
                fmt.Printf("%d\t%s\n", n, line)
                break
            }
        }
    }
}

func countLines(f *os.File, counts map[string]map[string]int, filename string) {
    input := bufio.NewScanner(f)
    for input.Scan() {
        counts[filename][input.Text()]++
    }
}

结果:

$ go run main.go ./a.txt
5       ./a.txt

a.txt中的内容为:

hello world
hello world
hello world
hello world
hello world

GIF动画

练习1.5

修改前面的Lissajous程序里的调色板,由黑色改为绿色。我们可以用color.RGBA{0xRR, 0xGG, 0xBB, 0xff}来得到#RRGGBB这个色值,三个十六进制的字符串分别代表红、绿、蓝像素。

package main

import (
    "image"
    "image/color"
    "image/gif"
    "io"
    "math"
    "math/rand"
    "os"
    "time"
)

var palette = []color.Color{color.White, color.RGBA{0, 0xFF, 0, 0xFF}}

const (
    whiteIndex = 0
    blackIndex = 1
)

func main() {
    rand.Seed(time.Now().UTC().UnixNano())
    lissajous(os.Stdout)
}

func lissajous(out io.Writer) {
    const (
        cycles  = 5     //完成x振子的转数
        res     = 0.001 //角分辨率
        size    = 100   //画布覆盖的大小
        nframes = 64    //动画帧数
        delay   = 8     //帧间的延迟,以10ms为单位
    )
    freq := rand.Float64() * 3.0 //y的相对频率的振荡器
    anim := gif.GIF{LoopCount: nframes}
    phase := 0.0 //相位差
    for i := 0; i < nframes; i++ {
        rect := image.Rect(0, 0, 2*size+1, 2*size+1)
        img := image.NewPaletted(rect, palette)
        for t := 0.0; t < cycles*2*math.Pi; t += res {
            x := math.Sin(t)
            y := math.Sin(t*freq + phase)
            img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5),
                blackIndex)
        }
        phase += 0.1
        anim.Delay = append(anim.Delay, delay)
        anim.Image = append(anim.Image, img)
    }
    gif.EncodeAll(out, &anim) // NOTE: ignoring encoding errors

}

练习1.6

修改Lissajous程序,修改其调色板来生成更丰富的颜色,然后修改SetColorIndex的第三个参数,看看显示结果吧。

package main

import (
    "image"
    "image/color"
    "image/gif"
    "io"
    "math"
    "math/rand"
    "os"
    "time"
)

// var palette = []color.Color{color.White, color.RGBA{0, 0xFF, 0, 0xFF}}

const (
    whiteIndex = 0
    blackIndex = 1
    colorCount = 10
)

func main() {
    var palette []color.Color
    rand.Seed(time.Now().UTC().UnixNano())
    for i := 0; i < colorCount; i++ {
        r := uint8(rand.Uint32() % 256)
        g := uint8(rand.Uint32() % 256)
        b := uint8(rand.Uint32() % 256)
        palette = append(palette, color.RGBA{r, g, b, 0xff})
    }
    lissajous(os.Stdout, palette)
}

func lissajous(out io.Writer, palette []color.Color) {
    const (
        cycles  = 5     //完成x振子的转数
        res     = 0.001 //角分辨率
        size    = 100   //画布覆盖的大小
        nframes = 64    //动画帧数
        delay   = 8     //帧间的延迟,以10ms为单位
    )
    freq := rand.Float64() * 3.0 //y的相对频率的振荡器
    anim := gif.GIF{LoopCount: nframes}
    phase := 0.0 //相位差
    for i := 0; i < nframes; i++ {
        rect := image.Rect(0, 0, 2*size+1, 2*size+1)
        img := image.NewPaletted(rect, palette)
        for t := 0.0; t < cycles*2*math.Pi; t += res {
            x := math.Sin(t)
            y := math.Sin(t*freq + phase)
            img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5),
                colorCount)
        }
        phase += 0.1
        anim.Delay = append(anim.Delay, delay)
        anim.Image = append(anim.Image, img)
    }
    gif.EncodeAll(out, &anim) // NOTE: ignoring encoding errors

}

获取URL

练习1.7

函数调用io.Copy(dst, src)会从src中读取内容,并将读到的结果写入到dst中,使用这个函数替代掉例子中的ioutil.ReadAll来拷贝响应结构体到os.Stdout,避免申请一个缓冲区(例子中的b)来存储。记得处理io.Copy返回结果中的错误。

package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
)

func main() {
    for _, url := range os.Args[1:] {
        resp, err := http.Get(url)
        if err != nil {
            fmt.Fprintf(os.Stderr, "fetch get failed, err: %#v", err)
            os.Exit(1)
        }

        b, err := io.Copy(os.Stdout, resp.Body)
        resp.Body.Close()
        if err != nil {
            fmt.Fprintf(os.Stderr, "copy resp failed, err: %#v", err)
            os.Exit(1)
        }
        fmt.Println(b) //302528 这是字节数
    }
}

练习1.8

修改fetch这个范例,如果输入的url参数没有 http:// 前缀的话,为这个url加上该前缀。你可能会用到strings.HasPrefix这个函数。

package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
    "strings"
)

func main() {
    for _, url := range os.Args[1:] {
        if !strings.HasPrefix(url, "http://") || !strings.HasPrefix(url, "https://") {
            url = "https://" + url
        }
        resp, err := http.Get(url)
        if err != nil {
            fmt.Fprintf(os.Stderr, "fetch get failed, err: %#v", err)
            os.Exit(1)
        }

        b, err := io.Copy(os.Stdout, resp.Body)
        resp.Body.Close()
        if err != nil {
            fmt.Fprintf(os.Stderr, "copy resp failed, err: %#v", err)
            os.Exit(1)
        }
        fmt.Println(b) //302528
    }
}

练习1.9

修改fetch打印出HTTP协议的状态码,可以从resp.Status变量得到该状态码。

package main

import (
    "fmt"
    "io"
    "net/http"
    "os"
    "strings"
)

func main() {
    for _, url := range os.Args[1:] {
        if !strings.HasPrefix(url, "http://") || !strings.HasPrefix(url, "https://") {
            url = "https://" + url
        }
        resp, err := http.Get(url)
        if err != nil {
            fmt.Fprintf(os.Stderr, "fetch get failed, err: %#v", err)
            os.Exit(1)
        }
        fmt.Println("resp status", resp.Status)
        b, err := io.Copy(os.Stdout, resp.Body)
        resp.Body.Close()
        if err != nil {
            fmt.Fprintf(os.Stderr, "copy resp failed, err: %#v", err)
            os.Exit(1)
        }
        fmt.Println(b) //302528
    }
}

web服务

练习1.12

修改Lissajour服务,从URL读取变量,比如你可以访问 http://localhost:8000/?cycles=20 这个URL,这样访问可以将程序里的cycles默认的5修改为20。字符串转换为数字可以调用strconv.Atoi函数。你可以在godoc里查看strconv.Atoi的详细说明。

package main

import (
    "image"
    "image/color"
    "image/gif"
    "io"
    "log"
    "math"
    "math/rand"
    "net/http"
    "strconv"
    "time"
)

const (
    whiteIndex = 0
    blackIndex = 1
    colorCount = 10
)

func main() {
    var palette []color.Color
    rand.Seed(time.Now().UTC().UnixNano())
    for i := 0; i < colorCount; i++ {
        r := uint8(rand.Uint32() % 256)
        g := uint8(rand.Uint32() % 256)
        b := uint8(rand.Uint32() % 256)
        palette = append(palette, color.RGBA{r, g, b, 0xff})
    }
    http.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
        if err := r.ParseForm(); err != nil {
            log.Print(err)
        }
        cycle := r.FormValue("cycle")
        if cycle == "" {
            cycle = "5"
        }
        newCycle, _ := strconv.ParseFloat(cycle, 64)
        lissajous(rw, palette, newCycle)
    })
    log.Fatal(http.ListenAndServe("localhost:8001", nil))
}

func lissajous(out io.Writer, palette []color.Color, cycles float64) {
    const (
        // cycles  = cycle //完成x振子的转数
        res     = 0.001 //角分辨率
        size    = 100   //画布覆盖的大小
        nframes = 64    //动画帧数
        delay   = 8     //帧间的延迟,以10ms为单位
    )
    freq := rand.Float64() * 3.0 //y的相对频率的振荡器
    anim := gif.GIF{LoopCount: nframes}
    phase := 0.0 //相位差
    for i := 0; i < nframes; i++ {
        rect := image.Rect(0, 0, 2*size+1, 2*size+1)
        img := image.NewPaletted(rect, palette)

        for t := 0.0; t < cycles*math.Pi; t += res {
            x := math.Sin(t)
            y := math.Sin(t*freq + phase)
            img.SetColorIndex(size+int(x*size+0.5), size+int(y*size+0.5),
                colorCount)
        }
        phase += 0.1
        anim.Delay = append(anim.Delay, delay)
        anim.Image = append(anim.Image, img)
    }
    gif.EncodeAll(out, &anim) // NOTE: ignoring encoding errors

}

程序结构

包和文件

练习2.1

tempconv包添加类型、常量和函数用来处理Kelvin绝对温度的转换,Kelvin 绝对零度是−273.15°CKelvin绝对温度1K和摄氏度1°C的单位间隔是一样的。

package tempconv

import "fmt"

type Celsius float64
type Fahrenheit float64
type Kelvin float64

const (
    AbsoluteZeroC Celsius = -273.15
    FreezingC     Celsius = 0
    BoilingC      Celsius = 100
)

func (c Celsius) String() string    { return fmt.Sprintf("%g°C", c) }
func (f Fahrenheit) String() string { return fmt.Sprintf("%g°F", f) }
func (k Kelvin) String() string     { return fmt.Sprintf("%gK", k) }

练习2.2

写一个通用的单位转换程序,用类似cf程序的方式从命令行读取参数,如果缺省的话则是从标准输入读取参数,然后做类似CelsiusFahrenheit的单位转换,长度单位可以对应英尺和米,重量单位可以对应磅和公斤等。

package main

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
)

type Feet float64     //英尺
type Meter float64    //米
type Pounds float64   //磅
type Kilogram float64 //公斤

func main() {
    if len(os.Args[1:]) > 0 {
        for _, v := range os.Args[1:] {
            t, err := strconv.ParseFloat(v, 64)
            if err != nil {
                fmt.Fprintf(os.Stderr, "trans failed %#v\n", err)
                os.Exit(1)
            }
            PrintMessage(t)
        }
    } else {
        input := bufio.NewScanner(os.Stdin)
        for input.Scan() {
            t, err := strconv.ParseFloat(input.Text(), 64)
            if err != nil {
                fmt.Fprintf(os.Stderr, "trans failed %#v\n", err)
                os.Exit(1)
            }
            PrintMessage(t)
        }
    }

}

func (f Feet) String() string     { return fmt.Sprintf("%g feet", f) }
func (m Meter) String() string    { return fmt.Sprintf("%g meters", m) }
func (p Pounds) String() string   { return fmt.Sprintf("%g pounds", p) }
func (k Kilogram) String() string { return fmt.Sprintf("%g kilograms", k) }

//英尺转为米
func FToM(f Feet) Meter { return Meter(f * 0.3048) }

//米转化为英尺
func MToF(m Meter) Feet { return Feet(m * 3.2808399) }

//磅转化为公斤
func PToK(p Pounds) Kilogram { return Kilogram(p * 0.45359237) }

//公斤转为磅
func KToP(k Kilogram) Pounds { return Pounds(k * 2.2046226) }

//打印信息
func PrintMessage(t float64) {
    f := Feet(t)
    m := Meter(t)
    p := Pounds(t)
    k := Kilogram(t)
    fmt.Printf("%s = %s \n%s = %s \n%s = %s \n%s = %s \n",
        f, FToM(f), m, MToF(m), p, PToK(p), k, KToP(k),
    )
}

结果:

# 从命令行中获取
go run main.go 32
32 feet = 9.7536 meters 
32 meters = 104.9868768 feet 
32 pounds = 14.51495584 kilograms 
32 kilograms = 70.5479232 pounds 

# 从标准输入中获取 go run main.go << o
heredoc> 43
heredoc> o   
43 feet = 13.1064 meters 
43 meters = 141.0761157 feet 
43 pounds = 19.50447191 kilograms 
43 kilograms = 94.7987718 pounds 

练习2.3

重写PopCount函数,用一个循环代替单一的表达式。比较两个版本的性能。

package main

import (
    "fmt"
    "time"
)

var pc [256]byte = func() (pc [256]byte) {
    for i := range pc {
        pc[i] = pc[i/2] + byte(i&1)
    }
    return
}()

func main() {
    start := time.Now().UnixNano()
    fmt.Println(PopCount(254))
    fmt.Println("elapsed: ", time.Now().UnixNano()-start)
    start = time.Now().UnixNano()
    fmt.Println(PopCount2(254))
    fmt.Println("elapsed: ", time.Now().UnixNano()-start)
}

func PopCount(x uint64) int {
    var num byte
    var i uint64
    for i = 0; i < 8; i++ {
        num += pc[byte(x>>(i*8))]
    }
    return int(num)
}

func PopCount2(x uint64) int {
    return int(pc[byte(x>>(0*8))] +
        pc[byte(x>>(1*8))] +
        pc[byte(x>>(2*8))] +
        pc[byte(x>>(3*8))] +
        pc[byte(x>>(4*8))] +
        pc[byte(x>>(5*8))] +
        pc[byte(x>>(6*8))] +
        pc[byte(x>>(7*8))])
}

性能比较

$ go run main.go
7
elapsed:  28000
7
elapsed:  1000

练习2.4

用移位算法重写PopCount函数,每次测试最右边的1bit,然后统计总数。比较和查表算法的性能差异。

package main

import (
    "fmt"
    "time"
)

var pc [256]byte = func() (pc [256]byte) {
    for i := range pc {
        pc[i] = pc[i/2] + byte(i&1)
    }
    return
}()

func main() {
    start := time.Now().UnixNano()
    fmt.Println(PopCountByTable(1024))
    fmt.Println("elapsed: ", time.Now().UnixNano()-start)

    start = time.Now().UnixNano()
    fmt.Println(PopCountShift(1024))
    fmt.Println("elapsed: ", time.Now().UnixNano()-start)

}

//查表法
func PopCountByTable(x uint64) int {
    return int(pc[byte(x>>(0*8))] +
        pc[byte(x>>(1*8))] +
        pc[byte(x>>(2*8))] +
        pc[byte(x>>(3*8))] +
        pc[byte(x>>(4*8))] +
        pc[byte(x>>(5*8))] +
        pc[byte(x>>(6*8))] +
        pc[byte(x>>(7*8))])
}

//移位算法
func PopCountShift(x uint64) int {
    num := 0
    for i := 0; x != 0; x = x >> 1 {
        if x&1 == 1 {
            i++
        }
        num = i
    }
    return num
}

结果:

$ go run main.go
1
elapsed:  29000
1
elapsed:  3000

练习2.5

表达式x&(x-1)用于将x的最低的一个非零的bit位清零。使用这个算法重写PopCount函数,然后比较性能。

package main

import (
    "fmt"
    "time"
)

var pc [256]byte = func() (pc [256]byte) {
    for i := range pc {
        pc[i] = pc[i/2] + byte(i&1)
    }
    return
}()

func main() {
    start := time.Now().UnixNano()
    fmt.Println(PopCountByTable(1024))
    fmt.Println("elapsed: ", time.Now().UnixNano()-start)

    start = time.Now().UnixNano()
    fmt.Println(PopCount2(1024))
    fmt.Println("elapsed: ", time.Now().UnixNano()-start)
}

//查表法
func PopCountByTable(x uint64) int {
    return int(pc[byte(x>>(0*8))] +
        pc[byte(x>>(1*8))] +
        pc[byte(x>>(2*8))] +
        pc[byte(x>>(3*8))] +
        pc[byte(x>>(4*8))] +
        pc[byte(x>>(5*8))] +
        pc[byte(x>>(6*8))] +
        pc[byte(x>>(7*8))])
}

//x & (x-1)算法
func PopCount2(x uint64) int {
    num := 0
    for x != 0 {
        x = x & (x - 1)
        num++
    }
    return num
}

结果:

$ go run main.go
1
elapsed:  25000
1
elapsed:  1000

基础数据类型

浮点数

练习3.1

如果f数返回的是无限制的float64值,那么SVG文件可能输出无效的多边形元素(虽然许多SVG渲染器会妥善处理这类问题)。修改程序跳过无效的多边形。

package main

import (
    "fmt"
    "math"
    "os"
)

const (
    width, height = 600, 320
    cells         = 100
    xyrange       = 30.0
    xyscale       = width / 2 / xyrange
    zscale        = height * 0.4
    angle         = math.Pi / 6
)

var sin30, cos30 = math.Sin(angle), math.Cos(angle) // sin(30°), cos(30°)

func main() {
    fmt.Printf("<svg xmlns='http://www.w3.org/2000/svg' "+
        "style='stroke: grey; fill: white; stroke-width: 0.7' "+
        "width='%d' height='%d'>", width, height)
    for i := 0; i < cells; i++ {
        for j := 0; j < cells; j++ {
            ax, ay := corner(i+1, j)
            bx, by := corner(i, j)
            cx, cy := corner(i, j+1)
            dx, dy := corner(i+1, j+1)
            if math.IsNaN(ax) || math.IsNaN(ay) || math.IsNaN(bx) || math.IsNaN(by) || math.IsNaN(cx) || math.IsNaN(cy) || math.IsNaN(dx) || math.IsNaN(dy) {
                fmt.Fprintln(os.Stderr, "非数NaN")
            } else {
                fmt.Printf("<polygon points='%g,%g %g,%g %g,%g %g,%g'/>\n",
                    ax, ay, bx, by, cx, cy, dx, dy)
            }

        }
    }
    fmt.Println("</svg>")
}

func corner(i, j int) (float64, float64) {
    x := xyrange * (float64(i)/cells - 0.5)
    y := xyrange * (float64(j)/cells - 0.5)

    z := f(x, y)

    sx := width/2 + (x-y)*cos30*xyscale
    sy := height/2 + (x+y)*sin30*xyscale - z*zscale
    return sx, sy
}

func f(x, y float64) float64 {
    r := math.Hypot(x, y)
    return math.Sin(r) / r
}

练习3.2

试验math包中其他函数的渲染图形。你是否能输出一个egg boxmogulsa saddle图案?

package main

import (
    "fmt"
    "math"
    "os"
)

const (
    width, height = 600, 320
    cells         = 100
    xyrange       = 30.0
    xyscale       = width / 2 / xyrange
    zscale        = height * 0.4
    angle         = math.Pi / 6
)

var sin30, cos30 = math.Sin(angle), math.Cos(angle) // sin(30°), cos(30°)

func main() {
    fmt.Printf("<svg xmlns='http://www.w3.org/2000/svg' "+
        "style='stroke: grey; fill: white; stroke-width: 0.7' "+
        "width='%d' height='%d'>", width, height)
    for i := 0; i < cells; i++ {
        for j := 0; j < cells; j++ {
            ax, ay := corner(i+1, j)
            bx, by := corner(i, j)
            cx, cy := corner(i, j+1)
            dx, dy := corner(i+1, j+1)
            if math.IsNaN(ax) || math.IsNaN(ay) || math.IsNaN(bx) || math.IsNaN(by) || math.IsNaN(cx) || math.IsNaN(cy) || math.IsNaN(dx) || math.IsNaN(dy) {
                fmt.Fprintln(os.Stderr, "非数NaN")
            } else {
                fmt.Printf("<polygon points='%g,%g %g,%g %g,%g %g,%g'/>\n",
                    ax, ay, bx, by, cx, cy, dx, dy)
            }

        }
    }
    fmt.Println("</svg>")
}

func corner(i, j int) (float64, float64) {
    x := xyrange * (float64(i)/cells - 0.5)
    y := xyrange * (float64(j)/cells - 0.5)

    z := saddle(x, y)

    sx := width/2 + (x-y)*cos30*xyscale
    sy := height/2 + (x+y)*sin30*xyscale - z*zscale
    return sx, sy
}

//雪堆状的
func f(x, y float64) float64 {
    r := math.Hypot(x, y)
    return math.Sin(r) / r
}

//egg box
func eggBox(x, y float64) float64 {
    return 0.2 * (math.Cos(x) + math.Cos(y))
}

//saddle
func saddle(x, y float64) float64 {
    a := 25.0
    b := 17.0
    a2 := a * a
    b2 := b * b
    r := y*y/a2 - x*x/b2
    return r
}

练习3.3

根据高度给每个多边形上色,那样峰值部将是红色(#ff0000),谷部将是蓝色(#0000ff)

package main

import (
    "fmt"
    "math"
)

const (
    width, height = 600, 320            // canvas size in pixels
    cells         = 100                 // number of grid cells
    xyrange       = 30.0                // axis ranges (-xyrange..+xyrange)
    xyscale       = width / 2 / xyrange // pixels per x or y unit
    zscale        = height * 0.4        // pixels per z unit
    angle         = math.Pi / 6         // angle of x, y axes (=30°)
)

var sin30, cos30 = math.Sin(angle), math.Cos(angle) // sin(30°), cos(30°)

func main() {
    z_min, z_max := min_max()
    fmt.Printf("<svg xmlns='http://www.w3.org/2000/svg' "+
        "style='stroke: grey; fill: white; stroke-width: 0.7' "+
        "width='%d' height='%d'>", width, height)
    for i := 0; i < cells; i++ {
        for j := 0; j < cells; j++ {
            ax, ay := corner(i+1, j)
            bx, by := corner(i, j)
            cx, cy := corner(i, j+1)
            dx, dy := corner(i+1, j+1)
            if math.IsNaN(ax) || math.IsNaN(ay) || math.IsNaN(bx) || math.IsNaN(by) || math.IsNaN(cx) || math.IsNaN(cy) || math.IsNaN(dx) || math.IsNaN(dy) {
                continue
            } else {
                fmt.Printf("<polygon style='stroke: %s;' points='%g,%g %g,%g %g,%g %g,%g'/>\n",
                    color(i, j, z_min, z_max), ax, ay, bx, by, cx, cy, dx, dy)
            }
        }
    }
    fmt.Println("</svg>")
}

// minmax返回给定x和y的最小值/最大值并假设为方域的z的最小值和最大值。
func min_max() (min, max float64) {
    min = math.NaN()
    max = math.NaN()
    for i := 0; i < cells; i++ {
        for j := 0; j < cells; j++ {
            for xoff := 0; xoff <= 1; xoff++ {
                for yoff := 0; yoff <= 1; yoff++ {
                    x := xyrange * (float64(i+xoff)/cells - 0.5)
                    y := xyrange * (float64(j+yoff)/cells - 0.5)
                    z := f(x, y)
                    if math.IsNaN(min) || z < min {
                        min = z
                    }
                    if math.IsNaN(max) || z > max {
                        max = z
                    }
                }
            }
        }
    }
    return min, max
}

func color(i, j int, zmin, zmax float64) string {
    min := math.NaN()
    max := math.NaN()
    for xoff := 0; xoff <= 1; xoff++ {
        for yoff := 0; yoff <= 1; yoff++ {
            x := xyrange * (float64(i+xoff)/cells - 0.5)
            y := xyrange * (float64(j+yoff)/cells - 0.5)
            z := f(x, y)
            if math.IsNaN(min) || z < min {
                min = z
            }
            if math.IsNaN(max) || z > max {
                max = z
            }
        }
    }

    color := ""
    if math.Abs(max) > math.Abs(min) {
        red := math.Exp(math.Abs(max)) / math.Exp(math.Abs(zmax)) * 255
        if red > 255 {
            red = 255
        }
        color = fmt.Sprintf("#%02x0000", int(red))
    } else {
        blue := math.Exp(math.Abs(min)) / math.Exp(math.Abs(zmin)) * 255
        if blue > 255 {
            blue = 255
        }
        color = fmt.Sprintf("#0000%02x", int(blue))
    }
    return color
}

func corner(i, j int) (float64, float64) {
    // Find point (x,y) at corner of cell (i,j).
    x := xyrange * (float64(i)/cells - 0.5)
    y := xyrange * (float64(j)/cells - 0.5)

    // Compute surface height z.
    z := f(x, y)

    // Project (x,y,z) isometrically onto 2-D SVG canvas (sx,sy).
    sx := width/2 + (x-y)*cos30*xyscale
    sy := height/2 + (x+y)*sin30*xyscale - z*zscale
    return sx, sy
}

func f(x, y float64) float64 {
    r := math.Hypot(x, y) // distance from (0,0)
    return math.Sin(r) / r
}

练习3.4

参考1.7Lissajous例子的函数,构造一个web服务器,用于计算函数曲面然后返回SVG数据给客户端。服务器必须设置Content-Type头部:

w.Header().Set("Content-Type", "image/svg+xml")
package main

import (
    "fmt"
    "log"
    "math"
    "net/http"
)

const (
    width, height = 600, 320            // canvas size in pixels
    cells         = 100                 // number of grid cells
    xyrange       = 30.0                // axis ranges (-xyrange..+xyrange)
    xyscale       = width / 2 / xyrange // pixels per x or y unit
    zscale        = height * 0.4        // pixels per z unit
    angle         = math.Pi / 6         // angle of x, y axes (=30°)
)

var sin30, cos30 = math.Sin(angle), math.Cos(angle) // sin(30°), cos(30°)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "image/svg+xml")
        z_min, z_max := min_max()
        fmt.Fprintf(w, "<svg xmlns='http://www.w3.org/2000/svg' "+
            "style='stroke: grey; fill: white; stroke-width: 0.7' "+
            "width='%d' height='%d'>", width, height)
        for i := 0; i < cells; i++ {
            for j := 0; j < cells; j++ {
                ax, ay := corner(i+1, j)
                bx, by := corner(i, j)
                cx, cy := corner(i, j+1)
                dx, dy := corner(i+1, j+1)
                if math.IsNaN(ax) || math.IsNaN(ay) || math.IsNaN(bx) || math.IsNaN(by) || math.IsNaN(cx) || math.IsNaN(cy) || math.IsNaN(dx) || math.IsNaN(dy) {
                    continue
                } else {
                    fmt.Fprintf(w, "<polygon style='stroke: %s;' points='%g,%g %g,%g %g,%g %g,%g'/>\n",
                        color(i, j, z_min, z_max), ax, ay, bx, by, cx, cy, dx, dy)
                }
            }
        }
        fmt.Fprint(w, "</svg>")
    })
    log.Fatal(http.ListenAndServe("localhost:8001", nil))
}

// minmax返回给定x和y的最小值/最大值并假设为方域的z的最小值和最大值。
func min_max() (min, max float64) {
    min = math.NaN()
    max = math.NaN()
    for i := 0; i < cells; i++ {
        for j := 0; j < cells; j++ {
            for xoff := 0; xoff <= 1; xoff++ {
                for yoff := 0; yoff <= 1; yoff++ {
                    x := xyrange * (float64(i+xoff)/cells - 0.5)
                    y := xyrange * (float64(j+yoff)/cells - 0.5)
                    z := f(x, y)
                    if math.IsNaN(min) || z < min {
                        min = z
                    }
                    if math.IsNaN(max) || z > max {
                        max = z
                    }
                }
            }
        }
    }
    return min, max
}

func color(i, j int, zmin, zmax float64) string {
    min := math.NaN()
    max := math.NaN()
    for xoff := 0; xoff <= 1; xoff++ {
        for yoff := 0; yoff <= 1; yoff++ {
            x := xyrange * (float64(i+xoff)/cells - 0.5)
            y := xyrange * (float64(j+yoff)/cells - 0.5)
            z := f(x, y)
            if math.IsNaN(min) || z < min {
                min = z
            }
            if math.IsNaN(max) || z > max {
                max = z
            }
        }
    }

    color := ""
    if math.Abs(max) > math.Abs(min) {
        red := math.Exp(math.Abs(max)) / math.Exp(math.Abs(zmax)) * 255
        if red > 255 {
            red = 255
        }
        color = fmt.Sprintf("#%02x0000", int(red))
    } else {
        blue := math.Exp(math.Abs(min)) / math.Exp(math.Abs(zmin)) * 255
        if blue > 255 {
            blue = 255
        }
        color = fmt.Sprintf("#0000%02x", int(blue))
    }
    return color
}

func corner(i, j int) (float64, float64) {
    // Find point (x,y) at corner of cell (i,j).
    x := xyrange * (float64(i)/cells - 0.5)
    y := xyrange * (float64(j)/cells - 0.5)

    // Compute surface height z.
    z := f(x, y)

    // Project (x,y,z) isometrically onto 2-D SVG canvas (sx,sy).
    sx := width/2 + (x-y)*cos30*xyscale
    sy := height/2 + (x+y)*sin30*xyscale - z*zscale
    return sx, sy
}

func f(x, y float64) float64 {
    r := math.Hypot(x, y) // distance from (0,0)
    return math.Sin(r) / r
}

复数

练习3.5

实现一个彩色的Mandelbrot图像,使用image.NewRGBA创建图像,使用color.RGBAcolor.YCbCr生成颜色。

package main

import (
    "image"
    "image/color"
    "image/png"
    "math/cmplx"
    "os"
)

func main() {
    const (
        xmin, ymin, xmax, ymax = -2, -2, +2, +2
        width, height          = 1024, 1024
    )

    img := image.NewRGBA(image.Rect(0, 0, width, height))
    for py := 0; py < height; py++ {
        y := float64(py)/height*(ymax-ymin) + ymin
        for px := 0; px < width; px++ {
            x := float64(px)/width*(xmax-xmin) + xmin
            z := complex(x, y)
            img.Set(px, py, mandelbrot(z))
        }
    }
    png.Encode(os.Stdout, img)
}

func mandelbrot(z complex128) color.Color {
    const iterations = 200

    var v complex128
    for n := uint8(0); n < iterations; n++ {
        v = v*v + z
        if cmplx.Abs(v) > 2 {
            return getColor(n)
        }
    }
    return color.Black
}

func getColor(n uint8) color.Color {
    palette := [16]color.Color{
        color.RGBA{66, 30, 15, 255},    // # brown 3
        color.RGBA{25, 7, 26, 255},     // # dark violett
        color.RGBA{9, 1, 47, 255},      //# darkest blue
        color.RGBA{4, 4, 73, 255},      //# blue 5
        color.RGBA{0, 7, 100, 255},     //# blue 4
        color.RGBA{12, 44, 138, 255},   //# blue 3
        color.RGBA{24, 82, 177, 255},   //# blue 2
        color.RGBA{57, 125, 209, 255},  //# blue 1
        color.RGBA{134, 181, 229, 255}, // # blue 0
        color.RGBA{211, 236, 248, 255}, // # lightest blue
        color.RGBA{241, 233, 191, 255}, // # lightest yellow
        color.RGBA{248, 201, 95, 255},  // # light yellow
        color.RGBA{255, 170, 0, 255},   // # dirty yellow
        color.RGBA{204, 128, 0, 255},   // # brown 0
        color.RGBA{153, 87, 0, 255},    // # brown 1
        color.RGBA{106, 52, 3, 255},    // # brown 2
    }
    return palette[n%16]
}

练习3.6

升采样技术可以降低每个像素对计算颜色值和平均值的影响。简单的方法是将每个像素分成四个子像素,实现它。

package main

import (
    "image"
    "image/color"
    "image/png"
    "math/cmplx"
    "os"
)

func main() {
    const (
        xmin, ymin, xmax, ymax = -2, -2, +2, +2
        width, height          = 1024, 1024
        epsX                   = (xmax - xmin) / width
        epsY                   = (ymax - ymin) / height
    )

    offX := []float64{-epsX, epsX}
    offY := []float64{-epsY, epsY}

    img := image.NewRGBA(image.Rect(0, 0, width, height))
    for py := 0; py < height; py++ {
        y := float64(py)/height*(ymax-ymin) + ymin
        for px := 0; px < width; px++ {
            x := float64(px)/width*(xmax-xmin) + xmin
            subPixels := make([]color.Color, 0)
            for i := 0; i < 2; i++ {
                for j := 0; j < 2; j++ {
                    z := complex(x+offX[i], y+offY[j])
                    subPixels = append(subPixels, mandelbrot(z))
                }
            }
            img.Set(px, py, avg(subPixels))
        }
    }
    png.Encode(os.Stdout, img)
}

func mandelbrot(z complex128) color.Color {
    const iterations = 200

    var v complex128
    for n := uint8(0); n < iterations; n++ {
        v = v*v + z
        if cmplx.Abs(v) > 2 {
            return getColor(n)
        }
    }
    return color.Black
}

func getColor(n uint8) color.Color {
    palette := [16]color.Color{
        color.RGBA{66, 30, 15, 255},    // # brown 3
        color.RGBA{25, 7, 26, 255},     // # dark violett
        color.RGBA{9, 1, 47, 255},      //# darkest blue
        color.RGBA{4, 4, 73, 255},      //# blue 5
        color.RGBA{0, 7, 100, 255},     //# blue 4
        color.RGBA{12, 44, 138, 255},   //# blue 3
        color.RGBA{24, 82, 177, 255},   //# blue 2
        color.RGBA{57, 125, 209, 255},  //# blue 1
        color.RGBA{134, 181, 229, 255}, // # blue 0
        color.RGBA{211, 236, 248, 255}, // # lightest blue
        color.RGBA{241, 233, 191, 255}, // # lightest yellow
        color.RGBA{248, 201, 95, 255},  // # light yellow
        color.RGBA{255, 170, 0, 255},   // # dirty yellow
        color.RGBA{204, 128, 0, 255},   // # brown 0
        color.RGBA{153, 87, 0, 255},    // # brown 1
        color.RGBA{106, 52, 3, 255},    // # brown 2
    }
    return palette[n%16]
}

func avg(colors []color.Color) color.Color {
    var r, g, b, a uint16
    n := len(colors)
    for _, c := range colors {
        r_, g_, b_, a_ := c.RGBA()
        r += uint16(r_ / uint32(n))
        g += uint16(g_ / uint32(n))
        b += uint16(b_ / uint32(n))
        a += uint16(a_ / uint32(n))
    }
    return color.RGBA64{r, g, b, a}
}

练习3.7

另一个生成分形图像的方式是使用牛顿法来求解一个复数方程,例如z^4-1=0。每个起点到四个根的迭代次数对应阴影的灰度。方程根对应的点用颜色表示。

package main

import (
    "image"
    "image/color"
    "image/png"
    "math"
    "math/cmplx"
    "os"
)

type Func func(complex128) complex128

var colorPool = []color.RGBA{
    {170, 57, 57, 255},
    {170, 108, 57, 255},
    {34, 102, 102, 255},
    {45, 136, 45, 255},
}

var chosenColors = map[complex128]color.RGBA{}

func main() {
    const (
        xmin, ymin, xmax, ymax = -2, -2, +2, +2
        width, height          = 1024, 1024
    )

    img := image.NewRGBA(image.Rect(0, 0, width, height))
    for py := 0; py < height; py++ {
        y := float64(py)/height*(ymax-ymin) + ymin
        for px := 0; px < width; px++ {
            x := float64(px)/width*(xmax-xmin) + xmin
            z := complex(x, y)
            //点(x,y)表示复数值z
            //img.Set(px, py, mandelbrot(z))
            img.Set(px, py, z4(z))
        }
    }
    png.Encode(os.Stdout, img)
}


func z4(z complex128) color.Color {
    f := func(z complex128) complex128 {
        return z*z*z*z - 1
    }
    fPrime := func(z complex128) complex128 {
        return (z - 1/(z*z*z)) / 4
    }
    return newton(z, f, fPrime)
}

func newton(z complex128, f Func, fPrime Func) color.Color {
    const iterations = 37
    const contrast = 7
    for i := uint8(0); i < iterations; i++ {
        z -= fPrime(z)
        if cmplx.Abs(f(z)) < 1e-6 {
            root := complex(round(real(z), 4), round(imag(z), 4))
            c, ok := chosenColors[root]
            if !ok {
                if len(colorPool) == 0 {
                    panic("no colors left")
                }
                c = colorPool[0]
                colorPool = colorPool[1:]
                chosenColors[root] = c
            }
            y, cb, cr := color.RGBToYCbCr(c.R, c.G, c.B)
            scale := math.Log(float64(i)) / math.Log(iterations)
            y -= uint8(float64(y) * scale)
            return color.YCbCr{y, cb, cr}
        }
    }
    return color.Black
}

func round(f float64, digits int) float64 {
    if math.Abs(f) < 0.5 {
        return 0
    }
    pow := math.Pow10(digits)
    return math.Trunc(f*pow+math.Copysign(0.5, f)) / pow
}

练习3.8

通过提高精度来生成更多级别的分形。使用四种不同精度类型的数字实现相同的分形:complex64complex128big.Floatbig.Rat。(后面两种类型在math/big包声明。Float是有指定限精度的浮点数;Rat是无限精度的有理数。)它们间的性能和内存使用对比如何?当渲染图可见时缩放的级别是多少?

package main

import (
    "image"
    "image/color"
    "image/png"
    "math/big"
    "math/cmplx"
    "os"
)

func main() {
    const (
        xmin, ymin, xmax, ymax = -2, -2, +2, +2
        width, height          = 1024, 1024
    )

    img := image.NewRGBA(image.Rect(0, 0, width, height))
    for py := 0; py < height; py++ {
        y := float64(py)/height*(ymax-ymin) + ymin
        for px := 0; px < width; px++ {
            x := float64(px)/width*(xmax-xmin) + xmin
            z := complex(x, y)
            img.Set(px, py, mandelbrotBigFloat(z))
        }
    }
    png.Encode(os.Stdout, img)
}

func mandelbrot(z complex128) color.Color {
    const iterations = 200
    const contrast = 15
    var v complex128
    for n := uint8(0); n < iterations; n++ {
        v = v*v + z
        if cmplx.Abs(v) > 2 {
            return getColor(n)
        }
    }
    return color.Black
}

func mandelbrot64(z complex128) color.Color {
    const iterations = 200
    const contrast = 15
    var v complex64
    for n := uint8(0); n < iterations; n++ {
        v = v*v + complex64(z)
        if cmplx.Abs(complex128(v)) > 2 {
            return getColor(n)
        }
    }
    return color.Black
}

func mandelbrotBigFloat(z complex128) color.Color {
    const iterations = 200
    const contrast = 15
    zR := (&big.Float{}).SetFloat64(real(z))
    zI := (&big.Float{}).SetFloat64(imag(z))
    var vR, vI = &big.Float{}, &big.Float{}
    for i := uint8(0); i < iterations; i++ {
        vR2, vI2 := &big.Float{}, &big.Float{}
        vR2.Mul(vR, vR).Sub(vR2, (&big.Float{}).Mul(vI, vI)).Add(vR2, zR)
        vI2.Mul(vR, vI).Mul(vI2, big.NewFloat(2)).Add(vI2, zI)
        vR, vI = vR2, vI2
        squareSum := &big.Float{}
        squareSum.Mul(vR, vR).Add(squareSum, (&big.Float{}).Mul(vI, vI))
        if squareSum.Cmp(big.NewFloat(4)) == 1 {
            return getColor(i)
        }
    }
    return color.Black
}

func mandelbrotRat(z complex128) color.Color {
    const iterations = 200
    const contrast = 15
    zR := (&big.Rat{}).SetFloat64(real(z))
    zI := (&big.Rat{}).SetFloat64(imag(z))
    var vR, vI = &big.Rat{}, &big.Rat{}
    for i := uint8(0); i < iterations; i++ {
        // (r+i)^2 = r^2 + 2ri + i^2
        vR2, vI2 := &big.Rat{}, &big.Rat{}
        vR2.Mul(vR, vR).Sub(vR2, (&big.Rat{}).Mul(vI, vI)).Add(vR2, zR)
        vI2.Mul(vR, vI).Mul(vI2, big.NewRat(2, 1)).Add(vI2, zI)
        vR, vI = vR2, vI2
        squareSum := &big.Rat{}
        squareSum.Mul(vR, vR).Add(squareSum, (&big.Rat{}).Mul(vI, vI))
        if squareSum.Cmp(big.NewRat(4, 1)) == 1 {
            return getColor(i)
        }
    }
    return color.Black
}

func getColor(n uint8) color.Color {
    paletted := [16]color.Color{
        color.RGBA{66, 30, 15, 255},    // # brown 3
        color.RGBA{25, 7, 26, 255},     // # dark violett
        color.RGBA{9, 1, 47, 255},      //# darkest blue
        color.RGBA{4, 4, 73, 255},      //# blue 5
        color.RGBA{0, 7, 100, 255},     //# blue 4
        color.RGBA{12, 44, 138, 255},   //# blue 3
        color.RGBA{24, 82, 177, 255},   //# blue 2
        color.RGBA{57, 125, 209, 255},  //# blue 1
        color.RGBA{134, 181, 229, 255}, // # blue 0
        color.RGBA{211, 236, 248, 255}, // # lightest blue
        color.RGBA{241, 233, 191, 255}, // # lightest yellow
        color.RGBA{248, 201, 95, 255},  // # light yellow
        color.RGBA{255, 170, 0, 255},   // # dirty yellow
        color.RGBA{204, 128, 0, 255},   // # brown 0
        color.RGBA{153, 87, 0, 255},    // # brown 1
        color.RGBA{106, 52, 3, 255},    // # brown 2
    }
    return paletted[n%16]
}

练习3.9

编写一个web服务器,用于给客户端生成分形的图像。运行客户端通过HTTP参数指定x、yzoom参数。

package main

import (
    "fmt"
    "image"
    "image/color"
    "image/png"
    "log"
    "math/cmplx"
    "net/http"
    "strconv"
)

func main() {
    const (
        width, height = 1024, 1024
    )
    params := map[string]float64{
        "xmin": -2,
        "xmax": 2,
        "ymin": -2,
        "ymax": 2,
        "zoom": 1,
    }

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        for name := range params {
            s := r.FormValue(name)
            if s == "" {
                continue
            }
            f, err := strconv.ParseFloat(s, 64)
            if err != nil {
                http.Error(w, fmt.Sprintf("query param %s: %s", name, err), http.StatusBadRequest)
                return
            }
            params[name] = f
        }
        if params["xmax"] <= params["xmin"] || params["ymax"] <= params["ymin"] {
            http.Error(w, fmt.Sprintf("min coordinate greater than max"), http.StatusBadRequest)
            return
        }
        xmin := params["xmin"]
        xmax := params["xmax"]
        ymin := params["ymin"]
        ymax := params["ymax"]
        zoom := params["zoom"]

        lenX := xmax - xmin
        midX := xmin + lenX/2
        xmin = midX - lenX/2/zoom
        xmax = midX + lenX/2/zoom
        lenY := ymax - ymin
        midY := ymin + lenY/2
        ymin = midY - lenY/2/zoom
        ymax = midY + lenY/2/zoom

        img := image.NewRGBA(image.Rect(0, 0, width, height))
        for py := 0; py < height; py++ {
            y := float64(py)/height*(ymax-ymin) + ymin
            for px := 0; px < width; px++ {
                x := float64(px)/width*(xmax-xmin) + xmin
                z := complex(x, y)
                img.Set(px, py, mandelbrot(z))
            }
        }
        err := png.Encode(w, img)
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
        }
    })
    log.Fatal(http.ListenAndServe(":1234", nil))
}

func mandelbrot(z complex128) color.Color {
    const iterations = 200
    const contrast = 15

    var v complex128
    for n := uint8(0); n < iterations; n++ {
        v = v*v + z
        if cmplx.Abs(v) > 2 {
            return getColor(n)
        }
    }
    return color.Black
}

func getColor(n uint8) color.Color {
    paletted := [16]color.Color{
        color.RGBA{66, 30, 15, 255},    // # brown 3
        color.RGBA{25, 7, 26, 255},     // # dark violett
        color.RGBA{9, 1, 47, 255},      //# darkest blue
        color.RGBA{4, 4, 73, 255},      //# blue 5
        color.RGBA{0, 7, 100, 255},     //# blue 4
        color.RGBA{12, 44, 138, 255},   //# blue 3
        color.RGBA{24, 82, 177, 255},   //# blue 2
        color.RGBA{57, 125, 209, 255},  //# blue 1
        color.RGBA{134, 181, 229, 255}, // # blue 0
        color.RGBA{211, 236, 248, 255}, // # lightest blue
        color.RGBA{241, 233, 191, 255}, // # lightest yellow
        color.RGBA{248, 201, 95, 255},  // # light yellow
        color.RGBA{255, 170, 0, 255},   // # dirty yellow
        color.RGBA{204, 128, 0, 255},   // # brown 0
        color.RGBA{153, 87, 0, 255},    // # brown 1
        color.RGBA{106, 52, 3, 255},    // # brown 2
    }
    return paletted[n%16]
}

字符串

练习3.10

编写一个非递归版本的comma函数,使用bytes.Buffer代替字符串链接操作。

package main

import (
    "bytes"
    "fmt"
)

func main() {
    fmt.Println(comma("1234543434543235456564"))
}

func comma(s string) string {
    n := len(s)
    if n <= 3 {
        return s
    }
    var buf bytes.Buffer
    mod := n % 3
    for i, v := range s {
        buf.WriteRune(v)
        if (i+1)%3 == mod {
            buf.WriteString(",")
        }
    }
    s = buf.String()
    return s[:len(s)-1] //末尾会多一个, 要去掉
}

练习3.11

完善comma函数,以支持浮点数处理和一个可选的正负号的处理。

package main

import (
    "bytes"
    "fmt"
    "strings"
)

func main() {
    fmt.Println(comma("-0.4343"))
}

func comma(s string) string {
    var buffer bytes.Buffer

    // 获取正负号
    var symbol byte
    if s[0] == '-' || s[0] == '+' {
        symbol = s[0]
        s = s[1:]
    }

    // 将符号添加到返回的字符串中
    buffer.WriteByte(symbol)

    // 分离整数部分与小数部位
    arr := strings.Split(s, ".")
    s = arr[0]
    l := len(s)

    // 格式整数部分
    for i := 0; i < len(s); i++ {
        buffer.WriteString(string(s[i]))
        // 取余3可以得到第一个插入逗号的位置,后面依次+3即可,末尾不加","
        if (i+1)%3 == l%3 && i != l-1 {
            buffer.WriteString(",")
        }
    }

    // 存在小数部分
    if len(arr) > 1 {
        buffer.WriteString(".")
        buffer.WriteString(arr[1])
    }

    s = buffer.String()

    return s
}

练习3.12

编写一个函数,判断两个字符串是否是相互打乱的,也就是说它们有着相同的字符,但是对应不同的顺序。

package main

import (
    "fmt"
)

func main() {
    fmt.Println(compare("abcd", "dcba"))
    fmt.Println(compare("afgerer", "fatefdh"))
}

func compare(s1, s2 string) bool {
    //字节数不一致
    if len(s1) != len(s2) {
        return false
    }
    s1Map := make(map[rune]int)
    s2Map := make(map[rune]int)
    for _, v := range s1 {
        s1Map[v]++
    }
    for _, v := range s2 {
        s2Map[v]++
    }
    for i, v := range s1Map {
        if s2Map[i] != v {
            return false
        }
    }
    return true
}

常量

练习3.13

编写KBMB的常量声明,然后扩展到YB

package main

const (
    KB = 1000
    MB = KB * KB
    GB = MB * KB
    TB = GB * KB
    PB = TB * KB
    EB = PB * KB
    ZB = EB * KB
    YB = ZB * KB
)

复合数据类型

数组

练习4.1

编写一个函数,计算两个SHA256哈希码中不同bit的数目。

package main

import (
    "crypto/sha256"
    "fmt"
)

func main() {
    fmt.Println(diffence("a", "b"))
}

func diffence(s1, s2 string) int {
    var num int
    a := sha256.Sum256([]byte(s1))
    b := sha256.Sum256([]byte(s2))

    for i := 0; i < len(a); i++ {
        //一个byte是8个bit 比较每个byte中每个bit是否相等
        for m := uint(1); m <= 8; m++ {
            if (a[i] >> m) != (b[i] >> m) {
                num++
            }
        }
    }
    return num
}

练习4.2

编写一个程序,默认情况下打印标准输入的SHA256编码,并支持通过命令行flag定制,输出SHA384SHA512哈希算法。

package main

import (
    "bufio"
    "crypto/sha256"
    "crypto/sha512"
    "flag"
    "fmt"
    "os"
    "strings"
)

func main() {
    hashMethond := flag.String("m", "sha256", "请输入哈希算法")
    flag.Parse()
    methond := strings.ToLower(*hashMethond)
    input := bufio.NewScanner(os.Stdin)
    for input.Scan() {
        fmt.Fprintf(os.Stdout, "%x\n", HashResult(methond, input.Text()))
    }

}

func HashResult(methond, s string) interface{} {

    switch methond {
    case "sha256":
        return sha256.Sum256([]byte(s))
    case "sha512":
        return sha512.Sum512([]byte(s))
    case "sha384":
        return sha512.Sum384([]byte(s))
    default:
        return sha256.Sum256([]byte(s))
    }

}

结果:

$ ./main -m sha512 << 0
heredoc> hello  
heredoc> 0
9b71d224bd62f3785d96d46ad3ea3d73319bfbc2890caadae2dff72519673ca72323c3d99ba5c11d7c7acc6e14b8c5da0c4663475c2e5c3adef46f73bcdec043

slice

练习4.3

重写reverse函数,使用数组指针代替slice

package main

import "fmt"

func main() {
    s := []int{1, 2, 3, 4, 5, 6}
    reverse2(&s)
    fmt.Println(s)
}

func reverse2(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]
    }
}

结果:

$ go run main.go
[6 5 4 3 2 1]

练习4.4

编写一个rotate函数,通过一次循环完成旋转。

package main

import "fmt"

func main() {
    s := []int{1, 2, 3, 4, 5}
    fmt.Println(rotate(s, 2))
}

func rotate(s []int, r int) []int {
    lens := len(s)
    res := make([]int, lens)
    for i := 0; i < lens; i++ {
        index := r + i
        if index >= lens {
            index -= lens
        }
        res[i] = s[index]
    }
    return res
}

结果:

$ go run main.go
[3 4 5 1 2]

练习4.5

写一个函数在原地完成消除[]string中相邻重复的字符串的操作。

package main

import "fmt"

func main() {
    s := []string{"h", "e", "l", "l", "o", "w", "o", "o", "r", "l", "d"}
    s = remove(s)
    fmt.Println(s)
    s2 := []string{"h", "e", "l", "l", "o", "w", "o", "o", "r", "l", "d"}
    fmt.Println(remove2(s2))
}

func remove(s []string) []string {
    for i := 0; i < len(s); i++ {
        if (i+1) < len(s) && s[i] == s[i+1] {
            copy(s[i:], s[i+1:])
            s = s[:len(s)-1]
        }
    }
    return s
}

func remove2(s []string) []string {
    var res []string
    for i := 0; i < len(s); i++ {
        res = append(res, s[i])
        if (i+1) < len(s) && s[i] == s[i+1] {
            i++
        }
    }
    return res
}

结果:

$ go run main.go
[h e l o w o r l d]
[h e l o w o r l d]

练习4.6

编写一个函数,原地将一个UTF-8编码的[]byte类型的slice中相邻的空格(参考unicode.IsSpace)替换成一个空格返回

package main

import (
    "fmt"
    "unicode"
)

func main() {
    s := "he  llo  wor  ld"
    res := remove([]byte(s))
    fmt.Printf("%#q\n", s)
    fmt.Println(res)
    fmt.Printf("%#q\n", string(res))
}

func remove(s []byte) []byte {
    var res []byte
    for i := 0; i < len(s); i++ {
        res = append(res, s[i])
        if (i+1) < len(s) && unicode.IsSpace(rune(s[i])) && unicode.IsSpace(rune(s[i+1])) {
            i++
        }
    }
    return res
}

结果:

$ go run main.go
`he  llo  wor  ld`
[104 101 32 108 108 111 32 119 111 114 32 108 100]
`he llo wor ld`

练习4.7

修改reverse函数用于原地反转UTF-8编码的[]byte。是否可以不用分配额外的内存?

package main

import "fmt"

func main() {
    s := []byte("test")
    reverse1(s)
    fmt.Println(s)
    fmt.Println(string(s))
}

func reverse1(s []byte) {
    for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
        s[i], s[j] = s[j], s[i]
    }
}

结果:

$ go run main.go
[116 115 101 116]
tset

map

练习4.8

修改charcount程序,使用unicode.IsLetter等相关的函数,统计字母、数字等Unicode中不同的字符类别。

package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
    "unicode"
    "unicode/utf8"
)

func main() {
    var utflen [utf8.UTFMax + 1]int //UTF-8编码的长度计数
    counts := make(map[rune]int)    //Unicode字符计数
    invalid := 0
    var runeCate = map[string]int{
        "letter":  0, //字母
        "mark":    0, //是否为标记字符
        "number":  0, //是否为数字
        "punct":   0, //是否是标点字符
        "space":   0, //是否是空格
        "symbol":  0, //符号字符
        "control": 0, //控制字符
    }

    in := bufio.NewReader(os.Stdin)

    for {
        r, n, err := in.ReadRune() //解码的rune字符的值,字符UTF-8编码后的长度,和一个错误值

        if err == io.EOF {
            break
        }
        if err != nil {
            fmt.Fprintf(os.Stderr, "charcount: %v\n", err)
            os.Exit(1)
        }
        // 如果输入的是无效的UTF-8编码的字符,返回的将是unicode.ReplacementChar表示无效字符,并且编码长度是1。
        if r == unicode.ReplacementChar && n == 1 {
            invalid++
            continue
        }
        switch {
        case unicode.IsControl(r):
            runeCate["control"]++
        case unicode.IsLetter(r):
            runeCate["letter"]++
        case unicode.IsMark(r):
            runeCate["mark"]++
        case unicode.IsNumber(r):
            runeCate["number"]++
        case unicode.IsPunct(r):
            runeCate["punct"]++
        case unicode.IsSpace(r):
            runeCate["space"]++
        case unicode.IsSymbol(r):
            runeCate["symbol"]++
        }
        counts[r]++
        utflen[n]++
    }
    fmt.Printf("rune\tcount\n")
    for r, n := range counts {
        fmt.Printf("%q\t%d\n", r, n)
    }

    fmt.Print("\nlen\tcount\n")
    for i, n := range utflen {
        if i > 0 {
            fmt.Printf("%d\t%d\n", i, n)
        }
    }
    fmt.Print("\ncate\tcount\n")
    for i, v := range runeCate {
        fmt.Printf("%q\t%d\n", i, v)
    }
    if invalid > 0 {
        fmt.Printf("\n%d invalid UTF-8 characters\n", invalid)
    }
}

结果:

$ ./main <<0        
heredoc> 你好啊世界abcdef123456
heredoc> 0
rune    count
'你'    1
'好'    1
'd'     1
'e'     1
'f'     1
'2'     1
'6'     1
'\n'    1
'啊'    1
'a'     1
'1'     1
'3'     1
'5'     1
'世'    1
'界'    1
'b'     1
'c'     1
'4'     1

len     count
1       13
2       0
3       5
4       0

cate    count
"letter"        11
"mark"                  0
"number"        6
"punct"                 0
"space"                 0
"symbol"        0
"control"       1

练习4.9

编写一个程序wordfreq程序,报告输入文本中每个单词出现的频率。在第一次调用Scan前先调用input.Split(bufio.ScanWords)函数,这样可以按单词而不是按行输入。

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    wordfreq()
}

func wordfreq() {
    counts := make(map[string]int)
    input := bufio.NewScanner(os.Stdin)
    input.Split(bufio.ScanWords)
    for input.Scan() {
        t := input.Text()
        counts[t]++
    }
    fmt.Print("word\tcounts\n")
    for i, v := range counts {
        fmt.Printf("%q\t%d\n", i, v)
    }
}

结果:

 ./main <<0           
heredoc> hello world hello hello 123 123
heredoc> 0
word    counts
"world" 1
"123"   2
"hello" 3

json

练习4.10

修改issues程序,根据问题的时间进行分类,比如不到一个月的、不到一年的、超过一年。

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "net/url"
    "os"
    "strings"
    "time"
)

const IssuseURL = "https://api.github.com/search/issues"

type IssuesSearchResult struct {
    TotalCount int `json:"total_count"`
    Items      []*Issuse
}

type Issuse struct {
    Number    int
    HTMLURL   string `json:"html_url"`
    Title     string
    Status    string
    User      *User
    CreatedAt time.Time `json:"created_at"`
    Body      string
}

type User struct {
    Login   string
    HTMLURL string `json:"html_url"`
    Id      int64
}

func SearchIssuses(terms []string) (*IssuesSearchResult, error) {
    q := url.QueryEscape(strings.Join(terms, " "))
    resp, err := http.Get(IssuseURL + "?q=" + q)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    if resp.StatusCode != http.StatusOK {
        return nil, fmt.Errorf("search query falied: %s", resp.Status)
    }
    var result IssuesSearchResult
    if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
        return nil, err
    }
    return &result, nil
}

func main() {
    result, err := SearchIssuses(os.Args[1:])
    if err != nil {
        log.Fatalf("error: %#v", err)
        return
    }
    now := time.Now().Unix()
    //不到一个月的
    preMonth := now - 30*24*3600
    //不到一年的
    preYear := now - 365*24*3600

    var monthArr []*Issuse    //不到一个月的
    var YearArr []*Issuse     //不到一年的
    var OverYearArr []*Issuse //超过一年的

    for _, v := range result.Items {
        createTime := v.CreatedAt.Unix()
        if createTime > preMonth {
            monthArr = append(monthArr, v)
        }
        if createTime > preYear && createTime < preMonth {
            YearArr = append(YearArr, v)
        }
        if createTime < preYear {
            OverYearArr = append(OverYearArr, v)
        }
    }
    fmt.Println("不到一个月的issuse")
    for _, v := range monthArr {
        fmt.Printf("title: %s\tnumber: %d\tcreated_at: %s\n", v.Title, v.Number, v.CreatedAt.Format("2006-01-02 15:04:05"))
    }
    fmt.Println("不到一年的issuse")
    for _, v := range YearArr {
        fmt.Printf("title: %s\tnumber: %d\tcreated_at: %s\n", v.Title, v.Number, v.CreatedAt.Format("2006-01-02 15:04:05"))
    }
    fmt.Println("超过一年的issuse")
    for _, v := range OverYearArr {
        fmt.Printf("title: %s\tnumber: %d\tcreated_at: %s\n", v.Title, v.Number, v.CreatedAt.Format("2006-01-02 15:04:05"))
    }
}

结果:

$ go run main.go a b c
不到一个月的issuse
title: A<B<C...; slow   number: 2131    created_at: 2021-05-03 07:41:58
title: _.includes('/a/b/c', '/a') return  false number: 5151    created_at: 2021-04-30 02:10:15
title: Unparenthesized `a ? b : c ? d : e` is deprecated. Use either `(a ? b : c) ? d : e` or `a ? b : (c ? d : e)`     number: 36      created_at: 2021-05-03 05:28:27
title: Deprecated: Unparenthesized `a ? b : c ? d : e` is deprecated. Use either `(a ? b : c) ? d : e` or `a ? b : (c ? d : e)` number: 92      created_at: 2021-05-03 21:27:13
title: [A] B    number: 2693    created_at: 2021-05-01 14:36:29
title: Draft: Examples with A, B documents      number: 536     created_at: 2021-04-25 09:48:41
title: Sacar A,B,C,D del h0 cualquiera  number: 24      created_at: 2021-05-02 03:14:42
title: convert DaiWoodward to C++       number: 1657    created_at: 2021-05-05 00:56:37
title: feat: substance program synthesizer      number: 551     created_at: 2021-04-30 20:43:18
title: Switch from C --> C++    number: 1       created_at: 2021-05-04 09:11:30
title: Feature request: -a, -b, -c options like grep has        number: 108     created_at: 2021-04-23 08:26:33
title: [CJ4 v0.12.1] B/C issue  number: 1122    created_at: 2021-04-19 00:02:50
title: Forced CODE128 codes to B-C-B format.    number: 365     created_at: 2021-04-08 20:01:54
title: C# Regex number: 1519    created_at: 2021-05-04 07:36:20
title: codegen: signed bitfield handling        number: 2046    created_at: 2021-04-30 16:06:34
title: Make final mailing from A/B test same template type and options as A/B so it can be copied       number: 20096   created_at: 2021-04-18 15:52:45
title: 김민지: BOJ 2251 물통    number: 151     created_at: 2021-05-03 07:03:42
title: Add grouping to dotenv   number: 319     created_at: 2021-04-30 12:17:31
不到一年的issuse
title: A x B + C        number: 20      created_at: 2021-04-01 14:23:34
title: Simplify (T)(a?b:c) to a?(T)b:(T)c       number: 5892    created_at: 2021-03-07 22:53:44
title: ADDED. A+b*c function    number: 1       created_at: 2021-02-24 16:24:21
title: Country figures 1 A/B/C  number: 131     created_at: 2021-03-14 15:03:59
title: b.c.a    number: 370     created_at: 2021-02-04 04:41:43
title: A,B C.... Suffix generation      number: 3       created_at: 2021-01-28 11:13:02
title: Create b+tree.c  number: 548     created_at: 2020-06-20 05:03:15
title: c = a + b type field syntax      number: 107     created_at: 2021-03-15 16:03:40
title: HilaR 1503 A#12 B&C* (def)       number: 98      created_at: 2021-03-15 07:39:06
title: Solve a for b    number: 1       created_at: 2021-03-16 03:29:40
title: JsonPath.compile("$.A.B.C.D(") returns $['A']['B']['C'].D()      number: 629     created_at: 2020-08-05 15:54:38
title: Should `a || b || c` and `a || (b || c)` be parsed to the same AST?      number: 246     created_at: 2021-03-03 13:50:47
超过一年的issuse

练习4.12

流行的web漫画服务xkcd也提供了JSON接口。例如,一个 https://xkcd.com/571/info.0.json 请求将返回一个很多人喜爱的571编号的详细描述。下载每个链接(只下载一次)然后创建一个离线索引。编写一个xkcd工具,使用这些离线索引,打印和命令行输入的检索词相匹配的漫画的URL

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "os"
)

const XkcdUrl = "https://xkcd.com/%s/info.0.json"

type Xkcd struct {
    Month      string
    Num        int64
    Link       string
    Year       string
    News       string
    SafeTitle  string `json:"safe_title"`
    Transcript string
    Alt        string
    Img        string
    Title      string
    Day        string
}

func main() {

    for _, v := range os.Args[1:] {
        var x Xkcd
        url := fmt.Sprintf(XkcdUrl, v)
        resp, err := http.Get(url)
        if err != nil {
            fmt.Fprintf(os.Stderr, "get failed: %s", err)
            return
        }
        defer resp.Body.Close()
        if resp.StatusCode != http.StatusOK {
            fmt.Fprintf(os.Stderr, "status code failed: %d", resp.StatusCode)
            return
        }
        if err := json.NewDecoder(resp.Body).Decode(&x); err != nil {
            fmt.Fprintf(os.Stderr, "josn failed: %s", err)
            return
        }
        fmt.Fprintf(os.Stdout, "month: %s\nnum: %d\nlink: %s\nyear: %s\nnews: %s\nsafe_title: %s\nalt: %s\nimg: %s\ntitle: %s\nday: %s\n", x.Month, x.Num, x.Link, x.Year, x.News, x.SafeTitle, x.Alt, x.Img, x.Title, x.Day)
    }
}

结果:

$ go run main.go 45   
month: 1
num: 45
link: 
year: 2006
news: 
safe_title: Schrodinger
alt: There was no alt-text until you moused over
img: https://imgs.xkcd.com/comics/schrodinger.jpg
title: Schrodinger
day: 4

练习4.13

使用开放电影数据库的JSON服务接口,允许你检索和下载 https://omdbapi.com/ 上电影的名字和对应的海报图像。编写一个poster工具,通过命令行输入的电影名字,下载对应的海报。

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "io/ioutil"
    "net/http"
    "os"
    "strings"
)

type Omdb struct {
    Poster string `json:"Poster"`
}

const OmdbApi = "http://www.omdbapi.com"

var apiKey string = "xxx" //你的apiKey
var i string = "xxx" //你的id 

func main() {
    for _, v := range os.Args[1:] {
        url := OmdbApi + "?apiKey=" + apiKey + "&i=" + i + "&t=" + v
        resp, err := http.Get(url)
        if err != nil {
            fmt.Fprintf(os.Stderr, "get url failed: %s", err)
            return
        }
        if resp.StatusCode != http.StatusOK {
            resp.Body.Close()
            fmt.Fprintf(os.Stderr, "status code failed: %d", resp.StatusCode)
            return
        }
        var o Omdb
        if err := json.NewDecoder(resp.Body).Decode(&o); err != nil {
            resp.Body.Close()
            fmt.Fprintf(os.Stderr, "json failed: %s", err)
            return
        }
        resp.Body.Close()
        lastIndex := strings.LastIndex(o.Poster, "/")
        name := o.Poster[lastIndex+1:]
        resp, _ = http.Get(o.Poster)
        body, _ := ioutil.ReadAll(resp.Body)
        out, _ := os.Create(name)
        io.Copy(out, bytes.NewReader(body))
    resp.Body.Close()
    }
}

结果:

$ go run main.go hello
暂无评论

发送评论 编辑评论


|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇