GO指南之练习答案

GO指南之练习答案

Go指南

练习:循环与函数

为了练习函数与循环,我们来实现一个平方根函数:给定一个数 x,我们需要找到一个数 z 使得 z² 尽可能地接近 x。

计算机通常使用循环来计算 x 的平方根。从某个猜测的值 z 开始,我们可以根据 z² 与 x 的近似度来改进 z,产生一个更好的猜测:

z -= (z*z - x) / (2*z)

重复调整的过程,猜测的结果会越来越精确,得到的答案也会尽可能接近实际的平方根。

请在提供的 func Sqrt​ 中实现它。无论输入是什么,可以先猜测 z 为 1。 首先,重复计算 10 次并连续打印每次的 z 值。观察对于不同的 x 值(1、2、3 ...), 你得到的答案是如何逼近结果的,以及猜测改进的速度有多快。

提示:用类型转换或浮点数语法来声明并初始化一个浮点数值:

z := 1.0
z := float64(1)

然后,修改循环条件,使得当值停止改变(或改变非常小)的时候退出循环。 观察迭代次数大于还是小于 10。尝试改变 z 的初始猜测,如 x 或 x/2。 你的函数结果与标准库中的 math.Sqrt 有多接近?

: 如果你对该算法的细节感兴趣,上面的 z² − x 是 z² 到它所要到达的值(即 x) 的距离,除数 2z 为 z² 的导数,我们通过 z² 的变化速度来改变 z 的调整量。 这种通用方法叫做牛顿法, 它对很多函数,特别是平方根而言非常有效。)

答案:

package mainimport ("fmt""math"
)func Sqrt(x float64) float64 {z := 1.0tmp := 0.0for {z = z - (z*z -x) / (2*z)fmt.Println(z)if math.Abs(z-tmp) < 0.0000000001 {break} else {tmp = z}}return z
}func main() {Sqrt(4)
}

运行结果:

2.5
2.05
2.000609756097561
2.0000000929222947
2.000000000000002
2

练习:切片

实现 Pic​。它应当返回一个长度为 dy​ 的切片,其中每个元素是一个长度为 dx​,元素类型为 uint8​ 的切片。当你运行此程序时,它会将每个整数解释为灰度值 (好吧,其实是蓝度值)并显示它所对应的图像。

图像的解析式由你来定。几个有趣的函数包括 (x+y)/2​、x*y​、x^y​、x*log(y)​ 和 x%(y+1)​。

(提示:需要使用循环来分配 [][]uint8​ 中的每个 []uint8​。)

(请使用 uint8(intValue)​ 在类型之间转换;你可能会用到 math​ 包中的函数。)

答案:

package mainimport "golang.org/x/tour/pic"//import "math"func Pic(dx, dy int) [][]uint8 {img := make([][]uint8, dy)for i := range img {img[i] = make([]uint8, dx)for j := range img[i] {img[i][j] = uint8((i + j) / 2) // 此处可替换成其他函数}}return img
}func main() {pic.Show(Pic)
}

练习:映射

实现 WordCount​。它应当返回一个映射,其中包含字符串 s​ 中每个“单词”的个数。 函数 wc.Test​ 会为此函数执行一系列测试用例,并输出成功还是失败。

你会发现 strings.Fields 很有用。

答案:

package mainimport ("golang.org/x/tour/wc""strings"
)func WordCount(s string) map[string]int {p := strings.Fields(s)var m map[string]int // 声明m = make(map[string]int) // 初始化for _,v :=range p{m[v] += 1}return m 
}func main() {wc.Test(WordCount)
}

运行结果:

PASSf("I am learning Go!") = map[string]int{"Go!":1, "I":1, "am":1, "learning":1}
PASSf("The quick brown fox jumped over the lazy dog.") = map[string]int{"The":1, "brown":1, "dog.":1, "fox":1, "jumped":1, "lazy":1, "over":1, "quick":1, "the":1}
PASSf("I ate a donut. Then I ate another donut.") = map[string]int{"I":2, "Then":1, "a":1, "another":1, "ate":2, "donut.":2}
PASSf("A man a plan a canal panama.") = map[string]int{"A":1, "a":2, "canal":1, "man":1, "panama.":1, "plan":1}

练习:斐波纳契闭包

让我们用函数做些好玩的。

实现一个 fibonacci​ 函数,它返回一个函数(闭包),该闭包返回一个斐波纳契数列 (0, 1, 1, 2, 3, 5, ...)。

答案:

package mainimport "fmt"func fibonacci() func() int {f1, f2 := 0, 1return func() int {f := f1f1, f2 = f2, f1 + f2return f}
}func main() {f := fibonacci()for i := 0; i < 10; i++ {fmt.Printf("%v, ",f()) //原:fmt.Println(f())}
}

运行结果:

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 

练习:Stringer

通过让 IPAddr​ 类型实现 fmt.Stringer​ 来打印点号分隔的地址。

例如,IPAddr{1, 2, 3, 4}​ 应当打印为 "1.2.3.4"​。

答案:

package mainimport "fmt"type IPAddr [4]byte// TODO: 为 IPAddr 添加一个 "String() string" 方法。
func (i IPAddr) String() string{return fmt.Sprintf("%v.%v.%v.%v",i[0],i[1],i[2],i[3])
}func main() {hosts := map[string]IPAddr{"loopback":  {127, 0, 0, 1},"googleDNS": {8, 8, 8, 8},}for name, ip := range hosts {fmt.Printf("%v: %v\n", name, ip)}
}

运行结果:

loopback: 127.0.0.1
googleDNS: 8.8.8.8

练习:错误

从之前的练习中复制 Sqrt​ 函数,修改它使其返回 error​ 值。

Sqrt​ 接受到一个负数时,应当返回一个非 nil 的错误值。复数同样也不被支持。

创建一个新的类型

type ErrNegativeSqrt float64

并为其实现

func (e ErrNegativeSqrt) Error() string

方法使其拥有 error​ 值,通过 ErrNegativeSqrt(-2).Error()​ 调用该方法应返回 "cannot Sqrt negative number: -2"​。

注意:Error​ 方法内调用 fmt.Sprint(e)​ 会让程序陷入死循环。可以通过先转换 e​ 来避免这个问题:fmt.Sprint(float64(e))​。这是为什么呢?

修改 Sqrt​ 函数,使其接受一个负数时,返回 ErrNegativeSqrt​ 值。

答案:

package mainimport ("fmt"
)type ErrNegativeSqrt float64func (e ErrNegativeSqrt) Error() string {return fmt.Sprintf("cannot Sqrt negative number: %v", float64(e))
}func Sqrt(x float64) (float64, error) {if x < 0 {return 0, ErrNegativeSqrt(x)} else {z := x / 2for i := 0; i < 10 && z*z-x != 0; i++ {z -= (z*z - x) / (2 * z)}return z, nil}
}func main() {fmt.Println(Sqrt(2))fmt.Println(Sqrt(-2))
}

运行结果:

1.414213562373095 <nil>
0 cannot Sqrt negative number: -2

练习:Reader

实现一个 Reader​ 类型,它产生一个 ASCII 字符 'A'​ 的无限流。

答案:

package mainimport "golang.org/x/tour/reader"type MyReader struct{}// TODO: 给 MyReader 添加一个 Read([]byte) (int, error) 方法func (a MyReader) Read(b []byte) (int, error) {b[0] = 'A'return 1, nil
}func main() {reader.Validate(MyReader{})
}

Go 指南上一页说过:

Read ​用数据填充给定的字节切片并返回填充的字节数和错误值。在遇到数据流的结尾时,它会返回一个 io.EOF ​错误。

那我们填充了一个 A ​就返回 1​,无限流转换一下就是不返回 io.EOF​,我们返回一个 nil ​就行了!

练习:rot13Reader

有种常见的模式是一个 io.Reader​ 包装另一个 io.Reader​,然后通过某种方式修改其数据流。

例如,gzip.NewReader​ 函数接受一个 io.Reader​(已压缩的数据流)并返回一个同样实现了 io.Reader​ 的 *gzip.Reader​(解压后的数据流)。

编写一个实现了 io.Reader​ 并从另一个 io.Reader​ 中读取数据的 rot13Reader​,通过应用 rot13 代换密码对数据流进行修改。

rot13Reader​ 类型已经提供。实现 Read​ 方法以满足 io.Reader​。

答案:

package mainimport ("io""os""strings"
)type rot13Reader struct {r io.Reader
}func rot13(b []byte) []byte {for i, _ := range b {switch {case (b[i] >= 'A' && b[i] <= 'M') || (b[i] >= 'a' && b[i] <= 'm'):b[i] += 13case (b[i] >= 'N' && b[i] <= 'Z') || (b[i] >= 'n' && b[i] <= 'z'):b[i] -= 13}}return b
}func (a rot13Reader) Read(b []byte) (int, error) {i, e := a.r.Read(b)b = rot13(b)return i, e
}
func main() {s := strings.NewReader("Lbh penpxrq gur pbqr!")r := rot13Reader{s}io.Copy(os.Stdout, &r)
}

运行结果:

You cracked the code!

练习:图像

还记得之前编写的图片生成器 吗?我们再来编写另外一个,不过这次它将会返回一个 image.Image​ 的实现而非一个数据切片。

定义你自己的 Image​ 类型,实现必要的方法并调用 pic.ShowImage​。

Bounds​ 应当返回一个 image.Rectangle​ ,例如 image.Rect(0, 0, w, h)​。

ColorModel​ 应当返回 color.RGBAModel​。

At​ 应当返回一个颜色。上一个图片生成器的值 v​ 对应于此次的 color.RGBA{v, v, 255, 255}​。

答案:

package mainimport ("golang.org/x/tour/pic""image""image/color"
)type Image struct { //新建一个Image结构体w, h int
}func (i Image) Bounds() image.Rectangle { //实现Image包中生成图片边界的方法return image.Rect(0, 0, i.w, i.h)
}func (i Image) ColorModel() color.Model { //实现Image包中颜色模式的方法return color.RGBAModel
}func (i Image) At(x, y int) color.Color { //实现Image包中生成图像某个点的方法return color.RGBA{uint8(x), uint8(y), uint8(255), uint8(255)}
}func main() {m := Image{255, 255}pic.ShowImage(m)
}

练习:等价二叉查找树

不同二叉树的叶节点上可以保存相同的值序列。例如,以下两个二叉树都保存了序列 `1,1,2,3,5,8,13`。

在大多数语言中,检查两个二叉树是否保存了相同序列的函数都相当复杂。 我们将使用 Go 的并发和信道来编写一个简单的解法。

本例使用了 tree​ 包,它定义了类型:

type Tree struct {Left  *TreeValue intRight *Tree
}

1. 实现 Walk​ 函数。

2. 测试 Walk​ 函数。

函数 tree.New(k)​ 用于构造一个随机结构的已排序二叉查找树,它保存了值 k​, 2k​, 3k​, ..., 10k​。

创建一个新的信道 ch​ 并且对其进行步进:

go Walk(tree.New(1), ch)

然后从信道中读取并打印 10 个值。应当是数字 1, 2, 3, ..., 10.

3.Walk​ 实现 Same​ 函数来检测 t1​ 和 t2​ 是否存储了相同的值。

4. 测试 Same​ 函数。

Same(tree.New(1), tree.New(1))​ 应当返回 true​,而 Same(tree.New(1), tree.New(2))​ 应当返回 false​。

Tree​ 的文档可在这里找到。

思路一(用go):

  1. 准备两个信道
  2. 两棵树分别同时写入自己的信道,写入一个数据后立刻读出数据 (为使该步骤顺利进行,需要将两个Walk​加上go​)
  3. 对比读出的数据,一旦数据不同 return false

为什么选择没有缓冲的信道?

  • 防止出现ch1在读出第1个数据,ch2已经在读出第2个数据的情况,那样会误判为数据不一样return false
package mainimport ("fmt""golang.org/x/tour/tree"
)// Walk 步进 tree t 将所有的值从 tree 发送到 channel ch。
func Walk(t *tree.Tree, ch chan int) {// 	输出左边;if nil != t.Left {Walk(t.Left, ch)}// 输出中间;ch <- t.Value// 输出右边if nil != t.Right {Walk(t.Right, ch)}
}// Same 判断 t1 和 t2 是否包含相同的值。
func Same(t1, t2 *tree.Tree) bool {ch1 := make(chan int)ch2 := make(chan int)go Walk(t1, ch1)go Walk(t2, ch2)//close(ch1)//close(ch2)for i := 0; i < 10; i++ {x := <-ch1y := <-ch2fmt.Println(x, y)if x != y {return false}}return true}func main() {flag := Same(tree.New(1), tree.New(1))fmt.Println(flag)
}

思路二(不使用go)

先把所有的数据全部分别写入自己的信道,之后再依次读取,进行对比

func Same(t1, t2 *tree.Tree) bool {ch1 := make(chan int, 10)ch2 := make(chan int, 10)Walk(t1, ch1)Walk(t2, ch2)close(ch1)close(ch2)i := 0for x := range ch1 { // 循环次数y := <-ch2fmt.Println(x, y)if x != y {return false}i++}return true
}

运行结果:

1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
10 10
true

练习:Web 爬虫

在这个练习中,我们将会使用 Go 的并发特性来并行化一个 Web 爬虫。

修改 Crawl​ 函数来并行地抓取 URL,并且保证不重复。

提示: 你可以用一个 map 来缓存已经获取的 URL,但是要注意 map 本身并不是并发安全的!

答案:

借鉴利用队列实现广度优先算法的思路,把第一个url当做根节点,根节点下面的url当做孩子节点,队列当做管道。

  • 把第一个url作为根节点,查询[showCrawl()函数]
  • 将对应孩子节点集合加入管道。
  • 遍历这个孩子节点集合
  • 重复2~3步骤
  • 直到管道中没有元素

为什么要使用map来存储url而不用数组?

  • 虽然只需要存储一个数据(url),但是通过map[url] (通过key来作为索引) 可以判断url是否存在,而数组判断某个值是否存在相对麻烦。

  • map 可以通过语句_, ok := map[url]​ 来判断某个key-value 对是否存在。

    当这个索引不存在 OK == false

管道如何设置"当管道为空时" 停止读取?

  • 设置一个flag int​用于记录管道中还有多少个元素,每次取元素之前判断flag 是否为0

关于管道记录数flag,为什么不判断直接++呢?万一这个url下面没有任何链接呢,那么就可以不用将[]string写入管道?

    1. 首先,把每个查询后的url页面下属的url集合放入管道中,要把管道内元素个数++

    2. 不用判断更加方便

    3. 如果没有任何链接,Fetch()会返回一个nil的[]string数组,并且将nil数组加入到管道中

    4. 在Crawl()中执行 for _, url := range urls时,这个for循环不会执行 。正因为这一条,我们可以每次执行查询命令都直接将nil 的[]string加入管道,统一管理

      注意,在for…range 空数组时,只是不会有任何输出,但不会报错。

package mainimport ("fmt"
)type Fetcher interface {// Fetch 返回 URL 的 body 内容,并且将在这个页面上找到的 URL 放到一个 slice 中。Fetch(url string) (body string, urls []string, err error)
}// 存储已经找到url及其body
var store = make(map[string]string)// 查询页面,并将页面下的url放到管道中
func showCrawl(url string, Urls chan []string, fetcher Fetcher) {// 1. 搜索url并存储// 2. 将本页面下属的url写入管道body, urls, err := fetcher.Fetch(url)if err != nil {fmt.Println(err)} else {// 显示并存入字典中fmt.Printf("found: %s %q\n", url, body)// flag++}store[url] = body // 查询过,但是页面没有内容Urls <- urls
}// Crawl 使用 fetcher 从某个 URL 开始递归的爬取页面,直到达到最大深度。
func Crawl(url string, depth int, fetcher Fetcher) {// TODO: 并行的抓取 URL。// TODO: 不重复抓取页面。// 下面并没有实现上面两种情况:// 初始化工作if depth < 1 {return}Urls := make(chan []string, 10)  //需要有缓存的管道:广度优先遍历需要一层层存储go showCrawl(url, Urls, fetcher) // 用于开篇第一次的查询flag := 1                        // 用于探测管道中还有多少组数据(一个页面的全部下属url为一组)// 2. 根据要求的深度(depth)查询 : 向下查询多少层for i := depth; i > 0; i-- {// 广度优先查询下属url:不一定是广度优先,只要管道里有,就读取// fmt.Print("dfff")if flag > 0 {urls := <-Urls // urls == []stringfmt.Printf("i:%d, flag:%d urls:%s\n", i, flag, urls)flag--                     // 读走一个集合for _, url := range urls { // 一个页面下的url集合fmt.Printf("接收到的url:%s\n", url)if _, ok := store[url]; !ok { // 判断是否查询过go showCrawl(url, Urls, fetcher)flag++ //把每个查询后的url页面下属的url集合放入管道中,要把管道内元素个数++//为什么不判断直接++呢?万一这个url下面没有任何链接呢?//1. 不用判断更加方便  2. 如果没有任何链接,Feth()会返回一个nil数组,并且将nil数组加入到管道中// 在Crawl()中执行 for _, url := range urls时,这个for循环不会执行}}}}
}func main() {// flag := 1Crawl("https://golang.org/", 6, fetcher)
}// fakeFetcher 是返回若干结果的 Fetcher。
type fakeFetcher map[string]*fakeResulttype fakeResult struct {body stringurls []string
}func (f fakeFetcher) Fetch(url string) (string, []string, error) {if res, ok := f[url]; ok {return res.body, res.urls, nil}return "", nil, fmt.Errorf("not found: %s", url)
}// fetcher 是填充后的 fakeFetcher。
var fetcher = fakeFetcher{"https://golang.org/": &fakeResult{"The Go Programming Language",[]string{"https://golang.org/pkg/","https://golang.org/cmd/",},},"https://golang.org/pkg/": &fakeResult{"Packages",[]string{"https://golang.org/","https://golang.org/cmd/","https://golang.org/pkg/fmt/","https://golang.org/pkg/os/",},},"https://golang.org/pkg/fmt/": &fakeResult{"Package fmt",[]string{"https://golang.org/","https://golang.org/pkg/",},},"https://golang.org/pkg/os/": &fakeResult{"Package os",[]string{"https://golang.org/","https://golang.org/pkg/",},},
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/880051.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第三轮easy~hard题目

题目1代码 #include<iostream> #include<queue> #include<string> using namespace std; int main() {priority_queue<int> s;string str;int a;str="0";while(str!="end"){cin>>str;if(str=="insert"){cin>&g…

通过Ollama本地部署DeepSeek R1以及简单使用

本文介绍了在Windows环境下,通过Ollama来本地部署DeepSeek R1。该问包含了Ollama的下载、安装、安装目录迁移、大模型存储位置修改、下载DeepSeek以及通过Web UI来对话等相关内容。1、下载Ollama 首先我们到Ollama官网去下载安装包,此处我们下载的是Windows版本的安装包,如下…

git删除gitee上传大文件转载

前言:在提交推送本地更改至仓库时,误将大文件给提交了,导致push时报错文件过大,因此需要将已经commit的大文件移除后再push 若已知要删除的文件或文件夹路径,则可以从第4步开始 1.对仓库进行gc操作 $ git gc 2.查询大文件 git verify-pack -v .git/objects/pack/pack-*.id…

Axure RP 9.0 软件安装、汉化

‌Axure RP9是一款专业的原型设计工具,主要用于快速创建交互式的高保真原型,帮助设计师和产品经理进行用户体验设计和界面设计‌。‌ 主要功能和应用场景‌原型设计‌:Axure RP9提供了丰富的交互组件和功能,如可交互的按钮、链接、表单元素等,用户可以模拟真实的应用程序或…

第八节 BERT实战

做下游分类任务,如何处理一句话的输入input_dis:输入哪些字 21128个汉字里编码 mask:输入的话有多长. 模型输入固定,不够的话用padding补上 Seq_ids:句子编码 segmentBERT输入token embedding 字编码(21128, 768) segment embedding 句子编码(2, 768) position embedding不用给…

linux X64函数参数传递过程研究

linux X64函数参数传递过程研究 - ZhaoKevin - 博客园 基础知识 函数传参存在两种方式,一种是通过栈,一种是通过寄存器。对于x64体系结构,如果函数参数不大于6个时,使用寄存器传参,对于函数参数大于6个的函数,前六个参数使用寄存器传递,后面的使用栈传递。参数传递的规律…

使用systemback封装Ubuntu系统iso镜像

25年小橘祝亲们钱财发发发,好运来来来。小橘初八就已经开工了,不知道家人们是不是也像小橘一样苦哈哈。今天给亲们分享使用systemback封装Ubuntu系统iso镜像。 一、环境部署 1.安装systembackecho "deb [arch=amd64] http://mirrors.bwbot.org/ stable main" > …

免费+数据安全!手把手教你在PC跑DeepSeek-R1大模型,小白也能秒变AI大神!

0 为啥本地部署? 在本地运行 AI 模型具有以下优势:隐私:你的数据保留在你的机器上 — — 不存在共享敏感信息的风险 成本: DeepSeek R1 可免费使用,无需订阅费或使用费 控制:无需外部依赖即可进行微调和实验1 使用Ollama 1.1 下载并运行应用程序 直达官网:1.2 选择你的平…

mkv和ass字幕文件合并

主要使用两种工具:ffmpeg和mkvmerge 操作系统:windows10及以上 ffmpeg下载:https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-win64-gpl-shared.zip mkvmerge下载:https://mkvtoolnix.download/windows/releases/89.0/mkvtoolnix-64-…

【日记】我已经穿越了那层屏障(1903 字)

正文每次节后都知道,能说 “节后再说” 这句话当时有多爽(笑。现在节后有好多好多事情要做——年后再说的事,袭来!昨天是收假之后第一天,开了个会,我没去。结果他们下来都一幅晴天霹雳的样子,我十分诧异。结果一问,基本上全部的人都要轮岗了。我三月份又要跑去业务线,…

洛谷题单指南-线段树的进阶用法-P5445 [APIO2019] 路灯

原题链接:https://www.luogu.com.cn/problem/P5445 题意解读:给定一个长度为n的01串,一共有q个时刻,对于每个时刻,可能有两种操作:1. 把第x个位置取反 2. 查询a ~ b - 1之间的串在过去有多少个时刻都为1。 解题思路: 一、朴素想法 每个时刻对路灯的状态建立线段树,可以…

[LLM] ZeRO-DP技术简析

[LLM] ZeRO-DP技术简析 本文对ZeRO: Memory Optimizations Toward Training Trillion Parameter Models中提出的ZeRO-DP进行简要总结。相关的讲解其实网上也有很多了,不过只看网上的终究还是有点走马观花,所以我还是决定自己写一篇博客,记录一下我自己的理解。这篇博客讲的不…