前言
有穷自动机是词法分析的核心,它的本质是一个识别器,对于每个可能的输入串回答是或者否。
这系列文章将详细介绍如何把输入的正则表达式变为确定的有穷自动机(DFA)。实现的语言是Python,版本3.6
一、输入的处理
(一)增加与逻辑
一般来说,输入的时候,与逻辑的那个点是忽略不计的。比如a·b一般都写成ab
所以干脆就默认输入中都没有与逻辑那个点。
然后需要做的第一步就是给输入添加与逻辑。如果假设DFA只接受* · | ()五个运算符号,a-z,A-Z,0-9为普通输入符号(字母表中字母),在什么地方的后面应该加与逻辑的点呢?
想了很久也没有想到什么简便的方法,所以咱就暴力一点吧。
把输入符号分为五类
1. a 表示字母表中的字母
2. *
3. |
4. (
5. )
穷举任意两类元素,发现只有 1 1;1 4; 2 1; 2 4; 5 1;5 4; 之间需要加点,为了打字方便我直接用的是英语句号的点.
用 judge 来表示上一个元素的类型
judge=1 表示 a 或者 * 或者 )
judge=2 表示 | 或者 (
代码如下
def add_mul(s):
'''
算法思路,穷举法,将输入分为
1. a 表示字母表中的字母
2. *
3. |
4. (
5. )
穷举任意两个元素,发现只有 11 14; 21 24; 51 54; 需要加点
用judge来表示上一个元素的类型
1. a 或者 * 或者 )
2. | 或者 (
:param s: 输入的字符串
:return: 添加 . 符号后的字符数组
'''
ss = []
judge = 2
for i in range (len(s)):
if judge == 1:
if s[i] in alphabet or s[i] == '(':
ss.append('.')
ss.append(s[i])
else:
ss.append(s[i])
elif judge == 2:
ss.append(s[i])
if s[i] in alphabet or s[i] == '*' or s[i] == ')':#更新judge的值
judge = 1
else:
judge = 2
return ss
需要说明的是强烈不推荐s ss这种变量命名方式,代码长了你都想宰了你自己,但这只是一个小的处理函数,用的时候也是整体的用,无伤大雅,就睁一只眼闭一只眼吧。
(二)将输入转换成逆波兰表达式
逆波兰表达式简单的说就是操作数在前,操作符在后。之所以将输入转换成逆波兰表达式,是为了便于后面构造NFA。
使用的算法名为调度场算法(shunting-yard algorithm)
核心思想就是把运算符按优先级大小从高到低排序,然后从高到低输出,表示也就是我们通常的乘法在加法之前算的这么一个规则,然后再考虑一下括号什么的边边角角的特殊情况。
具体的操作如下:
- 建立运算符栈用于运算符的存储,此运算符遵循越往栈顶优先级越高的原则
- 顺序扫描表达式,如果当前字符是字母(优先级为0的符号),则直接输出;如果当前字符为运算符或者括号(优先级不为0的符号),则判断:
(1) 若当前运算符为’(’,直接入栈;
(2) 若为’)’,出栈并顺序输出运算符直到遇到第一个’(’,遇到的第一个’(‘出栈但不输出;
(3) 若为其它,比较运算符栈栈顶元素与当前元素的优先级:
A. 如果栈顶元素是’(’,当前元素直接入栈;
B. 如果栈顶元素优先级>=当前元素优先级,出栈并顺序输出运算符直到栈顶元素优先级<当前元素优先级,然后当前元素入栈;
C. 如果栈顶元素优先级<当前元素优先级,当前元素直接入栈。 - 重复上述操作直至表达式扫描完毕
- 顺序出栈并输出运算符直到栈元素为空
python实现的代码如下:
# 2.转换为逆波兰表达式
# 2.1判断是否在字母表中
def is_in(c):
for i in range(alphabet_lentgh):
if(c == alphabet[i]):
return True;
return False;
# 2.2返回操作符的优先级
def operational_character_priority(c):
if(c == '*'):
return 1;
if(c == '.'):
return 2;
if(c == '|'):
return 3;
if(c == '('):
return 4;
if(c == '#'):
return 5;
return -1;
# 2.3调度场算法
def reverse_polish_notation(ss): #turn into reverse polish notation
operator_stack=[];
reverse_polish=[];
#终止符
operator_stack.append('#');
for i in range(len(ss)):
if(is_in(ss[i])):
reverse_polish.append(ss[i]);
elif(ss[i]=='('):
operator_stack.append(ss[i]);
elif(ss[i]==')'):
while(True):
if(operator_stack[len(operator_stack)-1]=='('):
operator_stack.pop();
break;
else:
reverse_polish.append(operator_stack[len(operator_stack)-1]);
operator_stack.pop();
else:
while(True):
if(operational_character_priority(ss[i])>operational_character_priority(operator_stack[len(operator_stack)-1])):#优先级高的出栈
reverse_polish.append(operator_stack[len(operator_stack)-1]);
operator_stack.pop();
else:
operator_stack.append(ss[i]);
break;
for x in range (len(operator_stack)-1):
t = len(operator_stack)-1
if(operator_stack[t]!='#'):
reverse_polish.append(operator_stack[t]);
operator_stack.pop()
return reverse_polish;