1.正则表达式概述
正则表达式(Regular Expression)是一种用于描述字符串模式的强大工具,广泛应用于文本搜索、替换、验证等场景。它由普通字符和特殊字符(元字符)组成,能够定义复杂的匹配模式。
2.正则表达式基本语法
2.1 基本元字符
-
.
匹配任意单个字符(除换行符) -
^
匹配字符串开始 -
$
匹配字符串结束 -
*
匹配前一个字符0次或多次 -
+
匹配前一个字符1次或多次 -
?
匹配前一个字符0次或1次 -
{n}
匹配前一个字符恰好n次 -
{n,}
匹配前一个字符至少n次 -
{n,m}
匹配前一个字符n到m次
2.2 字符类
-
[abc]
匹配a、b或c中的任意一个 -
[^abc]
匹配除a、b、c外的任意字符 -
[a-z]
匹配任意小写字母 -
\d
匹配数字,等价于[0-9] -
\w
匹配单词字符,等价于[A-Za-z0-9_] -
\s
匹配空白字符(空格、制表符、换行符等)
3.正则表达式匹配算法
3.1 非确定性有限自动机(NFA)方法
NFA是最常用的正则表达式实现方式,其特点包括:
-
每个状态可能有多个转移
-
允许ε转移(不消耗输入字符的转移)
-
需要回溯机制
算法步骤:
-
将正则表达式转换为NFA
-
从初始状态开始,同时跟踪所有可能的状态
-
对于每个输入字符,计算可能到达的状态集合
-
如果最终状态集合包含接受状态,则匹配成功
3.2 确定性有限自动机(DFA)方法
DFA是另一种实现方式,特点包括:
-
每个状态对于特定输入只有一个转移
-
不需要回溯
-
匹配速度通常比NFA快
算法步骤:
-
将正则表达式转换为NFA
-
将NFA转换为DFA(使用子集构造法)
-
从初始状态开始,根据输入字符进行状态转移
-
如果最终到达接受状态,则匹配成功
4.正则表达式引擎实现
4.1 基础递归实现
package main
import (
"fmt"
)
// 基础正则表达式匹配(递归实现)
func isMatch(text string, pattern string) bool {
// 如果模式为空,文本也必须为空才匹配
if len(pattern) == 0 {
return len(text) == 0
}
// 检查第一个字符是否匹配
firstMatch := len(text) > 0 && (pattern[0] == text[0] || pattern[0] == '.')
// 处理 '*' 情况
if len(pattern) >= 2 && pattern[1] == '*' {
// 两种情况:
// 1. 忽略 '*' 和它前面的字符(匹配0次)
// 2. 匹配一个字符后继续尝试匹配
return isMatch(text, pattern[2:]) || (firstMatch && isMatch(text[1:], pattern))
}
// 没有 '*' 的情况,简单递归
return firstMatch && isMatch(text[1:], pattern[1:])
}
func main() {
testCases := []struct {
text string
pattern string
expect bool
}{
{"aa", "a", false},
{"aa", "a*", true},
{"ab", ".*", true},
{"aab", "c*a*b", true},
{"mississippi", "mis*is*p*.", false},
}
for _, tc := range testCases {
result := isMatch(tc.text, tc.pattern)
fmt.Printf("text: %q, pattern: %q, result: %v, expect: %v\n",
tc.text, tc.pattern, result, tc.expect)
}
}
4.2 动态规划实现(更高效)
package main
import (
"fmt"
)
// 动态规划实现正则表达式匹配
func isMatchDP(text string, pattern string) bool {
// 创建DP表,dp[i][j]表示text[:i]和pattern[:j]是否匹配
dp := make([][]bool, len(text)+1)
for i := range dp {
dp[i] = make([]bool, len(pattern)+1)
}
// 空文本和空模式匹配
dp[0][0] = true
// 处理模式如a*, a*b*, a*b*c*等可以与空文本匹配的情况
for j := 1; j <= len(pattern); j++ {
if pattern[j-1] == '*' {
dp[0][j] = dp[0][j-2]
}
}
for i := 1; i <= len(text); i++ {
for j := 1; j <= len(pattern); j++ {
if pattern[j-1] == '.' || pattern[j-1] == text[i-1] {
dp[i][j] = dp[i-1][j-1]
} else if pattern[j-1] == '*' {
// 两种情况:
// 1. 忽略 '*' 和它前面的字符(匹配0次)
// 2. 如果前一个字符匹配,则可以匹配多次
dp[i][j] = dp[i][j-2] || ((pattern[j-2] == '.' || pattern[j-2] == text[i-1]) && dp[i-1][j])
}
}
}
return dp[len(text)][len(pattern)]
}
func main() {
testCases := []struct {
text string
pattern string
expect bool
}{
{"aa", "a", false},
{"aa", "a*", true},
{"ab", ".*", true},
{"aab", "c*a*b", true},
{"mississippi", "mis*is*p*.", false},
{"aaa", "a*a", true},
{"ab", ".*c", false},
}
for _, tc := range testCases {
result := isMatchDP(tc.text, tc.pattern)
fmt.Printf("text: %q, pattern: %q, result: %v, expect: %v\n",
tc.text, tc.pattern, result, tc.expect)
}
}
4.3 更完整的正则表达式实现(支持更多特性)
package main
import (
"fmt"
"unicode/utf8"
)
type state struct {
out rune // 匹配的字符,0表示ε转移
out1 *state // 第一个转移状态
out2 *state // 第二个转移状态(用于分支)
lastlist int // 用于跟踪是否已经处理过
}
type matcher struct {
start *state
}
// 将正则表达式编译为NFA
func compile(pattern string) *matcher {
// 这里简化处理,实际实现需要更复杂的解析
// 仅支持 . * | 和普通字符
// 实际的正则表达式编译器会更复杂
// 简化实现:假设pattern是"a*b"这样的简单形式
start := &state{}
s1 := &state{out: 'a', out1: start} // a*
s2 := &state{out: 'b'} // b
start.out1 = s1
start.out2 = s2
return &matcher{start: start}
}
// 匹配函数
func (m *matcher) match(text string) bool {
current := []*state{}
next := []*state{}
listid := 0
addState := func(s *state) {
if s == nil || s.lastlist == listid {
return
}
s.lastlist = listid
if s.out == 0 {
// ε转移
addState(s.out1)
addState(s.out2)
} else {
next = append(next, s)
}
}
// 从起始状态开始
addState(m.start)
current, next = next, current
for _, r := range text {
listid++
next = next[:0]
for _, s := range current {
if s.out == '.' || s.out == r {
addState(s.out1)
}
}
current, next = next, current
if len(current) == 0 {
return false
}
}
// 检查是否有到达接受状态
for _, s := range current {
if s.out1 == nil && s.out2 == nil {
return true
}
}
return false
}
func main() {
// 简化测试
re := compile("a*b")
fmt.Println("Match 'aab':", re.match("aab")) // true
fmt.Println("Match 'b':", re.match("b")) // true
fmt.Println("Match 'ac':", re.match("ac")) // false
}
4.4 使用Go标准库regexp
package main
import (
"fmt"
"regexp"
)
func main() {
// 编译正则表达式
re := regexp.MustCompile(`^a.*b$`)
// 测试匹配
fmt.Println(re.MatchString("aabb")) // true
fmt.Println(re.MatchString("acb")) // true
fmt.Println(re.MatchString("ab")) // true
fmt.Println(re.MatchString("ba")) // false
// 查找匹配项
re = regexp.MustCompile(`\d+`)
fmt.Println(re.FindAllString("123 abc 456 def", -1)) // [123 456]
// 替换
re = regexp.MustCompile(`(\d+)-(\d+)`)
fmt.Println(re.ReplaceAllString("123-456", "$2-$1")) // "456-123"
}
5.正则表达式优化技术
5.1 编译优化
-
预编译正则表达式
-
缓存已编译的正则表达式对象
5.2 匹配优化
-
惰性量词(在量词后加
?
) -
原子分组(减少回溯)
-
占有量词(避免回溯)
5.3 性能考虑
-
避免过度使用回溯
-
使用更具体的字符类
-
避免嵌套量词
6.总结
正则表达式是文本处理中不可或缺的工具,理解其底层算法有助于编写更高效、更准确的正则表达式。未来随着正则表达式引擎的不断优化,以及与其他文本处理技术(如自然语言处理)的结合,正则表达式将继续在数据处理领域发挥重要作用。