第三章

  • 整数
  • 浮点数
  • 复数
  • 布尔值
  • 字符串
  • 常量

这一章阅读体验很好,因为书上给的示例很炫酷,产生的结果是炫酷的图形,然而想要理解却挺头痛,因为需要一些基础的图形学知识,这章习题做得比较头疼,不是因为代码逻辑,而是因为”业务逻辑”

练习3.1

假如函数f返回一个float64型的无穷大值,就会导致SVG文件含有无效的元素.修改本程序以避免无效多边形

  • 我的想法是将无穷大和math.MaxFloat64进行比较,比它还大就替换为最大值

    1
    2
    3
    4
    5
    6
    7
     func TestMax(t *testing.T) {
    var z float64
    if 1/z > math.MaxFloat64{
    fmt.Println(math.MaxFloat64)
    }
    }

练习3.2

用math包的其他函数试验可视化效果.你是否能生成各种曲面,分别呈鸡蛋盒状、雪坡状或马鞍状

  • 没有完全搞明白,三维映射到二维的过程,不过整个代码的逻辑还是很清晰的,这一题略过
练习3.3

按高度给每个多边形上色,使得封顶呈红色(#ff0000),谷底呈蓝色(#0000ff)

  • 思路是得到最大值最小值,将中间部分分为256个部分,然后每个z值都能映射到一个颜色,想着可以平滑过渡,然而效果却很糟糕😰
  • svg的polygon显示颜色,只要在每个polygon上面添加fill属性就可以了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package main

import (
"math"
"fmt"
)

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)

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)
min, max := getMinMax()
for i := 0; i < cells; i++ {
for j := 0; j < cells; j++ {
ax, ay, r, g, b := corner(i+1, j, min, max)
bx, by, r, g, b := corner(i, j, min, max)
cx, cy, r, g, b := corner(i, j+1, min, max)
dx, dy, r, g, b := corner(i+1, j+1, min, max)
fmt.Printf("<polygon points='%g,%g %g,%g %g,%g %g,%g' fill='#%x%x%x'/>\n",
ax, ay, bx, by, cx, cy, dx, dy, r, g, b)
}
}
fmt.Println("</svg>")
}

func getColor(min, max, current float64) (int, int, int) {
step := (max - min) / 255
v := int((current - min) / step)
r := v
g := 0
b := 255 - v
return r, g, b
}

func getMinMax() (float64, float64) {
var min, max float64
for i := 0; i < cells; i++ {
for j := 0; j < cells; j++ {
x := xyrange * (float64(i)/cells - 0.5)
y := xyrange * (float64(j)/cells - 0.5)

z := f(x, y)
if z < min {
min = z
}
if z > max {
max = z
}
}
}
return min, max
}

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

z := f(x, y)
// 将(x,y,z)等角投射到二维SVG绘图平面上,坐标是(sx,sy)
sx := width/2 + (x-y)*cos30*xyscale
sy := height/2 + (x+y)*sin30*xyscale - z*zscale
r, g, b := getColor(min, max, z)
return sx, sy, r, g, b
}

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

练习3.4

仿照1.7节的示例Lissajous的方法,构建一个web服务器,计算并生成曲面,同时将svg数据写入客户端.服务器必须如下设置Content-Type报头 w.Header().set("Content-Type","image/svg+xml")

  • 这个问题比较简单,体现了Go的接口思维,将main改成函数,传入io.Writer接口,将fmt.Printf改为fmt.Fprintf就可以了,最后web请求加Header让浏览器识别svg
1
2
3
4
5
6
7
8
9
func main() {
handler := func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "image/svg+xml")
image(w)
}
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe("localhost:8877", nil))
return
}

练习3.5

用image.NewRGBA函数和color.RGBA类型或color.YCbCr类型实现一个Mandelbrot集的全彩图

  • 又是一个图形学,一维变量最简单的方式是R=G=B,最后出来一个平滑变化的灰度图。作者要求变成全彩图(喂!大哥,我是来学Golang的,不是来搞图形学的),我也不知道这需要什么图形学知识😕,不过看到有人给出了这样一个看起来很简单也符合题目要求的变化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 {
v := 255 - contrast*n
return color.YCbCr{v, 255 - v, 255}
}
}
return color.Black
}

练习3.6:

超采样通过对几个临近像素颜色取样并取均值,是一种减少锯齿化的方法.最简单的做法是将每个像素分为4个”子像素”.给出实现方式

  • 暂时略过

练习3.7:

另一种简单的分形是运用牛顿法求某个函数的复数解,比如z的四次方-1 = 0.以平面上各点作为牛顿法的起始,根据逼近其中一个根(共有四个根)所需的迭代次数对该点设定灰度.再根据求得的根对每个点进行全彩上色

  • 暂时略过

练习3.8:

生成高度放大的分形需要极高的数学精度.分别用以下四种类型(complex64,complex128,big.Float,big.Rat)表示数据实现同一个分形(后面两种类型由math/big包给出.big.Float类型随意选用float32/float64浮点数,但精度有限;big.Rat类型使用无限精度的有理数.)它们在计算性能和内存消耗上相比如何?放大到什么程度,渲染的失真变得可见

  • 暂时略过

练习3.9:

编写一个web服务器,它生成分形并将图像写入客户端.要让客户端得以通过HTTP请求的参数指定x,y值和放大系数

  • 终于有一题能做的了😭,读取查询参数,修改函数输出传入参数io.Writer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package main

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

func main() {
handler := func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "image/x-png")
xv := r.URL.Query().Get("x")
x, err := strconv.Atoi(xv)
if err != nil {
x = 2
}
yv := r.URL.Query().Get("y")
y, err := strconv.Atoi(yv)
if err != nil {
y = 2
}
generate(x, y, w)
}
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe("localhost:8877", nil))
return
}

func generate(_x, _y int, w io.Writer) {
var xmin, ymin, xmax, ymax float64 = float64(-1*_x), float64(-1*_x), float64(_y), float64(_y)
const (
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(w, 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 {
v := 255 - contrast*n
return color.YCbCr{v, 255 - v, 255}
}
}
return color.Black
}

练习3.10:

编写一个非递归的comma函数,运用bytes.Buffer,而不是简单的字符串拼接

  • 得到字符串长度,先对三取余,然后每三位循环一次,最后一个循环特殊处理
  • 使用bytes.Buffer.String得到结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
"bytes"
"fmt"
)

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

l := len(s)
mod := l % 3
if mod > 0 {
r.Write([]byte(s[:mod] + ","))
}
for mod+3 < l {
r.Write([]byte(s[mod:mod+3] + ","))
mod += 3
}
if mod+3 == l {
r.Write([]byte(s[mod:mod+3]))
}
fmt.Println(r.String())
}

func main() {
comma("12345")
}

练习3.11

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

  • 先处理(剔除)前置符号,在处理后置浮点数,最后stings.Join连接起来
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package main

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

func comma(s string) {
var r bytes.Buffer
start := ""
if strings.HasPrefix(s, "-") || strings.HasPrefix(s, "+") {
start = string(s[0])
s = s[1:]
}

end := ""
if strings.Contains(s, ".") {
ss := strings.Split(s, ".")
s, end = ss[0], "."+ss[1]
}

l := len(s)
mod := l % 3
if mod > 0 {
r.Write([]byte(s[:mod] + ","))
}
for mod+3 < l {
r.Write([]byte(s[mod:mod+3] + ","))
mod += 3
}
if mod+3 == l {
r.Write([]byte(s[mod:mod+3]))
}
fmt.Println(strings.Join([]string{start, r.String(), end}, ""))
}

func main() {
comma("12345")
comma("12345.1")
comma("-12345.1")
}

练习3.12

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

  • 遍历字符串A,对比字符在两个字符串的个数是否相等
  • 字符串遍历有坑,得到的是数字,需要string转换一下类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"fmt"
"strings"
)

func same(s1, s2 string) bool {
if len(s1) != len(s2) {
return false
}

for _, v := range s1 {
if strings.Count(s1, string(v)) != strings.Count(s2, string(v)) {
return false
}
}
return true
}

func main() {
fmt.Println(same("123", "122"))
fmt.Println(same("123", "3321"))
fmt.Println(same("123", "321"))
}

练习3.13

编写KB、MB的常量声明,然后扩展到YB

  • iota

暂略