|
5 | 5 | - 字符串长度;
|
6 | 6 | - 求子串;
|
7 | 7 | - 是否存在某个字符或子串;
|
8 |
| -- 子串出现的次数; |
| 8 | +- 子串出现的次数(字符串匹配); |
9 | 9 | - 字符串分割(切分)为[]string;
|
10 |
| -- 是否存在某个前缀或后缀; |
11 |
| -- 字符或子串在字符串中的首次出现的位置或最后一次出现的位置; |
| 10 | +- 字符串是否有某个前缀或后缀; |
| 11 | +- 字符或子串在字符串中首次出现的位置或最后一次出现的位置; |
12 | 12 | - 通过某个字符串将[]string连接起来;
|
13 | 13 | - 字符串重复几次;
|
14 | 14 | - 字符串中子串替换;
|
|
59 | 59 |
|
60 | 60 | 关于Index相关函数的实现,我们后面介绍。
|
61 | 61 |
|
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 | + // 有三个 |
63 | 208 |
|
64 | 209 |
|
65 | 210 |
|
|
0 commit comments