removeSlashPrefix
func removeSlashPrefix(text string) string {
for {
if strings.HasPrefix(text, "/") {
_, i := utf8.DecodeRuneInString(text)
text = text[i:]
continue
}
break
}
return text
}
这个函数的名字是removeSlashPrefix,它接受一个字符串参数text,返回一个字符串。函数的主要作用是移除输入字符串text的前缀中的所有斜线("/")。
让我们一步一步来解析这个函数:
for { ... }
:这是一个无限循环,只有在满足某个条件时才会跳出。
if strings.HasPrefix(text, "/") { ... }
:这个if语句检查字符串text是否以斜线("/")开始。如果是,那么就执行花括号内的代码。
_, i := utf8.DecodeRuneInString(text)
:这行代码会解码字符串text的第一个字符(在这个函数中,这个字符肯定是一个斜线),并返回该字符的长度i。因为Go语言中的字符串是UTF-8编码的,所以一个字符可能占用多个字节。
text = text[i:]
:这行代码会移除字符串text的第一个字符,也就是斜线。
continue
:这个关键字会使得循环立即跳到下一次迭代,也就是再次检查字符串text的前缀是否还有斜线。
break
:这个关键字会使得循环立即结束,也就是当字符串text的前缀不再有斜线时,函数就会返回处理后的字符串。
举个例子,如果我们调用removeSlashPrefix("///hello"),那么函数会返回"hello",因为它移除了字符串前缀的所有斜线。
printIfUnique
func printIfUnique(text string, list map[string]struct{}) {
text = removeSlashPrefix(text)
if _, ok := list[text]; !ok {
fmt.Println(text)
list[text] = struct{}{}
}
}
这个函数的名字是printIfUnique,它接受两个参数:一个字符串text和一个映射list。这个映射的键是字符串,值是空结构体。函数的主要作用是检查字符串text是否在映射list中已经存在。如果不存在,那么就打印这个字符串,并将其添加到映射中。
我们一步一步来解析这个函数:
text = removeSlashPrefix(text)
:这行代码调用了我们之前解析过的函数removeSlashPrefix,移除字符串text的前缀中的所有斜线。
if _, ok := list[text]; !ok { ... }
:这个if语句检查字符串text是否在映射list中已经存在。如果不存在(也就是ok为false),那么就执行花括号内的代码。
fmt.Println(text)
:这行代码会打印字符串text。
list[text] = struct{}{}
:这行代码会将字符串text添加到映射list中,值是一个空结构体。
举个例子,如果我们有一个映射list,它的键是字符串,值是空结构体,然后我们调用printIfUnique("///hello", list),那么函数会打印"hello",并将其添加到映射中。如果我们再次调用printIfUnique("///hello", list),那么函数什么也不会做,因为"hello"已经在映射中了。
splitBy
func splitBy(text string, split string, list map[string]struct{}) {
splitString := strings.Split(text, split)
for _, match := range splitString {
if match != "" {
printIfUnique(match, list)
}
}
}
这个函数的名字是splitBy,它接受三个参数:两个字符串text和split,以及一个映射list。这个映射的键是字符串,值是空结构体。函数的主要作用是将字符串text按照字符串split进行分割,然后对分割得到的每个子字符串进行处理。
我们一步一步来解析这个函数:
splitString := strings.Split(text, split)
:这行代码调用了strings.Split函数,将字符串text按照字符串split进行分割,得到一个字符串切片splitString。
for _, match := range splitString { ... }
:这个for循环遍历splitString中的每个元素match。
if match != "" { ... }
:这个if语句检查match是否为空字符串。如果不是空字符串,那么就执行花括号内的代码。
printIfUnique(match, list)
:这行代码调用了我们之前解析过的函数printIfUnique,对字符串match进行处理。
举个例子,如果我们有一个映射list,它的键是字符串,值是空结构体,然后我们调用splitBy("hello/world", "/", list)
,那么函数会打印"hello"和"world",并将它们添加到映射中。如果我们再次调用splitBy("hello/world", "/", list),那么函数什么也不会做,因为"hello"和"world"已经在映射中了
process
func process(text string, list map[string]struct{}, r *regexp.Regexp) {
for _, match := range r.FindAllString(text, -1) {
if match != "" {
if strings.Contains(match, ".") {
splitBy(match, ".", list)
}
if strings.Contains(match, "/") {
splitBy(match, "/", list)
}
printIfUnique(match, list)
}
}
}
这个函数叫做process,它接受三个参数:一个字符串text,一个映射list,以及一个正则表达式对象r。这个映射的键是字符串,值是空结构体。函数的主要作用是处理字符串text,找出所有匹配正则表达式r的子字符串,并对这些子字符串进行进一步的处理。
我们一步一步来解析这个函数:
for _, match := range r.FindAllString(text, -1) { ... }
:这个for循环遍历所有匹配正则表达式r的子字符串match。FindAllString函数的第二个参数是-1,表示找出所有的匹配项。
if match != "" { ... }
:这个if语句检查match是否为空字符串。如果不是空字符串,那么就执行花括号内的代码。
if strings.Contains(match, ".") { ... }
和if strings.Contains(match, "/") { ... }
:这两个if语句检查match是否包含.
或/
。如果包含,那么就调用splitBy函数,将match按照.
或/
进行分割,并对分割得到的每个子字符串进行处理。
printIfUnique(match, list)
:这行代码调用了我们之前解析过的函数printIfUnique,对字符串match进行处理。
举个例子,假设我们有一个映射list,它的键是字符串,值是空结构体,然后我们调用process("hello.world/foo", list, regexp.MustCompile("w+")),那么函数会打印"hello"、"world"、"foo",并将它们添加到映射中。如果我们再次调用process("hello.world/foo", list, regexp.MustCompile("\w+"))
,那么函数什么也不会做,因为"hello"、"world"和"foo"已经在映射中了。
main
func main() {
list := make(map[string]struct{}) // store the output lines to check for dupes
s := bufio.NewScanner(os.Stdin)
r := regexp.MustCompile(`[a-zA-Z0-9.-_/]*`)
for s.Scan() {
process(s.Text(), list, r)
}
if s.Err() != nil {
log.Printf("Error: %sn", s.Err())
}
}
它的主要作用是从标准输入读取文本,然后对每一行文本进行处理。
我们一步一步来解析这段代码:
list := make(map[string]struct{})
:这行代码创建了一个映射list,它的键是字符串,值是空结构体。这个映射用来存储输出的行,以检查是否有重复的行。
s := bufio.NewScanner(os.Stdin)
:这行代码创建了一个新的扫描器s,用来从标准输入读取文本。
r := regexp.MustCompile([a-zA-Z0-9.-_/]*)
:这行代码创建了一个新的正则表达式对象r,用来匹配由字母、数字、.、-、_、/组成的字符串。
for s.Scan() { ... }
:这个for循环遍历标准输入的每一行。Scan方法读取下一行,并将其作为字符串返回。如果读取成功,Scan方法返回true,否则返回false。
process(s.Text(), list, r)
:这行代码调用了我们之前解析过的函数process,对每一行文本进行处理。
if s.Err() != nil { ... }
:这个if语句检查是否在读取标准输入时发生了错误。如果发生了错误,那么就打印错误信息。
举个例子,假设我们的标准输入是这样的文本:
hello.world/foo
hello.world/bar
那么这段代码会打印:
hello
world
foo
bar
并且,如果我们再次输入hello.world/foo,那么代码不会打印任何东西,因为hello、world和foo已经在映射中了。
福利视频
笔者自己录制的一套php视频教程(适合0基础的),感兴趣的童鞋可以看看,基础视频总共约200多集,目前已经录制完毕,后续还有更多视频出品
https://space.bilibili.com/177546377/channel/seriesdetail?sid=2949374
技术交流
技术交流请加笔者微信:richardo1o1
原文始发于微信公众号(迪哥讲事):一个自定义字典工具的源码分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论