Skip to content

Commit c447dd0

Browse files
committed
继续strings
1 parent d8d58ca commit c447dd0

File tree

1 file changed

+149
-4
lines changed

1 file changed

+149
-4
lines changed

Diff for: chapter02/02.1.md

+149-4
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
- 字符串长度;
66
- 求子串;
77
- 是否存在某个字符或子串;
8-
- 子串出现的次数;
8+
- 子串出现的次数(字符串匹配)
99
- 字符串分割(切分)为[]string;
10-
- 是否存在某个前缀或后缀
11-
- 字符或子串在字符串中的首次出现的位置或最后一次出现的位置
10+
- 字符串是否有某个前缀或后缀
11+
- 字符或子串在字符串中首次出现的位置或最后一次出现的位置
1212
- 通过某个字符串将[]string连接起来;
1313
- 字符串重复几次;
1414
- 字符串中子串替换;
@@ -59,7 +59,152 @@
5959

6060
关于Index相关函数的实现,我们后面介绍。
6161

62-
## 2.1.2 子串出现次数 ##
62+
## 2.1.2 子串出现次数(字符串匹配) ##
63+
64+
在数据结构与算法中,可能会讲解一下字符串匹配算法:
65+
66+
- 朴素匹配算法
67+
- KMP算法
68+
- Rabin-Karp算法
69+
- Boyer-Moore算法
70+
71+
还有其他的算法,这里不一一列举,感兴趣的可以网上搜一下。
72+
73+
在Go中,查找子串出现次数即字符串模式匹配,实现的是Rabin-Karp算法。Count 函数的签名如下:
74+
75+
func Count(s, sep string) int
76+
77+
在 Count 的实现中,处理了几种特殊情况,属于字符匹配预处理的一部分。这里要特别说明一下的是当 sep 为空时,Count 的返回值是:utf8.RuneCountInString(s) + 1
78+
79+
fmt.Println(strings.Count("five", "")) // before & after each rune
80+
81+
输出:
82+
83+
5
84+
85+
关于Rabin-Karp算法的实现,有兴趣的可以看看 Count 的源码。
86+
87+
另外,Count 是计算子串在字符串中出现的无重叠的次数,比如:
88+
89+
fmt.Println(strings.Count("fivevev", "vev"))
90+
91+
输出:
92+
93+
1
94+
95+
## 2.1.3 字符串分割为[]string ##
96+
97+
这个需求很常见,倒不一定是为了得到[]string。
98+
99+
该包提供了六个三组分割函数:Fields 和 FieldsFunc、Split 和 SplitAfter、SplitN 和 SplitAfterN。
100+
101+
### 2.1.3.1 Fields 和 FieldsFunc ###
102+
103+
这两个函数的签名如下:
104+
105+
func Fields(s string) []string
106+
func FieldsFunc(s string, f func(rune) bool) []string
107+
108+
Fields 用一个或多个连续的空格分隔字符串 s,返回子字符串的数组(slice)。如果字符串 s 只包含空格,则返回空列表([]string的长度为0)。其中,空格的定义是 unicode.IsSpace,之前已经介绍过。
109+
110+
由于是用空格分隔,因此结果中不会含有空格或空子字符串,例如:
111+
112+
fmt.Printf("Fields are: %q", strings.Fields(" foo bar baz "))
113+
114+
输出:
115+
116+
Fields are: ["foo" "bar" "baz"]
117+
118+
FieldsFunc 用这样的Unicode代码点 c 进行分隔:满足 f(c) 返回 true。该函数返回[]string。如果字符串 s 中所有的代码点(unicode code points)都满足f(c)或者 s 是空,则 FieldsFunc 返回空slice。
119+
120+
也就是说,我们可以通过实现一个回调函数来指定分隔字符串 s 的字符。比如上面的例子,我们通过 FieldsFunc 来实现:
121+
122+
fmt.Println(strings.FieldsFunc(" foo bar baz ", unicode.IsSpace))
123+
124+
实际上,Fields 函数就是调用 FieldsFunc 实现的:
125+
126+
func Fields(s string) []string {
127+
return FieldsFunc(s, unicode.IsSpace)
128+
}
129+
130+
对于 FieldsFunc 源码留给读者自己阅读。
131+
132+
### 2.1.3.2 Split 和 SplitAfter、 SplitN 和 SplitAfterN ###
133+
134+
之所以将这四个函数放在一起讲,是因为它们都是通过一个同一个内部函数来实现的。它们的函数签名及其实现:
135+
136+
func Split(s, sep string) []string { return genSplit(s, sep, 0, -1) }
137+
func SplitAfter(s, sep string) []string { return genSplit(s, sep, len(sep), -1) }
138+
func SplitN(s, sep string, n int) []string { return genSplit(s, sep, 0, n) }
139+
func SplitAfterN(s, sep string, n int) []string { return genSplit(s, sep, len(sep), n) }
140+
141+
它们都调用了 genSplit 函数。
142+
143+
这四个函数都是通过 sep 进行分割,返回[]string。如果 sep 为空,相当于分成一个个的 UTF-8 字符,如 `Split("abc","")`,得到的是[a b c]
144+
145+
Split(s, sep) 和 SplitN(s, sep, -1) 等价;SplitAfter(s, sep) 和 SplitAfterN(s, sep, -1) 等价。
146+
147+
那么,Split 和 SplitAfter 有啥区别呢?通过这两句代码的结果就知道它们的区别了:
148+
149+
fmt.Printf("%q\n", strings.Split("foo,bar,baz", ","))
150+
fmt.Printf("%q\n", strings.SplitAfter("foo,bar,baz", ","))
151+
152+
输出:
153+
154+
["foo" "bar" "baz"]
155+
["foo," "bar," "baz"]
156+
157+
也就是说,Split 会将 s 中的 sep 去掉,而 SplitAfter 会保留 sep。
158+
159+
带 N 的方法可以通过最后一个参数 n 控制返回的结果中的 slice 中的元素个数,当 n < 0 时,返回所有的子字符串;当 n == 0 时,返回的结果是 nil;当 n > 0 时,表示返回的 slice 中最多只有 n 个元素,其中,最后一个元素不会分割,比如:
160+
161+
fmt.Printf("%q\n", strings.SplitN("foo,bar,baz", ",", 2))
162+
163+
输出:
164+
165+
["foo" "bar,baz"]
166+
167+
另外看一下官方文档提供的例子,注意一下输出结果:
168+
169+
fmt.Printf("%q\n", strings.Split("a,b,c", ","))
170+
fmt.Printf("%q\n", strings.Split("a man a plan a canal panama", "a "))
171+
fmt.Printf("%q\n", strings.Split(" xyz ", ""))
172+
fmt.Printf("%q\n", strings.Split("", "Bernardo O'Higgins"))
173+
174+
输出:
175+
176+
["a" "b" "c"]
177+
["" "man " "plan " "canal panama"]
178+
[" " "x" "y" "z" " "]
179+
[""]
180+
181+
## 2.1.4 字符串是否有某个前缀或后缀 ##
182+
183+
这两个函数比较简单,源码如下:
184+
185+
// s 中是否以 prefix 开始
186+
func HasPrefix(s, prefix string) bool {
187+
return len(s) >= len(prefix) && s[0:len(prefix)] == prefix
188+
}
189+
// s 中是否以 suffix 结尾
190+
func HasSuffix(s, suffix string) bool {
191+
return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
192+
}
193+
194+
## 2.1.5 字符或子串在字符串中出现的位置 ##
195+
196+
有一序列函数与该功能有关:
197+
198+
// 在 s 中查找 sep 的第一次出现,返回第一次出现的索引
199+
func Index(s, sep string) int
200+
// chars中任何一个Unicode代码点在s中首次出现的位置
201+
func IndexAny(s, chars string) int
202+
// 查找字符 c 在 s 中第一次出现的位置,其中 c 满足 f(c) 返回 true
203+
func IndexFunc(s string, f func(rune) bool) int
204+
// Unicode 代码点 r 在 s 中第一次出现的位置
205+
func IndexRune(s string, r rune) int
206+
207+
// 有三个
63208

64209

65210

0 commit comments

Comments
 (0)