实现一个正则表达式引擎in Python(三)
项目地址:Regex in Python
前两篇已经完成的写了一个基于NFA的正则表达式引擎了,下面要做的就是更近一步,把NFA转换为DFA,并对DFA最小化
DFA的定义
对于NFA转换为DFA的算法,主要就是将NFA中可以状态节点进行合并,进而让状态节点对于一个输入字符都有唯一的一个跳转节点
所以对于DFA的节点就含有一个nfa状态节点的集合和一个唯一的标识和对是否是接收状态的flag
class Dfa(object):
STATUS_NUM = 0
def __init__(self):
self.nfa_sets = []
self.accepted = False
self.status_num = -1
@classmethod
def nfas_to_dfa(cls, nfas):
dfa = cls()
for n in nfas:
dfa.nfa_sets.append(n)
if n.next_1 is None and n.next_2 is None:
dfa.accepted = True
dfa.status_num = Dfa.STATUS_NUM
Dfa.STATUS_NUM = Dfa.STATUS_NUM + 1
return dfa
NFA转换为DFA
将NFA转换为DFA的最终目标是获得一张跳转表,这个和之前C语言编译的语法分析表有点像
这个函数就是NFA转换为DFA的全部算法了,主要逻辑就是:
- 先利用之前的closure算法,计算出可以合并的NFA节点,然后生成一个DFA的节点
- 然后对这个DFA集合进行遍历
- 之后对于每个输入字符进行move操作,然后对得到的move集合再进行一次closure操作,这样就可以得到下一个DFA状态节点(这里还要进行一个判重的操作,就是可能当前DFA状态节点可能已经生成过了)
- 然后将这两个节点的对应关系放入跳转表中
- 这时候的DFA如果其中含有的NFA存在一个可接收的状态节点,那么当前的DFA的当然也是可接受状态了
def convert_to_dfa(nfa_start_node):
jump_table = list_dict(MAX_DFA_STATUS_NUM)
ns = [nfa_start_node]
n_closure = closure(ns)
dfa = Dfa.nfas_to_dfa(n_closure)
dfa_list.append(dfa)
dfa_index = 0
while dfa_index < len(dfa_list):
dfa = dfa_list[dfa_index]
for i in range(ASCII_COUNT):
c = chr(i)
nfa_move = move(dfa.nfa_sets, c)
if nfa_move is not None:
nfa_closure = closure(nfa_move)
if nfa_closure is None:
continue
new_dfa = convert_completed(dfa_list, nfa_closure)
if new_dfa is None:
new_dfa = Dfa.nfas_to_dfa(nfa_closure)
dfa_list.append(new_dfa)
next_state = new_dfa.status_num
jump_table[dfa.status_num][c] = next_state
if new_dfa.accepted:
jump_table[new_dfa.status_num]['accepted'] = True
dfa_index = dfa_index + 1
return jump_table
DFA最小化
DFA最小化本质上是也是对状态节点的合并,然后分区
- 先根据是否为接收状态进行分区
- 再根据DFA跳转表的跳转关系对分区里的节点进行再次分区,如果当前DFA节点跳转后的状态节点也位于同一个分区中,证明它们可以被归为一个分区
- 重复上面的算法
Dfa分区定义
DfaGroup和之前的定义大同小异,都是有一个唯一的标识和一个放DFA状态节点的list
class DfaGroup(object):
GROUP_COUNT = 0
def __init__(self):
self.set_count()
self.group = []
def set_count(self):
self.group_num = DfaGroup.GROUP_COUNT
DfaGroup.GROUP_COUNT = DfaGroup.GROUP_COUNT + 1
def remove(self, element):
self.group.remove(element)
def add(self, element):
self.group.append(element)
def get(self, count):
if count > len(self.group) - 1:
return None
return self.group[count]
def __len__(self):
return len(self.group)
Minimize DFA
partition是最小化DFA算法最重要的部分
- 会先从跳转表中找出当前DFA对应跳转的下一个状态节点
- first是用来比较的DFA节点
- 如果next节点的下一个状态和first节点的下一状态不在同一分区下的话,说明它们不可以在同一个分区
- 就重新创建一个新分区
所以其实DFA最小化做的就是合并相同的下一个跳转状态的节点
def partition(jump_table, group, first, next, ch):
goto_first = jump_table[first.status_num].get(ch)
goto_next = jump_table[next.status_num].get(ch)
if dfa_in_group(goto_first) != dfa_in_group(goto_next):
new_group = DfaGroup()
group_list.append(new_group)
group.remove(next)
new_group.add(next)
return True
return False
创建跳转表
再分完区之后节点和节点间的跳转就变成了区和区间的跳转了
- 遍历DFA集合
- 从之前的跳转表中找到相应的节点和相应的跳转关系
- 然后找出它们对应的分区,即转换为分区和分区之间的跳转
def create_mindfa_table(jump_table):
trans_table = list_dict(ASCII_COUNT)
for dfa in dfa_list:
from_dfa = dfa.status_num
for i in range(ASCII_COUNT):
ch = chr(i)
to_dfa = jump_table[from_dfa].get(ch)
if to_dfa:
from_group = dfa_in_group(from_dfa)
to_group = dfa_in_group(to_dfa)
trans_table[from_group.group_num][ch] = to_group.group_num
if dfa.accepted:
from_group = dfa_in_group(from_dfa)
trans_table[from_group.group_num]['accepted'] = True
return trans_table
匹配输入字符串
利用跳转表进行对输入字符串的匹配的逻辑非常简单
- 遍历输入的字符串
- 拿到当前状态对应的输入的跳转关系
- 进行跳转或者完成匹配
def dfa_match(input_string, jump_table, minimize=True):
if minimize:
cur_status = dfa_in_group(0).group_num
else:
cur_status = 0
for i, c in enumerate(input_string):
jump_dict = jump_table[cur_status]
if jump_dict:
js = jump_dict.get(c)
if js is None:
return False
else:
cur_status = js
if i == len(input_string) - 1 and jump_dict.get('accepted'):
return True
return jump_table[cur_status].get('accepted') is not None
总结
到此已经完成了一个简单的正则表达式引擎的所有过程
正则表达式 -> NFA -> DFA -> DFA最小化 -> 进行匹配
实现一个正则表达式引擎in Python(三)的更多相关文章
- 实现一个正则表达式引擎in Python(一)
前言 项目地址:Regex in Python 开学摸鱼了几个礼拜,最近几天用Python造了一个正则表达式引擎的轮子,在这里记录分享一下. 实现目标 实现了所有基本语法 st = 'AS342abc ...
- 实现一个正则表达式引擎in Python(二)
项目地址:Regex in Python 在看一下之前正则的语法的 BNF 范式 group ::= ("(" expr ")")* expr ::= fact ...
- 1000行代码徒手写正则表达式引擎【1】--JAVA中正则表达式的使用
简介: 本文是系列博客的第一篇,主要讲解和分析正则表达式规则以及JAVA中原生正则表达式引擎的使用.在后续的文章中会涉及基于NFA的正则表达式引擎内部的工作原理,并在此基础上用1000行左右的JAVA ...
- Python的regex模块——更强大的正则表达式引擎
Python自带了正则表达式引擎(内置的re模块),但是不支持一些高级特性,比如下面这几个: 固化分组 Atomic grouping 占有优先量词 Possessive quantifi ...
- 正则表达式引擎的构建——基于编译原理DFA(龙书第三章)——3 计算4个函数
整个引擎代码在github上,地址为:https://github.com/sun2043430/RegularExpression_Engine.git nullable, firstpos, la ...
- 基于ε-NFA的正则表达式引擎
正则表达式几乎每个程序员都会用到,对于这么常见的一个语言,有没有想过怎么去实现一个呢?乍一想,也许觉得困难,实际上实现一个正则表达式的引擎并没有想像中的复杂,<编译原理>一书中有一章专门讲 ...
- 正则表达式—RegEx(RegularExpressio)(三)
今日随笔,继续写一点关于正则表达式的 知识.前两天介绍了正则表达式验证匹配,提取等一些基本的知识,今天继续分享下它的另一个强大的应用:替换(replace). 开始之前,还是要补一下昨天的内容. 在我 ...
- (转)正则表达式—RegEx(RegularExpressio)(三)
原文地址:http://www.cnblogs.com/feng-c-x/archive/2013/09/05/3302465.html 今日随笔,继续写一点关于正则表达式的 知识.前两天介绍了正则表 ...
- 正则表达式引擎:nfa的转换规则。
正则表达式引擎:nfa的转换规则. 正则到nfa 前言 在写代码的过程中,本来还想根据龙书上的说明来实现re到nfa的转换.可是写代码的时候发现,根据课本来会生成很多的无用过渡节点和空转换边,需要许多 ...
随机推荐
- net core Webapi基础工程搭建(七)——小试AOP及常规测试_Part 1
目录 前言 拦截器 异常拦截器 测试结果 身份验证拦截器 测试 小结 补充 2019-07-31 前言 一天天不知道怎么过的,但确实挺忙,事赶事不带停那种,让我感觉跟在流水线干活一样,忙活的事差不多了 ...
- 常用Feed流架构实现
业务中很多需求都会用到类似feed流的架构. 例如 微信朋友圈 微博 动态 1对N消息. 一般feed流的架构实现有下面几种. 假如现在的业务场景是微博,然后当前的数据情况是: 用户A关注了用户B和C ...
- ssh通过pem文件登陆服务器
一些为了安全操作,推荐使用私钥进行登录服务器,拿jenkins来说,默认的验证方式就是私钥 实现方式 先在本机通过ssh-keygen直接生成公私钥 如下在当前文件夹下生成my.pem(私钥)和my. ...
- Servlet 常用API学习(二)
Servlet常用API学习 一.HTTP简介 WEB浏览器与WEB服务器之间的一问一答的交互过程必须遵循一定的规则,这个规则就是HTTP协议. HTTP是 hypertext transfer pr ...
- Java多线程之线程的互斥处理
Java多线程之线程的互斥处理 一.前言 多线程程序中的各个线程都是自由运行的,所以它们有时就会同时操作同一个实例.这在某些情况下会引发问题.例如,从银行账户取款时,余额确认部分的代码应该是像下面这样 ...
- linux命令名 用法及参数 功能注解
命令名 用法及参数 功能注解 1 文件管理 # ls ls -a 列出当前目录下的所有文件,包括以.头的隐含文件 文件管理 # l ...
- macOS平台下Qt应用程序菜单翻译及调整
一.翻译 在macOS平台上,系统会为应用程序菜单添加一些额外的菜单项.先来看一些典型的例子: 这个是Qt Creator的菜单,系统为应用程序菜单添加了一些桌面显示操作相关的菜单项: 这个是Qt D ...
- js 取两位小数
var totalPrice = (product.unitPrice * product.nums).toFixed(2);
- [NOI2001]炮兵阵地 题解
题意 我们先来了解一下基本的位运算 于( \(\bigwedge\) ),或 (\(\bigvee\) ) 异或(\(\bigoplus\)) 在下面我们用(&)代表于,(|)代表或 一道状压 ...
- springmvc 全局异常解决方案
系统中异常包括两类:预期异常和运行时异常RuntimeException,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发.测试通过手段减少运行时异常的发生. 系统的dao.service. ...