ch5/ch5-06 #145
Replies: 12 comments 9 replies
-
练习5.10 - 注释由ChatGPT生成func topoSort(m map[string][]string) []string {
// order 存储了项目的拓扑排序顺序。
var order []string
// seen 记录了已访问过的项目。
seen := make(map[string]bool)
// visitAll 函数递归访问 items 切片中的每个项目,并将它们按照正确的拓扑顺序添加到 order 切片中。
var visitAll func(items []string)
visitAll = func(items []string) {
for _, item := range items {
// 如果当前项目还没有被访问过,则递归调用 visitAll 函数,并将当前项目加入到 order 切片中。
if !seen[item] {
seen[item] = true
visitAll(m[item])
order = append(order, item)
}
}
}
// keys 存储了 m 中所有键的切片。
var keys []string
// 遍历 m 中的每个键,并将它们存储到 keys 切片中。
for key := range m {
keys = append(keys, key)
}
// 调用 visitAll 函数,并传入 keys 切片,从而从依赖图中的“叶子”节点(即没有依赖的项目)开始,
// 逐步往上遍历依赖关系,并将项目添加到 order 切片中。
visitAll(keys)
// 返回 order 切片,表示拓扑排序后项目的顺序。
return order
}
func main() {
// 调用 topoSort 函数,并将返回值存储到 courses 切片中。
courses := topoSort(prereqs)
// 遍历 courses 切片中的每个项目,并以“索引:项目”的格式将它们输出到屏幕上。
// 这么做的目的是使map的输出是有序的
for i := 0; i < len(courses); i++ {
fmt.Printf("%d:\t%s\n", i+1, courses[i])
}
} |
Beta Was this translation helpful? Give feedback.
-
练习5.11// prereqs记录了每个课程的前置课程
var prereqs = map[string][]string{
"algorithms": {"data structures"},
// ---环---
"linear algebra": {"calculus"},
"calculus": {"linear algebra"},
// ---环---
"compilers": {
"data structures",
"formal languages",
"computer organization",
},
"data structures": {"discrete math"},
"databases": {"data structures"},
"discrete math": {"intro to programming"},
"formal languages": {"discrete math"},
"networks": {"operating systems"},
"operating systems": {"data structures", "computer organization"},
"programming languages": {"data structures", "computer organization"},
}
// hasCycle 检查图中是否存在环
func hasCycle(m map[string][]string) bool {
// visited 记录已经访问过的节点
visited := make(map[string]bool)
// dfs 函数用来进行深度优先搜索
var dfs func(string, map[string]bool) bool
dfs = func(item string, path map[string]bool) bool {
// 如果当前节点已经被访问过,则返回 false
if visited[item] {
return false
}
// 将当前节点标记为已访问,并加入访问路径
visited[item] = true
path[item] = true
// 遍历当前节点的所有相邻节点
for _, next := range m[item] {
// 如果相邻节点已经在访问路径上,说明存在环,返回 true
if path[next] || dfs(next, path) {
return true
}
}
// 将当前节点从访问路径中移除,并返回 false
delete(path, item)
return false
}
// 遍历 map 中的每个节点,并调用 dfs 函数检查是否存在环
for k := range m {
path := make(map[string]bool)
if dfs(k, path) {
return true
}
}
// 如果所有节点都检查完,则说明图中不存在环,返回 false
return false
}
func main() {
// 有环则输出true
fmt.Println(hasCycle(prereqs))
} |
Beta Was this translation helpful? Give feedback.
-
练习5.12文件Practice5.12.gopackage main
import (
"Study/src/study/day7Function/HtmlTools"
"fmt"
"golang.org/x/net/html"
)
// forEachNode 针对每个结点x,都会调用pre(x)和post(x)。
// pre和post都是可选的。
// 遍历孩子结点之前,pre被调用
// 遍历孩子结点之后,post被调用
func forEachNode(n *html.Node, pre, post func(n *html.Node)) {
if pre != nil {
pre(n)
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
forEachNode(c, pre, post)
}
if post != nil {
post(n)
}
}
func main() {
var depth int
var startElement func (n *html.Node) = func (n *html.Node) {
if n.Type == html.ElementNode {
fmt.Printf("%*s<%s>\n", depth*2, "", n.Data)
depth++
}
}
endElement := func (n *html.Node) {
if n.Type == html.ElementNode {
depth--
fmt.Printf("%*s</%s>\n", depth*2, "", n.Data)
}
}
forEachNode(HtmlTools.GetHtml(), startElement, endElement)
} 文件 /HtmlTools/Tools.gopackage HtmlTools
import (
"fmt"
"golang.org/x/net/html"
"log"
"net/http"
)
func GetHtml() *html.Node {
resp, err := http.Get("https://golang-china.github.io/gopl-zh/ch5/ch5-02.html")
if err != nil {
log.Fatal(err)
}
if resp.StatusCode != http.StatusOK {
err := resp.Body.Close()
if err != nil {
log.Fatal(err)
return nil
}
}
doc, err := html.Parse(resp.Body)
if err != nil {
log.Fatal(err)
return nil
}
return doc
} |
Beta Was this translation helpful? Give feedback.
-
练习5.13文件 Practice5.13.gopackage main
import (
"Study/src/study/day7Function/HtmlTools"
"bufio"
"fmt"
"golang.org/x/net/html"
"log"
"os"
"path"
"path/filepath"
"runtime"
"strings"
)
// BreadthFirst 队列中的每节点都调用f()。
// f()返回的所有节点都被添加到队列中。
// 对每个节点最多调用一次f()。
func BreadthFirst(f func(item string) []string, worklist []string) {
// seen
seen := make(map[string]bool)
for len(worklist) > 0 {
// 复制当前队列
items := worklist
// 清空队列
worklist = nil
// 遍历复制的当前队列
for _, item := range items {
// 判断seen, 防止重复添加到队列
if !seen[item] {
seen[item] = true
// 添加到队列
worklist = append(worklist, f(item)...)
}
}
}
}
// ExtractRestructure 从HTML文档中提取链接,并将相对路径转换为绝对路径
//
// URL string, 要提取链接的HTML文档
// isSave bool, 是否保存提取到的链接对应的页面
// saveOriginOnly bool, 是否只保存与当前页面域名相同的页面
//
// return []string, error, 提取到的链接列表和错误信息
func ExtractRestructure(URL string, isSave bool, saveOriginOnly bool) ([]string, error) {
// 检查参数
if URL == "" {
URL = "https://go.dev/"
}
node, resp := HtmlTools.GetHtmlResponse(URL)
originURL := resp.Request.URL.Host
var links []string
visitNode := func(n *html.Node) {
if n.Type == html.ElementNode && n.Data == "a" {
for _, a := range n.Attr {
if a.Key != "href" {
continue
}
// 将相对路径和url的内容组合在一起形成完整的绝对路径
link, err := resp.Request.URL.Parse(a.Val)
if err != nil {
// 忽略掉报error的URL
fmt.Println(err)
continue
}
// 将link添加到切片
links = append(links, link.String())
// 判断是否只保存同一域名下的地址
if saveOriginOnly && (link.Host != originURL) {
continue
}
// 保存页面
if isSave && link.String() != "" {
filePath := getCurrentAbPath() + "/www/urls.txt"
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
fmt.Println("文件打开失败", err)
}
//及时关闭file句柄
defer file.Close()
//写入文件时,使用带缓存的 *Writer
write := bufio.NewWriter(file)
if _, err := write.WriteString(link.String() + "\r\n"); err != nil {
panic(err)
}
// 将缓存写入文件
if err := write.Flush(); err != nil {
panic(err)
}
}
}
}
}
HtmlTools.ForEachNode(node, visitNode, nil)
return links, nil
}
func main() {
// crawl 此函数返回一个[]string, 内容为url
crawl := func(url string) []string {
fmt.Println("Crawl:", url)
list, err := ExtractRestructure(url, true, true)
if err != nil {
log.Print(err)
}
return list
}
// Crawl the web breadth-first,
// starting from the command-line arguments.
BreadthFirst(crawl, []string{"https://go.dev/"})
}
// see https://zhuanlan.zhihu.com/p/363714760
// 最终方案-全兼容
func getCurrentAbPath() string {
dir := getCurrentAbPathByExecutable()
if strings.Contains(dir, getTmpDir()) {
return getCurrentAbPathByCaller()
}
return dir
}
// 获取系统临时目录,兼容go run
func getTmpDir() string {
dir := os.Getenv("TEMP")
if dir == "" {
dir = os.Getenv("TMP")
}
res, _ := filepath.EvalSymlinks(dir)
return res
}
// 获取当前执行文件绝对路径
func getCurrentAbPathByExecutable() string {
exePath, err := os.Executable()
if err != nil {
log.Fatal(err)
}
res, _ := filepath.EvalSymlinks(filepath.Dir(exePath))
return res
}
// 获取当前执行文件绝对路径(go run)
func getCurrentAbPathByCaller() string {
var abPath string
_, filename, _, ok := runtime.Caller(0)
if ok {
abPath = path.Dir(filename)
}
return abPath
} 文件 /HtmlTools/Tools.gopackage HtmlTools
import (
"fmt"
"golang.org/x/net/html"
"log"
"net/http"
)
func GetHtmlResponse(url string) (*html.Node, *http.Response) {
// 检查参数
if url == "" {
url = "https://go.dev/"
}
resp, err := http.Get("https://go.dev/")
if err != nil {
log.Fatal(err)
}
if resp.StatusCode != http.StatusOK {
err := resp.Body.Close()
if err != nil {
log.Fatal(err)
return nil, nil
}
}
doc, err := html.Parse(resp.Body)
if err != nil {
log.Fatal(err)
return nil, nil
}
return doc, resp
}
// ForEachNode 深度遍历 n, pre和post分别表示压栈和出栈时执行的动作
//
// n *html.Node
// pre func(n *html.Node)
// post func(n *html.Node)
//
// 将 ForEachNode() 移动到单独的软件包,供以后方便调用
func ForEachNode(n *html.Node, pre, post func(n *html.Node)) {
if n == nil {
return
}
if pre != nil {
pre(n)
}
//for c := n.FirstChild; c != nil; c = c.NextSibling {
// forEachNode(c, pre, post)
//}
ForEachNode(n.FirstChild, pre, post)
ForEachNode(n.NextSibling, pre, post)
if post != nil {
post(n)
}
} |
Beta Was this translation helpful? Give feedback.
-
练习5.14只做了有向图的BFS,树和无向图差不多,只要你能理解队列的思想,应该不难~~ func breadthFirstDigraph(m map[string][]string) []string {
var worklist, res []string
// 将第一层节点加入到队列
for k, _ := range m {
worklist = append(worklist, k)
}
// seen
seen := make(map[string]bool)
for len(worklist) > 0 {
// 复制当前队列
items := worklist
// 清空队列
worklist = nil
// 遍历复制的当前队列
for _, item := range items {
// 判断seen, 防止重复添加到队列
if !seen[item] {
seen[item] = true
// 添加到队列
worklist = append(worklist, m[item]...)
fmt.Println(item)
res = append(res, item)
}
}
}
return res
} |
Beta Was this translation helpful? Give feedback.
-
好家伙,我的顺序就不一样。。。 |
Beta Was this translation helpful? Give feedback.
-
dir := dir ,这里还是没有说明白,例子举得也不好,确实最后都是同一个值,但没理解了为啥 |
Beta Was this translation helpful? Give feedback.
-
5.10 输入为Map类型func topoSortMap(m map[string][]string) []string {
var order []string
seen := make(map[string]bool)
var visitAll func(map[string][]string)
visitAll = func(mp map[string][]string) {
mp1 := make(map[string][]string, len(m))
for k, v := range mp {
if !seen[k] {
seen[k] = true
for _, v1 := range v { // 遍历预修课程
if !seen[v1] { // 判断预修课程是否被观测。由于图中某些节点存在多个边,因此,如果预修课程被提前观测,则不再观测该课程。
value, ok := m[v1] // 判断预修课程是否存在学习序列
if ok {
mp1[v1] = value // 存在则继续观测
} else {
mp1[v1] = make([]string, 0) // 不存在则结束观测
}
}
}
visitAll(mp1)
order = append(order, k)
}
}
}
visitAll(m)
return order
} |
Beta Was this translation helpful? Give feedback.
-
练习5.10
输出结果:
|
Beta Was this translation helpful? Give feedback.
-
// squares返回一个匿名函数。 必须要 |
Beta Was this translation helpful? Give feedback.
-
5.6.1中,不知道是不是因为我是用最新版本的go解释器,我没有将循环体中的循环变量赋值给新的变量,直接用的循环体变量给到函数值中,所有的文件夹都被删除了。并没有出现只会删除最后一个文件夹的情况。 |
Beta Was this translation helpful? Give feedback.
-
出现环的充要条件是有课程在seem里面 但不再order里面 import ( // prereqs记录了每个课程的前置课程 func main() { func topoSort(m map[string][]string) []string {
} func contains(slice []string, target string) bool { // 新增函数用于判断元素是否在切片中 |
Beta Was this translation helpful? Give feedback.
-
ch5/ch5-06
中文版
https://golang-china.github.io/gopl-zh/ch5/ch5-06.html
Beta Was this translation helpful? Give feedback.
All reactions