基于NFA的简单正则表达式
关于正则表达式
正则表达式可以简单说是模式识别,通过一个可变模式,匹配文本字符串中符合模式的字符串。
对于编程和文本工作人员,正则表达式是生产力工具,对于互联网这种连接信息的载体,正则表达式无处不在,同时不好的正则表达式,可能成为程序瓶颈。
NFA
读过2022-05-04 KMP算法,基于DFA的子字符串查找的同仁,应该对 DFA (确定有限自动机)不陌生,通过对匹配文本的字符进行状态转移,可以线性的解决子字符串查找问题。
但对于正则表达式,DFA就稍有问题,因为表达式模式不确定,无法用一个简单DFA表示,需要引入NFA,非确定有限自动机。
对比DFA,NFA的状态转移是不确定的,但是有限的,我们用有向图的数据结构替代数组,构建NFA。
epsilon 转换
正则表达式与普通字符串的区别是含有epsilon转换,也就是功能表达而非字符值。
来个例子:
“ABAC” :普通字符串,只含有要匹配的字母。
“(A*B|AC)" :正则表达式,含有功能字符,“( )”, " * ", " | ",这些字符不是用来直接匹配的,括号改变优先级,确定其他功能符号的作用范围,星号代表前面的字符或子表达式重复 0 - N 次,N 属于自然数。或符号表达或的语义,相较纯字符串,要复杂的多。
所谓 epsilon 转换,就是将这些功能符号的功能翻译成状态转移的方案,承载这个方案的,就是有向图。
转换过程
基本规则:
遇到左括号,向后移动一位;
'(' - > next
遇到星号,如果前一位是字符,则向前一位,向后一位。
'*' -> next
'*' -> pre
遇到星号,如果前一位是 " ) ",则向前找到 " ( ",同时 " ( " 到星号,向后一位。
'*' -> '('
'(' -> '*'
'*' -> next
遇到右括号,寻找最近的 " | " 或者 " ( ",如果最近的是 “ | ”,则继续寻找左括号,“或”指向右括号,左括号指向“或”+1位置
'|' -> ')'
'(' -> '|' + 1
为了完成转换,我们需要将左括号 " ( " 和或 " | " 进行压栈操作,并在碰到右括号 " ) " 时弹出匹配。
匹配过程
我们将 epsilon 转换构造为有向图,从顶点 0 出发,找到所有可达顶点 v ,如果匹配模式字符串 regExpStr[ v] 等于文本 txt 的第 0 个字符 txt[0],或者为 “.” 则匹配成功,将 v 后一个顶点 v+1 放入匹配表 matchTable。
再根据 matchTable 中的顶点获取可达顶点,如果有匹配第 1 个字符,则将下一个顶点放入匹配表 matchTable中,如此继续,直至达到最后一个 txt 的字符。此时,如果可达顶点 v 中有值等于 txt 的 size,则匹配成功。
以下是代码,注释的比较详细了,可以一边测试,一边熟悉匹配过程。
#ifndef REGEXP
#define REGEXP
#include <algorithm>
#include <iostream>
#include <list>
#include <string>
#include <vector>
namespace GP
{
//有向图类
struct Digraph
{
//有向图类默认构造函数
Digraph() = default;
//创建一幅含有v个顶点但无边的有向图
explicit Digraph(int V) : numV(V)
{
adjTable = std::vector<std::vector<int>>(V);
}
//顶点总数
auto V() const -> int
{
return numV;
}
//边的总数
auto E() const -> int
{
return numE;
}
//有向图中添加一条边v->w
void addEdge(int v, int w)
{
adjTable[v].push_back(w);
++numE;
}
//由v指出的边所连接的所有顶点
auto adj(int v) const -> std::vector<int>
{
return adjTable[v];
}
//该图的反向图
auto reverse() const -> Digraph
{
Digraph R(numV);
for (int v = 0; v != numV; ++v)
{
for (auto &&w : adj(v))
{
R.addEdge(w, v);
}
}
return R;
}
//对象的字符串表示
auto toString() const -> std::string
{
std::string s = std::to_string(numV) + " vertices, " +
std::to_string(numE) + " edges\n";
for (int v = 0; v != numV; ++v)
{
s += std::to_string(v) + ": ";
for (auto &&w : adj(v))
{
s += std::to_string(w) + " ";
}
s += "\n";
}
return s;
}
private:
//顶点数量
int numV = 0;
//边数量
int numE = 0;
//邻接表
std::vector<std::vector<int>> adjTable;
};
//有向图可达性类
struct DirectedDFS
{
DirectedDFS() = default;
//在G中找到从s可达的所有顶点
DirectedDFS(const Digraph &G, int s)
{
markedTable = std::vector<bool>(G.V());
dfs(G, s);
}
//在G中找到从sources中的所有顶点可达的所有顶点
DirectedDFS(const Digraph &G, const std::vector<int> &sources)
{
markedTable = std::vector<bool>(G.V());
for (auto &&s : sources)
{
if (!markedTable[s])
{
dfs(G, s);
}
}
}
// v是可达的么?
auto marked(int v) -> bool
{
return markedTable[v];
}
private:
//深度优先搜索
void dfs(const Digraph &G, int v)
{
markedTable[v] = true;
for (auto &&w : G.adj(v))
{
if (!markedTable[w])
{
dfs(G, w);
}
}
}
//标记表
std::vector<bool> markedTable;
};
} // namespace GP
namespace REG
{
//非确定有限状态自动机
struct NFA
{
//构造非确定有限状态自动机
explicit NFA(std::string regExp)
: regExpStr(std::move(regExp)),
regStrSize(static_cast<int>(regExpStr.size())),
matchDiG(GP::Digraph(regStrSize + 1))
{
// 设置 "(", "|" 的栈,用于对应 ")"
std::vector<int> orLBracketStack;
orLBracketStack.reserve(regStrSize);
for (int regCharOrder = 0; regCharOrder != regStrSize; ++regCharOrder)
{
int leftBracket = regCharOrder;
//遇到 "(" "|" 压栈
if (regExpStr[regCharOrder] == '(' ||
regExpStr[regCharOrder] == '|')
{
orLBracketStack.push_back(regCharOrder);
}
//遇到 ")" 出栈
else if (regExpStr[regCharOrder] == ')')
{
int orSymbol = orLBracketStack.back();
orLBracketStack.pop_back();
//如果出栈的是 "|"
if (regExpStr[orSymbol] == '|')
{
//再出栈
leftBracket = orLBracketStack.back();
orLBracketStack.pop_back();
//有向图添加两条边,以 leftBracket 指向 orSymbol + 1
matchDiG.addEdge(leftBracket, orSymbol + 1);
// orSymbol 指向 rightBracket ")"
matchDiG.addEdge(orSymbol, regCharOrder);
}
else
{
leftBracket = orSymbol;
}
}
//如果下一个字符为 "*"
if (regCharOrder < regStrSize - 1 &&
regExpStr[regCharOrder + 1] == '*')
{
//有向图加入 2 条边,以 leftBracket 或 regCharOrder 指向 "*"
matchDiG.addEdge(leftBracket, regCharOrder + 1);
//以 "*" 指向 leftBracket 或 regCharOrder
matchDiG.addEdge(regCharOrder + 1, leftBracket);
}
//如果 regCharOrder 是 "(" 或 ")" 或 "*"
if (regExpStr[regCharOrder] == '(' ||
regExpStr[regCharOrder] == '*' ||
regExpStr[regCharOrder] == ')')
{
//有向图加入一条边,以 regCharOrder 指向 regCharOrder + 1
matchDiG.addEdge(regCharOrder, regCharOrder + 1);
}
}
}
//识别文本是否复合正则表达式
auto recognizes(const std::string &txt) -> bool
{
//可达位置表
std::vector<int> pc;
//匹配表
std::vector<int> match;
//构建正则表达式从 0 位可达的位置
GP::DirectedDFS dfs(matchDiG, 0);
//将可达位置输入可达表内
for (int v = 0; v != matchDiG.V(); ++v)
{
if (dfs.marked(v))
{
pc.push_back(v);
}
}
//扫描文本进行匹配
for (char i : txt)
{
match.clear();
//所有可达位置
for (int v : pc)
{
if (v < regStrSize)
{
//如果有可达位置匹配文本字符,或可达位置是 "."
if (regExpStr[v] == i || regExpStr[v] == '.')
{
//匹配字符向后移动一位
match.push_back(v + 1);
}
}
}
//清空可达位置表
pc.clear();
//根据匹配的位置出发,重新构建可达位置
dfs = GP::DirectedDFS(matchDiG, match);
//遍历 epsilon 转换有向图
for (int v = 0; v != matchDiG.V(); ++v)
{
//如果可达,输入可达位置表
if (dfs.marked(v))
{
pc.push_back(v);
}
}
}
//如果任意可达位置表中有等于 regStrSize 值,返回有匹配,否则返回匹配失败
return std::any_of(pc.rbegin(), pc.rend(),
[this](int v) -> bool { return v == regStrSize; });
}
private:
// 正则匹配表达式
std::string regExpStr;
// 正则匹配表达式字符数量
int regStrSize = 0;
// epsilon 转换有向图
GP::Digraph matchDiG;
};
//非确定有限状态自动机改良版
struct NFA_s
{
//构造非确定有限状态自动机
explicit NFA_s(std::string regExp)
: regExpStr(std::move(regExp)),
regStrSize(static_cast<int>(regExpStr.size())),
matchDiG(GP::Digraph(regStrSize + 1))
{
std::list<int> orLBracketStack;
for (int i = 0; i != regStrSize; ++i)
{
int leftBracket = i;
if (regExpStr[i] == '(' || regExpStr[i] == '|')
{
orLBracketStack.push_back(i);
}
else if (regExpStr[i] == ')')
{
int orSymbol = orLBracketStack.back();
orLBracketStack.pop_back();
if (regExpStr[orSymbol] == '|')
{
leftBracket = orLBracketStack.back();
orLBracketStack.pop_back();
matchDiG.addEdge(leftBracket, orSymbol + 1);
matchDiG.addEdge(orSymbol, i);
}
else
{
leftBracket = orSymbol;
}
}
if (i < regStrSize - 1 && regExpStr[i + 1] == '*')
{
matchDiG.addEdge(leftBracket, i + 1);
matchDiG.addEdge(i + 1, leftBracket);
}
if (regExpStr[i] == '(' || regExpStr[i] == '*' ||
regExpStr[i] == ')')
{
matchDiG.addEdge(i, i + 1);
}
}
}
auto recognizes(const std::string &txt) -> bool
{
std::vector<int> pc;
unsigned txtsize = txt.size();
GP::DirectedDFS dfs(matchDiG, 0);
for (int v = 0; v != matchDiG.V(); ++v)
{
if (dfs.marked(v))
{
pc.push_back(v);
}
}
for (int i = 0; i != txtsize; ++i)
{
std::vector<int> match;
for (int v : pc)
{
if (v < regStrSize)
{
if (regExpStr[v] == txt[i] || regExpStr[v] == '.')
{
match.push_back(v + 1);
}
}
}
pc.clear();
dfs = GP::DirectedDFS(matchDiG, match);
if (dfs.marked(regStrSize) && i == txtsize - 1)
{
return true;
}
for (int v = 0; v != matchDiG.V(); ++v)
{
if (dfs.marked(v))
{
pc.push_back(v);
}
}
}
return false;
}
private:
// 匹配转换
std::string regExpStr;
// 状态数量
int regStrSize = 0;
// epsilon 转换
GP::Digraph matchDiG;
};
} // namespace REG
#endif