day7函数 练习

master
独孤伶俜 2022-12-09 01:20:34 +08:00
parent d33b37a04a
commit 5ab496ae36
10 changed files with 626 additions and 2 deletions

View File

@ -0,0 +1,92 @@
package main
import (
"Study/src/study/day7Function/HtmlTools"
"fmt"
"golang.org/x/net/html"
"strings"
)
func main() {
// 函数字面量允许我们在使用函数时,再定义它。
// 通过这种技巧我们可以改写之前对strings.Map的调用
str1 := strings.Map(func(r rune) rune { return r + 1 }, "HAL-9000")
fmt.Println(str1)
// 定义变量保存square的返回值
sqar := square()
// 此时sqar里保存的是返回值也就是相当于一个函数类型的变量
// sqar 相当于
//sqar = func(x int) int {
// x++
// return x * x
//}
// 只不过在sqar := square()里x的值不来自于形参的x而是来自于上一级快语句square中。
sqar = square()
fmt.Println(sqar()) // 1
fmt.Println(sqar()) // 4
fmt.Println(sqar()) // 9
fmt.Println(sqar()) // 16
// 我们看到变量的生命周期不由它的作用域决定squares返回后变量x仍然隐式的存在于sqar中。
// 直到我们销毁sqar变量资源被gc回收或者程序运行完成被系统回收
// 这就是 闭包(closures)
// 闭包 一定是在返回的函数里面使用了外部函数的资源
// 闭包 通常用于扩展函数的功能
links, err := Extract()
if err != nil {
fmt.Println(err)
}
for _, link := range links {
fmt.Println(link)
}
}
// 函数square返回另一个类型为 func() int 的函数,
// 对square的第一次调用会生成第一个局部变量x并返回一个匿名函数
// 每次调用此匿名函数时, 改函数都会使x的值+1, 再返回x的平方,
// 第二次调用square时, 会生成新的匿名函数。
// 新的匿名函数操作的也是一个新的x变量
// ⚠需要注意x的作用域和生存周期
// ⚠x的作用域在匿名函数里是属于上一级块语句提供的
// ⚠所以只要square的返回值被保存了
// ⚠匿名函数就可以访问到x变量
func square() func() int {
var x int
return func() int {
x++
return x * x
}
}
// Extract
//
// 现在links中存储的不是href属性的原始值
// 而是通过resp.Request.URL解析后的值。
// 解析后这些连接以绝对路径的形式存在可以直接被http.Get访问。
func Extract() ([]string, error) {
node, resp := HtmlTools.GetHtmlS()
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())
}
}
}
HtmlTools.ForEachNode(node, visitNode, nil)
return links, nil
}

View File

@ -0,0 +1,83 @@
package main
import (
"Study/src/study/day7Function/HtmlTools"
"fmt"
"log"
)
// 接下来,我们讨论一个有点学术性的例子,考虑这样一个问题:
// 给定一些计算机课程,每个课程都有前置课程,只有完成了前置课程才可以开始当前课程的学习;
// 我们的目标是选择出一组课程,这组课程必须确保按顺序学习时,能全部被完成。每个课程的前置课程如下:
// 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"},
}
func main() {
// for i, course := range topoSort(prereqs) {
// fmt.Printf("%d:\t%s\n", i, course)
// }
//fmt.Println(hasCycle(prereqs))
//topoSort(prereqs)
//fmt.Printf("%v", len(prereqs["qqq"]))
//BreadthFirst(crawl, []string{"all"})
}
// BreadthFirst
// calls f() for each item in the worklist.
// Any items returned by f() are added to the worklist.
// f() is called at most once for each item.
//
// -
//
// BreadthFirst 队列中的每节点都调用f()。
// f()返回的所有节点都被添加到队列中。
// 对每个节点最多调用一次f()。
//
// f func(item string) []string
// returns []string
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)...)
}
}
}
}
// crawl 此函数返回一个[]string, 内容为url
func crawl(url string) []string {
fmt.Println(url)
list, err := HtmlTools.Extract()
if err != nil {
log.Print(err)
}
return list
}

View File

@ -1,6 +1,7 @@
package HtmlTools
import (
"fmt"
"golang.org/x/net/html"
"log"
"net/http"
@ -26,3 +27,85 @@ func GetHtml() *html.Node {
return doc
}
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)
}
}
// Extract
//
// 现在links中存储的不是href属性的原始值
// 而是通过resp.Request.URL解析后的值。
// 解析后这些连接以绝对路径的形式存在可以直接被http.Get访问。
//
// return []string, error
//
// 将Extract()移动到单独的软件包,供以后方便调用
func Extract() ([]string, error) {
node, resp := GetHtmlResponse("")
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())
}
}
}
ForEachNode(node, visitNode, nil)
return links, nil
}

View File

@ -0,0 +1,103 @@
package main
import (
"fmt"
)
// topoSort 函数返回一个切片,其中的字符串代表了可以安全学习的课程。
// m 参数表示每个课程的前置课程的映射。
//
// func topoSort(m map[string][]string) []string {
// // 定义一个 order 切片,用于存储排序后的课程名。
// var order []string
// // 定义一个 seen 映射,用于记录每个课程是否已经遍历过。
// seen := make(map[string]bool)
// // 定义一个 visitAll 函数,用于遍历所有的课程。
// var visitAll func(items map[string]bool)
// // 定义 visitAll 函数的实现体。
// visitAll = func(items map[string]bool) {
// // 遍历 items 参数中的所有课程。
// for item := range items {
// // 如果当前课程没有遍历过。
// if !seen[item] {
// // 将当前课程标记为已遍历。
// seen[item] = true
// // 递归调用 visitAll 函数,继续遍历当前课程的前置课程。
// visitAll(m[item])
// // 将当前课程追加到 order 切片的末尾。
// order = append(order, item)
// }
// }
// }
// // 定义一个 keys 切片,用于存储 m 参数中所有课程的名称。
// var keys = make(map[string]bool)
// // 遍历 m 参数中的所有课程。
// for key := range m {
// // 将当前课程的名称追加到 keys 切片的末尾。
// keys[key] = true
// }
// visitAll(keys)
// // 返回 order 切片。
// return order
//
// }
// topoSort 函数用于对给定的项目进行拓扑排序。
// 它接收一个字符串到字符串切片的映射 m其中 m[i] 存储了项目 i 的依赖项目。
// 函数返回一个切片,表示拓扑排序后项目的顺序。
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() {
// prereqs 记录了每个课程的前置课程
var prereqs = map[string][]string{
"algorithms": {"data structures"},
"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"},
}
// 调用 topoSort 函数,并将返回值存储到 courses 切片中。
courses := topoSort(prereqs)
// 遍历 courses 切片中的每个项目,并以“索引:项目”的格式将它们输出到屏幕上。
// 这么做的目的是使map的输出是有序的
for i := 0; i < len(courses); i++ {
fmt.Printf("%d:\t%s\n", i+1, courses[i])
}
}

View File

@ -0,0 +1,65 @@
package main
import "fmt"
// 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))
}

View File

@ -0,0 +1,41 @@
package 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)
}

View File

@ -0,0 +1,157 @@
package 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() != "" {
if err != nil {
panic(err)
}
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
}

View File

@ -36,7 +36,7 @@ func main() {
// return nil
//}
////调用
//forEachNode(HtmlTools.GetHtml(), ElementByID, "a")
//forEachNode(HtmlTools.GetHtmlS(), ElementByID, "a")
var each func(doc *html.Node) func(id string) *html.Node
each = func(doc *html.Node) func(id string) *html.Node {
@ -67,7 +67,7 @@ func main() {
return ResultNode
}
}
//each1 := each(HtmlTools.GetHtml())
//each1 := each(HtmlTools.GetHtmlS())
//each1("a")
// 手动清除ResultNode的内容